forked from swift-server/swift-openapi-lambda
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOpenAPILambdaRouterTrie.swift
170 lines (150 loc) · 7.18 KB
/
OpenAPILambdaRouterTrie.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenAPI Lambda open source project
//
// Copyright (c) 2023 Amazon.com, Inc. or its affiliates
// and the Swift OpenAPI Lambda project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift OpenAPI Lambda project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import HTTPTypes
/// A Trie router implementation
public struct TrieRouter: OpenAPILambdaRouter {
private let uriPath: URIPathCollection = URIPath()
/// add a route for a given HTTP method and path and associate a handler
public func add(method: HTTPRequest.Method, path: String, handler: @escaping OpenAPIHandler) throws {
try uriPath.add(method: method, path: path, handler: handler)
}
/// Retrieve the handler and path parameter for a given HTTP method and path
public func route(method: HTTPRequest.Method, path: String) async throws -> (
OpenAPIHandler, OpenAPILambdaRequestParameters
) { try uriPath.find(method: method, path: path) }
}
enum URIPathCollectionError: Error {
case canNotAddChildToHandlerNode
case canNotHaveMultipleParamChilds
}
protocol URIPathCollection {
func root() -> Node
func add(method: HTTPRequest.Method, path: String, handler: @escaping OpenAPIHandler) throws
func find(method: HTTPRequest.Method, path: String) throws -> (OpenAPIHandler, OpenAPILambdaRequestParameters)
}
/// A Trie graph representation of an URI path + its handler
/// see https://en.wikipedia.org/wiki/Trie
///
/// Example :
/// an URI of GET /stocks/{symbol} will generate a tree `root -> GET -> stocks -> symbol`
struct URIPath: URIPathCollection {
private var _root = Node()
func root() -> Node { self._root }
/// Add a method + path to the graph
/// This methods decompose the path into components and create nodes as required.
/// - Parameters:
/// - method: the HTTP method to use as first node
/// - path: the full path as received from OpenAPI registration (including parameters encoded as {param name}
/// - handler: the OpenAPI handler to invoke for such path
func add(method: HTTPRequest.Method, path: String, handler: @escaping OpenAPIHandler) throws {
// add or retrieve the HTTP method node
var node = try root().add(httpMethod: method)
// add each path element as child nodes
let pathComponents = path.split(separator: "/")
for component in pathComponents {
if component.hasPrefix("{") && component.hasSuffix("}") {
var comp = component
comp.removeFirst()
comp.removeLast()
node = try node.add(parameter: String(comp))
}
else {
node = try node.add(pathElement: String(component))
}
}
// finally add the handler
_ = try node.add(handler: handler)
}
/// Navigate the tree to find the handler
/// Collect parameter name and value on the way
/// - Parameters:
/// - method : the HTTP method for this path
/// - path : the path as received by the API Gateway (with parameter values and not names)
/// - Returns:
/// - the OpenAPIHandler for this path
/// - the OpenAI ServerRequestMetadata (a [String:String] with parameter names and their values
/// - Throws:
/// - OpenAPILambdaRouterError.noRouteForPath when there is no handler in the graph for the given combination of HTTP method and path
/// - OpenAPILambdaRouterError.noRouteForMethod when there is no handler for that HTTP method
/// - OpenAPILambdaRouterError.noHandlerForPath when there is no handler as leaf node of the tree. This is a programming error and should not happen
func find(method: HTTPRequest.Method, path: String) throws -> (OpenAPIHandler, OpenAPILambdaRequestParameters) {
var parameters: OpenAPILambdaRequestParameters = [:]
let root: Node = root()
// first node is the HTTP Method
guard let nodeHTTP = root.children[method.rawValue] else {
throw OpenAPILambdaRouterError.noRouteForMethod(method)
}
// search for each path component. If a component is not found, it might be a parameter
// stop at the start of the query string
let pathComponents = path.prefix(while: { $0 != "?" }).split(separator: "/")
var currentNode = nodeHTTP
for component in pathComponents {
if let child = currentNode.child(with: component) {
// found a node with path element, continue to explore
currentNode = child
}
else {
// no path element for this component, maybe this component is a parameter value
// let's see if we have a child param node
if let child = currentNode.parameterChild() {
let paramName = child.value.asString!
//TODO: do not collect param when another child is a path with matching name ?
// /stock/{symbol}
// /stock/date
// verify if this is authorized by OpenAPI spec
// collect the param value
parameters[paramName] = component
// continue progress
currentNode = child
}
else {
throw OpenAPILambdaRouterError.noRouteForPath(path)
}
}
}
//at this stage, current node must have a handler child
guard let handlerNode = currentNode.handlerChild() else {
throw OpenAPILambdaRouterError.noHandlerForPath(path)
}
// did we found an handler ?
guard let handler = handlerNode.value.handler else { throw OpenAPILambdaRouterError.noHandlerForPath(path) }
return (handler, parameters)
}
// /// Perform a deep search to find the first node with the matching value
// func findFirst(from startingNode: Node, name: String) -> Node? {
//
// // check if the node we're looking for is this one
// // it can be a parameter, a path element, or an http method
// switch startingNode.value {
// case .pathParameter(let param): if param == name { return startingNode }
// case .pathElement(let element): if element == name { return startingNode }
// case .httpMethod(let method): if method.rawValue == name { return startingNode }
// default: break
// }
//
// // otherwise, check child nodes
// for key in startingNode.children.keys {
//
// // if the search finds a non nil value, we found it
// if let result = findFirst(from: startingNode.children[key]!, name: name) {
// return result
// }
// // otherwise continue with the next key
// }
//
// // otherwise we did not find it
// return nil
// }
}