Przeglądaj źródła

Fix an issue in the HttpRouter

* Fix an issue causing the HttpRouter was not resolving correclty the overlapping of routes.
* Add the new property `isEndOfRoute` to the `Node` class to know when a node is the end of route or not.
Victor Sigler 7 lat temu
rodzic
commit
a9551c9bb3
2 zmienionych plików z 56 dodań i 11 usunięć
  1. 21 11
      Sources/HttpRouter.swift
  2. 35 0
      XCode/Tests/SwifterTestsHttpRouter.swift

+ 21 - 11
Sources/HttpRouter.swift

@@ -7,14 +7,19 @@
 
 import Foundation
 
-
 open class HttpRouter {
     
-    public init() {
-    }
+    public init() {}
     
     private class Node {
+        
+        /// The children nodes that form the route
         var nodes = [String: Node]()
+        
+        /// Define whether or not this node is the end of a route
+        var isEndOfRoute: Bool = false
+        
+        /// The closure to handle the route
         var handler: ((HttpRequest) -> HttpResponse)? = nil
     }
     
@@ -69,15 +74,20 @@ open class HttpRouter {
     }
     
     private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
-        if let pathSegment = generator.next() {
-            if let _ = node.nodes[pathSegment] {
-                return inflate(&node.nodes[pathSegment]!, generator: &generator)
+        
+        var currentNode = node
+        
+        while let pathSegment = generator.next() {
+            if let nextNode = currentNode.nodes[pathSegment] {
+                currentNode = nextNode
+            } else {
+                currentNode.nodes[pathSegment] = Node()
+                currentNode = currentNode.nodes[pathSegment]!
             }
-            var nextNode = Node()
-            node.nodes[pathSegment] = nextNode
-            return inflate(&nextNode, generator: &generator)
         }
-        return node
+        
+        currentNode.isEndOfRoute = true
+        return currentNode
     }
     
     private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
@@ -103,7 +113,7 @@ open class HttpRouter {
             var currentIndex = index + 1
             let variableNodes = node.nodes.filter { $0.0.first == ":" }
             if let variableNode = variableNodes.first {
-                if variableNode.1.nodes.count == 0 {
+                if currentIndex == count && variableNode.1.isEndOfRoute {
                     // if it's the last element of the pattern and it's a variable, stop the search and
                     // append a tail as a value for the variable.
                     let tail = generator.joined(separator: "/")

+ 35 - 0
XCode/Tests/SwifterTestsHttpRouter.swift

@@ -153,4 +153,39 @@ class SwifterTestsHttpRouter: XCTestCase {
         XCTAssertTrue(foundStaticRoute)
         XCTAssertTrue(foundVariableRoute)
     }
+    
+    func testHttpRouterHandlesOverlappingPathsInDynamicRoutes() {
+        let router = HttpRouter()
+        let request = HttpRequest()
+        
+        let firstVariableRouteExpectation = expectation(description: "First Variable Route")
+        var foundFirstVariableRoute = false
+        router.register("GET", path: "a/:id") { request in
+            foundFirstVariableRoute = true
+            firstVariableRouteExpectation.fulfill()
+            return HttpResponse.accepted
+        }
+        
+        let secondVariableRouteExpectation = expectation(description: "Second Variable Route")
+        var foundSecondVariableRoute = false
+        router.register("GET", path: "a/:id/c") { _ in
+            foundSecondVariableRoute = true
+            secondVariableRouteExpectation.fulfill()
+            return HttpResponse.accepted
+        }
+        
+        let firstRouteResult = router.route("GET", path: "a/b")
+        let firstRouterHandler = firstRouteResult?.1
+        XCTAssertNotNil(firstRouteResult)
+        _ = firstRouterHandler?(request)
+        
+        let secondRouteResult = router.route("GET", path: "a/b/c")
+        let secondRouterHandler = secondRouteResult?.1
+        XCTAssertNotNil(secondRouteResult)
+        _ = secondRouterHandler?(request)
+        
+        waitForExpectations(timeout: 10, handler: nil)
+        XCTAssertTrue(foundFirstVariableRoute)
+        XCTAssertTrue(foundSecondVariableRoute)
+    }
 }