Skip to content

Commit 18d34f2

Browse files
committed
Fix the HttpRouter to handle overlapping of the routes
• Include the `SwifterTestHttpRouter` in the `SwifteriOSTests` • Add a unit tests for overlapped routes
1 parent 294dc8e commit 18d34f2

File tree

3 files changed

+103
-36
lines changed

3 files changed

+103
-36
lines changed

Sources/HttpRouter.swift

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -81,47 +81,76 @@ open class HttpRouter {
8181
}
8282

8383
private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
84-
guard let pathToken = generator.next()?.removingPercentEncoding else {
85-
// if it's the last element of the requested URL, check if there is a pattern with variable tail.
86-
if let variableNode = node.nodes.filter({ $0.0.first == ":" }).first {
87-
if variableNode.value.nodes.isEmpty {
88-
params[variableNode.0] = ""
89-
return variableNode.value.handler
84+
85+
var matchedRoutes = [Node]()
86+
findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedRoutes, index: 0, count: generator.reversed().count)
87+
return matchedRoutes.first?.handler
88+
}
89+
90+
/// Find the handlers for a specified route
91+
///
92+
/// - Parameters:
93+
/// - node: The root node of the tree representing all the routes
94+
/// - params: The parameters of the match
95+
/// - generator: The IndexingIterator to iterate through the pattern to match
96+
/// - matchedNodes: An array with the nodes matching the route
97+
/// - index: The index of current position in the generator
98+
/// - count: The number of elements if the route to match
99+
private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>, matchedNodes: inout [Node], index: Int, count: Int) {
100+
101+
if let pathToken = generator.next()?.removingPercentEncoding {
102+
103+
var currentIndex = index + 1
104+
let variableNodes = node.nodes.filter { $0.0.first == ":" }
105+
if let variableNode = variableNodes.first {
106+
if variableNode.1.nodes.count == 0 {
107+
// if it's the last element of the pattern and it's a variable, stop the search and
108+
// append a tail as a value for the variable.
109+
let tail = generator.joined(separator: "/")
110+
if tail.count > 0 {
111+
params[variableNode.0] = pathToken + "/" + tail
112+
} else {
113+
params[variableNode.0] = pathToken
114+
}
115+
116+
matchedNodes.append(variableNode.value)
117+
return
90118
}
119+
params[variableNode.0] = pathToken
120+
findHandler(&node.nodes[variableNode.0]!, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
91121
}
92-
return node.handler
93-
}
94-
let variableNodes = node.nodes.filter { $0.0.first == ":" }
95-
if let variableNode = variableNodes.first {
96-
if variableNode.1.nodes.count == 0 {
97-
// if it's the last element of the pattern and it's a variable, stop the search and
98-
// append a tail as a value for the variable.
99-
let tail = generator.joined(separator: "/")
100-
if tail.count > 0 {
101-
params[variableNode.0] = pathToken + "/" + tail
102-
} else {
103-
params[variableNode.0] = pathToken
104-
}
105-
return variableNode.1.handler
122+
123+
if var node = node.nodes[pathToken] {
124+
findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
106125
}
107-
params[variableNode.0] = pathToken
108-
return findHandler(&node.nodes[variableNode.0]!, params: &params, generator: &generator)
109-
}
110-
if var node = node.nodes[pathToken] {
111-
return findHandler(&node, params: &params, generator: &generator)
112-
}
113-
if var node = node.nodes["*"] {
114-
return findHandler(&node, params: &params, generator: &generator)
115-
}
116-
if let startStarNode = node.nodes["**"] {
117-
let startStarNodeKeys = startStarNode.nodes.keys
118-
while let pathToken = generator.next() {
119-
if startStarNodeKeys.contains(pathToken) {
120-
return findHandler(&startStarNode.nodes[pathToken]!, params: &params, generator: &generator)
126+
127+
if var node = node.nodes["*"] {
128+
findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
129+
}
130+
131+
if let startStarNode = node.nodes["**"] {
132+
let startStarNodeKeys = startStarNode.nodes.keys
133+
while let pathToken = generator.next() {
134+
currentIndex += 1
135+
if startStarNodeKeys.contains(pathToken) {
136+
findHandler(&startStarNode.nodes[pathToken]!, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
137+
}
121138
}
122139
}
140+
} else if let variableNode = node.nodes.filter({ $0.0.first == ":" }).first {
141+
// if it's the last element of the requested URL, check if there is a pattern with variable tail.
142+
if variableNode.value.nodes.isEmpty {
143+
params[variableNode.0] = ""
144+
matchedNodes.append(variableNode.value)
145+
return
146+
}
147+
}
148+
149+
// if it's the last element and the path to match is done then it's a pattern matching
150+
if node.nodes.isEmpty && index == count {
151+
matchedNodes.append(node)
152+
return
123153
}
124-
return nil
125154
}
126155

127156
private func stripQuery(_ path: String) -> String {

XCode/Swifter.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
7AE893EA1C05127900A29F63 /* SwifteriOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893E91C05127900A29F63 /* SwifteriOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
4141
7AE893FE1C0512C400A29F63 /* SwifterMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893FD1C0512C400A29F63 /* SwifterMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
4242
7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */; };
43+
7B11AD4B21C9A8A6002F8820 /* SwifterTestsHttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */; };
4344
7B74CFA82163C40F001BE07B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */; };
4445
7C377E181D964B96009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
4546
7C377E191D964B9F009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
@@ -780,6 +781,7 @@
780781
0858E7F41D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
781782
7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
782783
7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
784+
7B11AD4B21C9A8A6002F8820 /* SwifterTestsHttpRouter.swift in Sources */,
783785
);
784786
runOnlyForDeploymentPostprocessing = 0;
785787
};

XCode/SwifterTestsCommon/SwifterTestsHttpRouter.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import XCTest
10+
import Swifter
1011

1112
class SwifterTestsHttpRouter: XCTestCase {
1213

@@ -105,7 +106,7 @@ class SwifterTestsHttpRouter: XCTestCase {
105106
XCTAssertEqual(router.route(nil, path: "/a/b/")?.0[":var"], "")
106107
}
107108

108-
func testHttpRouterPercentEnocedPathSegments() {
109+
func testHttpRouterPercentEncodedPathSegments() {
109110

110111
let router = HttpRouter()
111112

@@ -117,4 +118,39 @@ class SwifterTestsHttpRouter: XCTestCase {
117118
XCTAssertNotNil(router.route(nil, path: "/a/%3C%3E/%5E"))
118119
}
119120

121+
func testHttpRouterHandlesOverlappingPaths() {
122+
123+
let router = HttpRouter()
124+
let request = HttpRequest()
125+
126+
let staticRouteExpectation = expectation(description: "Static Route")
127+
var foundStaticRoute = false
128+
router.register("GET", path: "a/b") { _ in
129+
foundStaticRoute = true
130+
staticRouteExpectation.fulfill()
131+
return HttpResponse.accepted
132+
}
133+
134+
let variableRouteExpectation = expectation(description: "Variable Route")
135+
var foundVariableRoute = false
136+
router.register("GET", path: "a/:id/c") { _ in
137+
foundVariableRoute = true
138+
variableRouteExpectation.fulfill()
139+
return HttpResponse.accepted
140+
}
141+
142+
let staticRouteResult = router.route("GET", path: "a/b")
143+
let staticRouterHandler = staticRouteResult?.1
144+
XCTAssertNotNil(staticRouteResult)
145+
_ = staticRouterHandler?(request)
146+
147+
let variableRouteResult = router.route("GET", path: "a/b/c")
148+
let variableRouterHandler = variableRouteResult?.1
149+
XCTAssertNotNil(variableRouteResult)
150+
_ = variableRouterHandler?(request)
151+
152+
waitForExpectations(timeout: 10, handler: nil)
153+
XCTAssertTrue(foundStaticRoute)
154+
XCTAssertTrue(foundVariableRoute)
155+
}
120156
}

0 commit comments

Comments
 (0)