mirror of
https://github.com/swift-server/swift-openapi-lambda.git
synced 2026-05-03 07:22:26 +00:00
4f2e953097
Change the legal files and the license header in source files according to AWS standards
206 lines
8.8 KiB
Swift
206 lines
8.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift OpenAPI Lambda open source project
|
|
//
|
|
// Copyright Swift OpenAPI Lambda project authors
|
|
// Copyright (c) 2023 Amazon.com, Inc. or its affiliates.
|
|
// 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 node in the graph
|
|
/// A node has a value and a list of children nodes
|
|
/// A node value can be
|
|
/// - `root` (only one, at the top of the tree),
|
|
/// - an HTTP method (represented as a HTTPRequest.Method)
|
|
/// - a path element (represented as a `String`),
|
|
/// - a path parameter (represented as a `String`)
|
|
/// - a handler, only for leaf nodes (represented as `OpenAPIHandler`)
|
|
final class Node {
|
|
let value: NodeValue
|
|
var children: [String: Node] = [:]
|
|
|
|
/// Default init, create a root node
|
|
init() { value = .root }
|
|
|
|
/// Creates a node for an HTTP method
|
|
init(httpMethod: HTTPRequest.Method) { value = .httpMethod(httpMethod) }
|
|
|
|
/// Creates a node for a path element
|
|
init(pathElement: String) { value = .pathElement(pathElement) }
|
|
|
|
/// Creates a node for a path parameter
|
|
init(parameterName: String) { value = .pathParameter(parameterName) }
|
|
|
|
/// Creates a node for an OpenAPI handler
|
|
init(handler: @escaping OpenAPIHandler) { value = .handler(handler) }
|
|
|
|
/// Creates a node for an existing node value
|
|
private init(value: NodeValue) { self.value = value }
|
|
|
|
/// Convenience method to add a child node of type HttpRequest.Method to this node
|
|
/// - Parameter:
|
|
/// - httpMethod: an HTTP Method
|
|
/// - Returns:
|
|
/// - the node that has been created
|
|
/// - Throws:
|
|
/// - URIPathCollectionError.canNotAddChildToHandlerNode when trying to add a child to leaf node of type `.handler`
|
|
/// - URIPathCollectionError.canNotHaveMultipleParamChilds when trying to add multiple child node of type `.parameter`
|
|
func add(httpMethod: HTTPRequest.Method) throws -> Node { try add(child: NodeValue.httpMethod(httpMethod)) }
|
|
|
|
/// Convenience method to add a child node of type path element to this node
|
|
/// - Parameter:
|
|
/// - pathElement: the name of a path element. A path element is a `String` usually found between `/` characters in the URI
|
|
/// - Returns:
|
|
/// - the node that has been created
|
|
/// - Throws:
|
|
/// - URIPathCollectionError.canNotAddChildToHandlerNode when trying to add a child to leaf node of type `.handler`
|
|
/// - URIPathCollectionError.canNotHaveMultipleParamChilds when trying to add multiple child node of type `.parameter`
|
|
func add(pathElement: String) throws -> Node { try add(child: NodeValue.pathElement(pathElement)) }
|
|
|
|
/// Convenience method to add a child node of type path parameter to this node
|
|
/// - Parameter:
|
|
/// - pathParameter: the name of a path parameter. A path parameter is a `{name}` usually found between `/` characters in the URI
|
|
/// - Returns:
|
|
/// - the node that has been created
|
|
/// - Throws:
|
|
/// - URIPathCollectionError.canNotAddChildToHandlerNode when trying to add a child to leaf node of type `.handler`
|
|
/// - URIPathCollectionError.canNotHaveMultipleParamChilds when trying to add multiple child node of type `.parameter`
|
|
func add(parameter: String) throws -> Node { try add(child: NodeValue.pathParameter(parameter)) }
|
|
|
|
/// Convenience method to add a child node of type handler to this node
|
|
/// - Parameter:
|
|
/// - handler: a function handler. A handler MUST be a leaf node (it has no children) and is of type `OpenAPIHandler`
|
|
/// - Returns:
|
|
/// - the node that has been created
|
|
/// - Throws:
|
|
/// - URIPathCollectionError.canNotAddChildToHandlerNode when trying to add a child to leaf node of type `.handler`
|
|
/// - URIPathCollectionError.canNotHaveMultipleParamChilds when trying to add multiple child node of type `.parameter`
|
|
func add(handler: @escaping OpenAPIHandler) throws -> Node { try add(child: NodeValue.handler(handler)) }
|
|
|
|
/// Convenience method to add a child node to this node
|
|
/// - Parameter:
|
|
/// - child: A NodeValue to add as child node to this node
|
|
/// - Returns:
|
|
/// - the node that has been created
|
|
/// - Throws:
|
|
/// - URIPathCollectionError.canNotAddChildToHandlerNode when trying to add a child to leaf node of type `.handler`
|
|
/// - URIPathCollectionError.canNotHaveMultipleParamChilds when trying to add multiple child node of type `.parameter`
|
|
private func add(child: NodeValue) throws -> Node {
|
|
let key = key(for: child)
|
|
// if the child node already exist, just ignore
|
|
guard children[key] == nil else { return children[key]! }
|
|
// Reject the add() operation when this node is an handler. Handler are leaf nodes.
|
|
if self.value.isHandler { throw URIPathCollectionError.canNotAddChildToHandlerNode }
|
|
|
|
// Reject the add() operation when there is already a param Node as child
|
|
// OpenAI specification -> https://swagger.io/specification/
|
|
// The following paths are considered identical and invalid:
|
|
// /pets/{petId}
|
|
// /pets/{name}
|
|
|
|
// when the child we try to add is a param
|
|
if child.isPathParameter {
|
|
// and there is already a param child
|
|
if hasParamChild() { throw URIPathCollectionError.canNotHaveMultipleParamChilds }
|
|
}
|
|
let newNode = Node(value: child)
|
|
children[key] = newNode
|
|
return newNode
|
|
}
|
|
|
|
/// Returns the child with the given value, nil if no child with such name exist
|
|
/// - Parameter:
|
|
/// - name: the value of the child node
|
|
func child(with name: String) -> Node? { self.children[name] }
|
|
|
|
/// Returns the child with the given value, nil if no child with such name exist
|
|
/// - Parameter:
|
|
/// - name: the value of the child node
|
|
func child(with name: Substring) -> Node? { self.children[String(name)] }
|
|
|
|
/// Returns the parameter child for this node if there is one, nil otherwise
|
|
func parameterChild() -> Node? {
|
|
let paramNodes = self.children.filter { key, value in self.children[key]!.value.isPathParameter }
|
|
precondition(paramNodes.count <= 1)
|
|
return paramNodes.count == 1 ? paramNodes.first!.value : nil
|
|
}
|
|
|
|
/// Returns true when one of the child is a param node, false otherwise
|
|
private func hasParamChild() -> Bool {
|
|
if self.children.isEmpty { return false }
|
|
return self.children.keys.allSatisfy { key in self.children[key]!.value.isPathParameter }
|
|
}
|
|
|
|
/// Returns the handler child for this node if there is one, nil otherwise
|
|
func handlerChild() -> Node? {
|
|
let handlerNodes = self.children.filter { key, value in self.children[key]!.value.isHandler }
|
|
precondition(handlerNodes.count <= 1)
|
|
return handlerNodes.count == 1 ? handlerNodes.first!.value : nil
|
|
}
|
|
|
|
/// Get a `Hashable` key for a node value
|
|
/// There can be only one root node and one child handler (as a leaf of the tree), so it is OK to hardcode these keys
|
|
private func key(for value: NodeValue) -> String {
|
|
switch value {
|
|
case .root: "root"
|
|
case .httpMethod(let method): method.rawValue
|
|
case .pathElement(let element): element
|
|
case .pathParameter(let name): name
|
|
case .handler: "handler"
|
|
}
|
|
}
|
|
|
|
/// A value for a node.
|
|
/// Possible values are:
|
|
/// - `root` (only one, at the top of the tree),
|
|
/// - an HTTP method (represented as a HTTPRequest.Method)
|
|
/// - a path element (represented as a `String`),
|
|
/// - a path parameter (represented as a `String`)
|
|
/// - a handler, only for leaf nodes (represented as `OpenAPIHandler`)
|
|
enum NodeValue {
|
|
case root
|
|
case httpMethod(HTTPRequest.Method)
|
|
case pathElement(String)
|
|
case pathParameter(String)
|
|
case handler(OpenAPIHandler)
|
|
|
|
var isPathParameter: Bool {
|
|
switch self {
|
|
case .pathParameter(_): return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
var isHandler: Bool {
|
|
switch self {
|
|
case .handler(_): return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
var handler: OpenAPIHandler? {
|
|
switch self {
|
|
case .handler(let handler): return handler
|
|
default: return nil
|
|
}
|
|
}
|
|
|
|
var asString: String? {
|
|
switch self {
|
|
case .root: return nil
|
|
case .httpMethod(let method): return method.rawValue
|
|
case .pathElement(let element): return element
|
|
case .pathParameter(let param): return param
|
|
case .handler(_): return nil
|
|
}
|
|
}
|
|
}
|
|
}
|