Skip to content

Commit 48beccc

Browse files
slashmojoshuawright11
authored andcommitted
Add grouping of routes by path prefix (#58)
1 parent 19bf5d5 commit 48beccc

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
extension Application {
2+
/// Groups a set of endpoints by a path prefix.
3+
/// All endpoints added in the `configure` closure will
4+
/// be prefixed, but none in the handler chain that continues
5+
/// after the `.grouped`.
6+
///
7+
/// - Parameters:
8+
/// - pathPrefix: The path prefix for all routes
9+
/// defined in the `configure` closure.
10+
/// - configure: A closure for adding routes that will be
11+
/// prefixed by the given path prefix.
12+
/// - Returns: This application for chaining handlers.
13+
@discardableResult
14+
public func grouped(_ pathPrefix: String, configure: (Application) -> Void) -> Self {
15+
Services.router.pathPrefixes.append(pathPrefix)
16+
configure(self)
17+
_ = Services.router.pathPrefixes.popLast()
18+
return self
19+
}
20+
}

Sources/Alchemy/Routing/Router.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public final class Router {
2222

2323
/// Current middleware of this router.
2424
var middlewares: [Middleware] = []
25+
26+
var pathPrefixes: [String] = []
2527

2628
/// A trie that holds all the handlers.
2729
private let trie = RouterTrieNode<HTTPMethod, RouterHandler>()
@@ -42,7 +44,8 @@ public final class Router {
4244
for method: HTTPMethod,
4345
path: String
4446
) {
45-
let splitPath = path.split(separator: "/").map(String.init)
47+
let pathPrefixes = self.pathPrefixes.map { $0.hasPrefix("/") ? String($0.dropFirst()) : $0 }
48+
let splitPath = pathPrefixes + path.split(separator: "/").map(String.init)
4649
let middlewareClosures = self.middlewares.reversed().map(Middleware.intercept)
4750
self.trie.insert(path: splitPath, storageKey: method) {
4851
var next = { request in

Tests/AlchemyTests/Routing/RouterTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,53 @@ final class RouterTests: XCTestCase {
167167
// Could update the router to automatically add "/" if URI strings are missing them,
168168
// automatically add/remove trailing "/", etc.
169169
}
170+
171+
func testGroupedPathPrefix() throws {
172+
self.app
173+
.grouped("group") { app in
174+
app
175+
.register(.get1)
176+
.register(.get2)
177+
.grouped("nested") { app in
178+
app.register(.post1)
179+
}
180+
.register(.post2)
181+
}
182+
.register(.get3)
183+
184+
XCTAssertEqual(try self.app.request(TestRequest(
185+
method: .GET,
186+
path: "/group\(TestRequest.get1.path)",
187+
response: TestRequest.get1.path
188+
)), TestRequest.get1.response)
189+
190+
XCTAssertEqual(try self.app.request(TestRequest(
191+
method: .GET,
192+
path: "/group\(TestRequest.get2.path)",
193+
response: TestRequest.get2.path
194+
)), TestRequest.get2.response)
195+
196+
XCTAssertEqual(try self.app.request(TestRequest(
197+
method: .POST,
198+
path: "/group/nested\(TestRequest.post1.path)",
199+
response: TestRequest.post1.path
200+
)), TestRequest.post1.response)
201+
202+
XCTAssertEqual(try self.app.request(TestRequest(
203+
method: .POST,
204+
path: "/group\(TestRequest.post2.path)",
205+
response: TestRequest.post2.path
206+
)), TestRequest.post2.response)
207+
208+
// only available under group prefix
209+
XCTAssertNil(try self.app.request(TestRequest.get1))
210+
XCTAssertNil(try self.app.request(TestRequest.get2))
211+
XCTAssertNil(try self.app.request(TestRequest.post1))
212+
XCTAssertNil(try self.app.request(TestRequest.post2))
213+
214+
// defined outside group --> still available without group prefix
215+
XCTAssertEqual(try self.app.request(TestRequest.get3), TestRequest.get3.response)
216+
}
170217
}
171218

172219
/// Runs the specified callback on a request / response.

0 commit comments

Comments
 (0)