diff --git a/Sources/Hummingbird/Router/Router.swift b/Sources/Hummingbird/Router/Router.swift index 6a25334f..73b1e333 100644 --- a/Sources/Hummingbird/Router/Router.swift +++ b/Sources/Hummingbird/Router/Router.swift @@ -128,3 +128,25 @@ public struct RouterOptions: OptionSet, Sendable { /// For every GET request that does not have a HEAD request, auto generate the HEAD request public static var autoGenerateHeadEndpoints: Self { .init(rawValue: 1 << 1) } } + +extension Router { + /// Route description + public struct RouteDescription: CustomStringConvertible { + /// Route path + public let path: RouterPath + /// Route method + public let method: HTTPRequest.Method + + public var description: String { "\(method) \(path)" } + } + + /// List of routes added to router + public var routes: [RouteDescription] { + let trieValues = self.trie.root.values() + return trieValues.flatMap { endpoint in + endpoint.value.methods.keys + .sorted { $0.rawValue < $1.rawValue } + .map { RouteDescription(path: endpoint.path, method: $0) } + } + } +} diff --git a/Sources/Hummingbird/Router/TrieRouter.swift b/Sources/Hummingbird/Router/TrieRouter.swift index 02ad09c6..ea56bdef 100644 --- a/Sources/Hummingbird/Router/TrieRouter.swift +++ b/Sources/Hummingbird/Router/TrieRouter.swift @@ -91,3 +91,20 @@ import HummingbirdCore } } } + +extension RouterPathTrieBuilder.Node { + /// Return list of paths and associated values in trie node + /// - Parameter prefix: Prefix for path + /// - Returns: Array of path values pairs + func values(prefix: RouterPath = "/") -> [(value: Value, path: RouterPath)] { + var values: [(Value, RouterPath)] = [] + if let value = self.value { + values.append((value, prefix)) + } + for node in self.children { + let childValues = node.values(prefix: prefix.appendingPath(.init(components: [node.key]))) + values.append(contentsOf: childValues) + } + return values + } +} diff --git a/Tests/HummingbirdTests/RouterTests.swift b/Tests/HummingbirdTests/RouterTests.swift index bfa38dba..0dfc2eb2 100644 --- a/Tests/HummingbirdTests/RouterTests.swift +++ b/Tests/HummingbirdTests/RouterTests.swift @@ -728,6 +728,33 @@ final class RouterTests: XCTestCase { } } } + + func testEndpointDescriptions() { + let router = Router() + router.get("test") { _, _ in "" } + router.get("test/this") { _, _ in "" } + router.put("test") { _, _ in "" } + router.post("{test}/{what}") { _, _ in "" } + router.get("wildcard/*") { _, _ in "" } + router.get("recursive_wildcard/**") { _, _ in "" } + router.patch("/test/longer/path/name") { _, _ in "" } + let routes = router.routes + XCTAssertEqual(routes.count, 7) + XCTAssertEqual(routes[0].path.description, "/test") + XCTAssertEqual(routes[0].method, .get) + XCTAssertEqual(routes[1].path.description, "/test") + XCTAssertEqual(routes[1].method, .put) + XCTAssertEqual(routes[2].path.description, "/test/this") + XCTAssertEqual(routes[2].method, .get) + XCTAssertEqual(routes[3].path.description, "/test/longer/path/name") + XCTAssertEqual(routes[3].method, .patch) + XCTAssertEqual(routes[4].path.description, "/{test}/{what}") + XCTAssertEqual(routes[4].method, .post) + XCTAssertEqual(routes[5].path.description, "/wildcard/*") + XCTAssertEqual(routes[5].method, .get) + XCTAssertEqual(routes[6].path.description, "/recursive_wildcard/**") + XCTAssertEqual(routes[6].method, .get) + } } struct TestRouterContext2: RequestContext {