diff --git a/Benchmarks/Benchmarks/Router/Benchmarks.swift b/Benchmarks/Benchmarks/Router/Benchmarks.swift index 16875464c..85f5f88a3 100644 --- a/Benchmarks/Benchmarks/Router/Benchmarks.swift +++ b/Benchmarks/Benchmarks/Router/Benchmarks.swift @@ -24,6 +24,6 @@ let benchmarks = { ], warmupIterations: 10 ) - binaryTrieRouterBenchmarks() + trieRouterBenchmarks() routerBenchmarks() } diff --git a/Benchmarks/Benchmarks/Router/BinaryTrieRouterBenchmarks.swift b/Benchmarks/Benchmarks/Router/TrieRouterBenchmarks.swift similarity index 83% rename from Benchmarks/Benchmarks/Router/BinaryTrieRouterBenchmarks.swift rename to Benchmarks/Benchmarks/Router/TrieRouterBenchmarks.swift index 8b6a2199d..072fd1047 100644 --- a/Benchmarks/Benchmarks/Router/BinaryTrieRouterBenchmarks.swift +++ b/Benchmarks/Benchmarks/Router/TrieRouterBenchmarks.swift @@ -15,9 +15,9 @@ import Benchmark @_spi(Internal) import Hummingbird -func binaryTrieRouterBenchmarks() { - var trie: BinaryTrie! - Benchmark("BinaryTrieRouter", configuration: .init(scalingFactor: .kilo)) { benchmark in +func trieRouterBenchmarks() { + var trie: RouterTrie! + Benchmark("TrieRouter:Routing", configuration: .init(scalingFactor: .kilo)) { benchmark in let testValues = [ "/test/", "/test/one", @@ -39,11 +39,11 @@ func binaryTrieRouterBenchmarks() { trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2") trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile") trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*") - trie = BinaryTrie(base: trieBuilder) + trie = RouterTrie(base: trieBuilder) } - var trie2: BinaryTrie! - Benchmark("BinaryTrieRouterParameters", configuration: .init(scalingFactor: .kilo)) { benchmark in + var trie2: RouterTrie! + Benchmark("TrieRouter:Parameters", configuration: .init(scalingFactor: .kilo)) { benchmark in let testValues = [ "/test/value", "/test/value1/value2", @@ -61,11 +61,11 @@ func binaryTrieRouterBenchmarks() { trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2") trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*") trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile") - trie2 = BinaryTrie(base: trieBuilder) + trie2 = RouterTrie(base: trieBuilder) } - var trie3: BinaryTrie! - Benchmark("BinaryTrie:LongPaths", configuration: .init(scalingFactor: .kilo)) { benchmark in + var trie3: RouterTrie! + Benchmark("TrieRouter:LongPaths", configuration: .init(scalingFactor: .kilo)) { benchmark in let testValues = [ "/api/v1/users/1/profile", "/api/v1/a/very/long/path/with/lots/of/segments", @@ -79,6 +79,6 @@ func binaryTrieRouterBenchmarks() { let trieBuilder = RouterPathTrieBuilder() trieBuilder.addEntry("/api/v1/a/very/long/path/with/lots/of/segments", value: "/api/v1/a/very/long/path/with/lots/of/segments") trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile") - trie3 = BinaryTrie(base: trieBuilder) + trie3 = RouterTrie(base: trieBuilder) } } diff --git a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+resolve.swift b/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+resolve.swift deleted file mode 100644 index 10933cef0..000000000 --- a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+resolve.swift +++ /dev/null @@ -1,163 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Hummingbird server framework project -// -// Copyright (c) 2024 the Hummingbird authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import NIOCore - -extension BinaryTrie { - /// Resolve a path to a `Value` if available - @_spi(Internal) public func resolve(_ path: String) -> (value: Value, parameters: Parameters)? { - var trie = trie - let pathComponents = path.split(separator: "/", omittingEmptySubsequences: true) - var pathComponentsIterator = pathComponents.makeIterator() - var parameters = Parameters() - guard var node: BinaryTrieNode = trie.readBinaryTrieNode() else { return nil } - while let component = pathComponentsIterator.next() { - node = self.matchComponent(component, in: &trie, parameters: ¶meters) - if node.token == .recursiveWildcard { - // we have found a recursive wildcard. Go through all the path components until we match one of them - // or reach the end of the path component array - var range = component.startIndex.. (value: Value, parameters: Parameters)? { - if let index, let value = self.values[Int(index)] { - return (value: value, parameters: parameters) - } - - return nil - } - - /// Match sibling node for path component - private func matchComponent( - _ component: Substring, - in trie: inout ByteBuffer, - parameters: inout Parameters - ) -> BinaryTrieNode { - while let node = trie.readBinaryTrieNode() { - let result = self.matchComponent(component, withToken: node.token, in: &trie, parameters: ¶meters) - switch result { - case .match, .deadEnd: - return node - default: - trie.moveReaderIndex(to: Int(node.nextSiblingNodeIndex)) - } - } - // should never get here - return .init(index: 0, token: .deadEnd, nextSiblingNodeIndex: UInt32(trie.writerIndex)) - } - - private enum MatchResult { - case match, mismatch, ignore, deadEnd - } - - private func matchComponent( - _ component: Substring, - withToken token: BinaryTrieTokenKind, - in trie: inout ByteBuffer, - parameters: inout Parameters - ) -> MatchResult { - switch token { - case .path: - // The current node is a constant - guard - trie.readAndCompareString( - to: component, - length: Integer.self - ) - else { - return .mismatch - } - - return .match - case .capture: - // The current node is a parameter - guard - let parameter = trie.readLengthPrefixedString(as: Integer.self) - else { - return .mismatch - } - - parameters[Substring(parameter)] = component - return .match - case .prefixCapture: - guard - let suffix = trie.readLengthPrefixedString(as: Integer.self), - let parameter = trie.readLengthPrefixedString(as: Integer.self), - component.hasSuffix(suffix) - else { - return .mismatch - } - - parameters[Substring(parameter)] = component.dropLast(suffix.count) - return .match - case .suffixCapture: - guard - let prefix = trie.readLengthPrefixedString(as: Integer.self), - let parameter = trie.readLengthPrefixedString(as: Integer.self), - component.hasPrefix(prefix) - else { - return .mismatch - } - - parameters[Substring(parameter)] = component.dropFirst(prefix.count) - return .match - case .wildcard: - // Always matches, descend - return .match - case .prefixWildcard: - guard - let suffix = trie.readLengthPrefixedString(as: Integer.self), - component.hasSuffix(suffix) - else { - return .mismatch - } - - return .match - case .suffixWildcard: - guard - let prefix = trie.readLengthPrefixedString(as: Integer.self), - component.hasPrefix(prefix) - else { - return .mismatch - } - - return .match - case .recursiveWildcard: - return .match - case .null: - return .ignore - case .deadEnd: - return .deadEnd - } - } -} diff --git a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie.swift b/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie.swift deleted file mode 100644 index 4afce5383..000000000 --- a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Hummingbird server framework project -// -// Copyright (c) 2024 the Hummingbird authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -enum BinaryTrieTokenKind: UInt8 { - case null = 0 - case path, capture, prefixCapture, suffixCapture, wildcard, prefixWildcard, suffixWildcard, recursiveWildcard - case deadEnd -} - -struct BinaryTrieNode { - let index: UInt16 - let token: BinaryTrieTokenKind - let nextSiblingNodeIndex: UInt32 - - /// How many bytes a serialized BinaryTrieNode uses - static let serializedSize = 7 -} - -@_spi(Internal) public final class BinaryTrie: Sendable { - typealias Integer = UInt8 - let trie: ByteBuffer - let values: [Value?] - - @_spi(Internal) public init(base: RouterPathTrieBuilder) { - var trie = ByteBufferAllocator().buffer(capacity: 1024) - var values: [Value?] = [] - - Self.serialize( - base.root, - trie: &trie, - values: &values - ) - - self.trie = trie - self.values = values - } -} diff --git a/Sources/Hummingbird/Router/BinaryTrie/ByteBuffer+BinaryTrie.swift b/Sources/Hummingbird/Router/BinaryTrie/ByteBuffer+BinaryTrie.swift deleted file mode 100644 index aa29db18f..000000000 --- a/Sources/Hummingbird/Router/BinaryTrie/ByteBuffer+BinaryTrie.swift +++ /dev/null @@ -1,135 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Hummingbird server framework project -// -// Copyright (c) 2024 the Hummingbird authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(Darwin) -import Darwin.C -#elseif canImport(Musl) -import Musl -#elseif os(Linux) || os(FreeBSD) || os(Android) -import Glibc -#else -#error("unsupported os") -#endif - -internal extension ByteBuffer { - /// Write length prefixed string to ByteBuffer - mutating func writeLengthPrefixedString(_ string: Substring, as integer: F.Type) { - do { - try self.writeLengthPrefixed(as: F.self) { buffer in - buffer.writeSubstring(string) - } - } catch { - preconditionFailure("Failed to write \"\(string)\" into BinaryTrie") - } - } - - /// Write BinaryTrieNode into ByteBuffer at position - @discardableResult mutating func setBinaryTrieNode(_ node: BinaryTrieNode, at index: Int) -> Int { - var offset = self.setInteger(node.index, at: index) - offset += self.setInteger(node.token.rawValue, at: index + offset) - offset += self.setInteger(node.nextSiblingNodeIndex, at: index + offset) - return offset - } - - /// Write BinaryTrieNode into ByteBuffer at position - mutating func writeBinaryTrieNode(_ node: BinaryTrieNode) { - let offset = self.setBinaryTrieNode(node, at: self.writerIndex) - self.moveWriterIndex(forwardBy: offset) - } - - /// Reserve space for a BinaryTrieNode - mutating func reserveBinaryTrieNode() { - self.moveWriterIndex(forwardBy: BinaryTrieNode.serializedSize) - } - - /// Read BinaryTrieNode from ByteBuffer - mutating func readBinaryTrieNode() -> BinaryTrieNode? { - guard let index = self.readInteger(as: UInt16.self), - let token = self.readToken(), - let nextSiblingNodeIndex: UInt32 = self.readInteger() - else { - return nil - } - return BinaryTrieNode(index: index, token: token, nextSiblingNodeIndex: nextSiblingNodeIndex) - } - - /// Read string from ByteBuffer and compare against another string - mutating func readAndCompareString( - to string: Substring, - length: Length.Type - ) -> Bool { - guard - let _length: Length = readInteger() - else { - return false - } - - let length = Int(_length) - - func compare(utf8: UnsafeBufferPointer) -> Bool { - if utf8.count != length { - return false - } - - if length == 0 { - // Needed, because `memcmp` wants a non-null pointer on Linux - // and a zero-length buffer has no baseAddress - return true - } - - return withUnsafeReadableBytes { buffer in - if memcmp(utf8.baseAddress!, buffer.baseAddress!, length) == 0 { - moveReaderIndex(forwardBy: length) - return true - } else { - return false - } - } - } - - guard let result = string.withContiguousStorageIfAvailable({ characters in - characters.withMemoryRebound(to: UInt8.self) { utf8 in - compare(utf8: utf8) - } - }) else { - var string = string - return string.withUTF8 { utf8 in - compare(utf8: utf8) - } - } - - return result - } - - /// Read length prefixed string from ByteBuffer - mutating func readLengthPrefixedString(as integer: F.Type) -> String? { - guard let buffer = readLengthPrefixedSlice(as: F.self) else { - return nil - } - - return String(buffer: buffer) - } - - /// Read BinaryTrieTokenKind from ByteBuffer - mutating func readToken() -> BinaryTrieTokenKind? { - guard - let _token: BinaryTrieTokenKind.RawValue = readInteger(), - let token = BinaryTrieTokenKind(rawValue: _token) - else { - return nil - } - - return token - } -} diff --git a/Sources/Hummingbird/Router/RouterResponder.swift b/Sources/Hummingbird/Router/RouterResponder.swift index 745616d02..83945ce33 100644 --- a/Sources/Hummingbird/Router/RouterResponder.swift +++ b/Sources/Hummingbird/Router/RouterResponder.swift @@ -15,7 +15,7 @@ import NIOCore public struct RouterResponder: HTTPResponder { - let trie: BinaryTrie> + let trie: RouterTrie> let notFoundResponder: any HTTPResponder let options: RouterOptions @@ -25,7 +25,7 @@ public struct RouterResponder: HTTPResponder { options: RouterOptions, notFoundResponder: any HTTPResponder ) { - self.trie = BinaryTrie(base: trie) + self.trie = RouterTrie(base: trie) self.options = options self.notFoundResponder = notFoundResponder } diff --git a/Sources/Hummingbird/Router/Trie/RouterTrie.swift b/Sources/Hummingbird/Router/Trie/RouterTrie.swift new file mode 100644 index 000000000..be93c497f --- /dev/null +++ b/Sources/Hummingbird/Router/Trie/RouterTrie.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Hummingbird server framework project +// +// Copyright (c) 2024 the Hummingbird authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@usableFromInline +enum TrieToken: Equatable, Sendable { + case null + case path(constantIndex: UInt32) + case capture(parameterIndex: UInt32) + case prefixCapture(parameterIndex: UInt32, suffixIndex: UInt32) + case suffixCapture(prefixIndex: UInt32, parameterIndex: UInt32) + case prefixWildcard(suffixIndex: UInt32) + case suffixWildcard(prefixIndex: UInt32) + case wildcard, recursiveWildcard + case deadEnd +} + +@usableFromInline +struct TrieNode: Sendable { + @usableFromInline + let valueIndex: Int + + @usableFromInline + let token: TrieToken + + @usableFromInline + var nextSiblingNodeIndex: Int + + @usableFromInline + init(valueIndex: Int, token: TrieToken, nextSiblingNodeIndex: Int) { + self.valueIndex = valueIndex + self.token = token + self.nextSiblingNodeIndex = nextSiblingNodeIndex + } +} + +@usableFromInline +struct Trie: Sendable { + @usableFromInline + var nodes = [TrieNode]() + + @usableFromInline + var stringValues = [Substring]() + + @usableFromInline + init() {} +} + +@_spi(Internal) public final class RouterTrie: Sendable { + @usableFromInline + let trie: Trie + + @usableFromInline + let values: [Value?] + + @inlinable + @_spi(Internal) public init(base: RouterPathTrieBuilder) { + var trie = Trie() + var values: [Value?] = [] + + Self.serialize( + base.root, + trie: &trie, + values: &values + ) + + trie.nodes.append( + TrieNode( + valueIndex: 0, + token: .deadEnd, + nextSiblingNodeIndex: .max + ) + ) + + self.trie = trie + self.values = values + } +} diff --git a/Sources/Hummingbird/Router/Trie/Trie+resolve.swift b/Sources/Hummingbird/Router/Trie/Trie+resolve.swift new file mode 100644 index 000000000..7439a75e4 --- /dev/null +++ b/Sources/Hummingbird/Router/Trie/Trie+resolve.swift @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Hummingbird server framework project +// +// Copyright (c) 2024 the Hummingbird authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import NIOCore + +extension RouterTrie { + /// Resolve a path to a `Value` if available + @inlinable + @_spi(Internal) public func resolve(_ path: String) -> (value: Value, parameters: Parameters)? { + let pathComponents = path.split(separator: "/", omittingEmptySubsequences: true) + var pathComponentsIterator = pathComponents.makeIterator() + var parameters = Parameters() + + var node = trie.nodes[0] + var nodeIndex = 1 + + while let component = pathComponentsIterator.next() { + node = self.matchComponent(component, atNodeIndex: &nodeIndex, parameters: ¶meters) + if node.token == .recursiveWildcard { + // we have found a recursive wildcard. Go through all the path components until we match one of them + // or reach the end of the path component array + var range = component.startIndex.. TrieNode { + while nodeIndex < trie.nodes.count { + let node = trie.nodes[nodeIndex] + let result = self.matchComponent( + component, + node: node, + parameters: ¶meters + ) + switch result { + case .match, .deadEnd: + nodeIndex += 1 + return node + default: + nodeIndex = Int(node.nextSiblingNodeIndex) + } + } + + // should never get here + return TrieNode(valueIndex: 0, token: .deadEnd, nextSiblingNodeIndex: .max) + } + + @usableFromInline + enum MatchResult { + case match, mismatch, ignore, deadEnd + } + + @inlinable + func matchComponent( + _ component: Substring, + node: TrieNode, + parameters: inout Parameters + ) -> MatchResult { + switch node.token { + case .path(let constant): + // The current node is a constant + if trie.stringValues[Int(constant)] == component { + return .match + } + + return .mismatch + case .capture(let parameter): + parameters[trie.stringValues[Int(parameter)]] = component + return .match + case .prefixCapture(let parameter, let suffix): + let suffix = trie.stringValues[Int(suffix)] + + if component.hasSuffix(suffix) { + parameters[trie.stringValues[Int(parameter)]] = component.dropLast(suffix.count) + return .match + } + + return .mismatch + case .suffixCapture(let prefix, let parameter): + let prefix = trie.stringValues[Int(prefix)] + if component.hasPrefix(prefix) { + parameters[trie.stringValues[Int(parameter)]] = component.dropFirst(prefix.count) + return .match + } + + return .mismatch + case .wildcard: + // Always matches, descend + return .match + case .prefixWildcard(let suffix): + if component.hasSuffix(trie.stringValues[Int(suffix)]) { + return .match + } + + return .mismatch + case .suffixWildcard(let prefix): + if component.hasPrefix(trie.stringValues[Int(prefix)]) { + return .match + } + + return .mismatch + case .recursiveWildcard: + return .match + case .null: + return .ignore + case .deadEnd: + return .deadEnd + } + } +} diff --git a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+serialize.swift b/Sources/Hummingbird/Router/Trie/Trie+serialize.swift similarity index 53% rename from Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+serialize.swift rename to Sources/Hummingbird/Router/Trie/Trie+serialize.swift index 7884da892..4fe1dbae7 100644 --- a/Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+serialize.swift +++ b/Sources/Hummingbird/Router/Trie/Trie+serialize.swift @@ -14,72 +14,78 @@ import NIOCore -extension BinaryTrie { +extension RouterTrie { + @inlinable static func serialize( _ node: RouterPathTrieBuilder.Node, - trie: inout ByteBuffer, + trie: inout Trie, values: inout [Value?] ) { - let binaryTrieNodeIndex = trie.writerIndex - trie.reserveBinaryTrieNode() // Index where `value` is located - let index = UInt16(values.count) + let valueIndex = values.count values.append(node.value) - let token: BinaryTrieTokenKind + let token: TrieToken + + func setStringValue(_ constant: Substring) -> UInt32 { + if let index = trie.stringValues.firstIndex(of: constant) { + return UInt32(index) + } else { + let index = trie.stringValues.count + trie.stringValues.append(constant) + return UInt32(index) + } + } + switch node.key { case .path(let path): - token = .path - // Serialize the path constant - trie.writeLengthPrefixedString(path, as: Integer.self) - case .capture(let parameter): - token = .capture - // Serialize the parameter - trie.writeLengthPrefixedString(parameter, as: Integer.self) - case .prefixCapture(suffix: let suffix, parameter: let parameter): - token = .prefixCapture - // Serialize the suffix and parameter - trie.writeLengthPrefixedString(suffix, as: Integer.self) - trie.writeLengthPrefixedString(parameter, as: Integer.self) - case .suffixCapture(prefix: let prefix, parameter: let parameter): - token = .suffixCapture - // Serialize the prefix and parameter - trie.writeLengthPrefixedString(prefix, as: Integer.self) - trie.writeLengthPrefixedString(parameter, as: Integer.self) + token = .path(constantIndex: setStringValue(path)) + case .capture(let parameterName): + token = .capture(parameterIndex: setStringValue(parameterName)) + case .prefixCapture(suffix: let suffix, parameter: let parameterName): + token = .prefixCapture( + parameterIndex: setStringValue(parameterName), + suffixIndex: setStringValue(suffix) + ) + case .suffixCapture(prefix: let prefix, parameter: let parameterName): + token = .suffixCapture( + prefixIndex: setStringValue(prefix), + parameterIndex: setStringValue(parameterName) + ) case .wildcard: token = .wildcard case .prefixWildcard(let suffix): - token = .prefixWildcard - // Serialize the suffix - trie.writeLengthPrefixedString(suffix, as: Integer.self) + token = .prefixWildcard(suffixIndex: setStringValue(suffix)) case .suffixWildcard(let prefix): - token = .suffixWildcard - // Serialize the prefix - trie.writeLengthPrefixedString(prefix, as: Integer.self) + token = .suffixWildcard(prefixIndex: setStringValue(prefix)) case .recursiveWildcard: token = .recursiveWildcard case .null: token = .null } + let nodeIndex = trie.nodes.count + trie.nodes.append( + TrieNode( + valueIndex: valueIndex, + token: token, + nextSiblingNodeIndex: .max + ) + ) + self.serializeChildren( of: node, trie: &trie, values: &values ) - let deadEndIndex = trie.writerIndex - // The last node in a trie is always a deadEnd token. We reserve space for it so we - // get the correct writer index for the next sibling - trie.reserveBinaryTrieNode() - trie.setBinaryTrieNode(.init(index: 0, token: .deadEnd, nextSiblingNodeIndex: UInt32(trie.writerIndex)), at: deadEndIndex) - // Write trie node - trie.setBinaryTrieNode(.init(index: index, token: token, nextSiblingNodeIndex: UInt32(trie.writerIndex)), at: binaryTrieNodeIndex) + trie.nodes[nodeIndex].nextSiblingNodeIndex = trie.nodes.count } + @inlinable static func serializeChildren( of node: RouterPathTrieBuilder.Node, - trie: inout ByteBuffer, + trie: inout Trie, values: inout [Value?] ) { // Serialize the child nodes in order of priority @@ -89,13 +95,15 @@ extension BinaryTrie { } } - private static func highestPriorityFirst(lhs: RouterPathTrieBuilder.Node, rhs: RouterPathTrieBuilder.Node) -> Bool { + @inlinable + internal static func highestPriorityFirst(lhs: RouterPathTrieBuilder.Node, rhs: RouterPathTrieBuilder.Node) -> Bool { lhs.key.priority > rhs.key.priority } } extension RouterPath.Element { - fileprivate var priority: Int { + @usableFromInline + var priority: Int { switch self { case .prefixCapture, .suffixCapture: // Most specific diff --git a/Sources/Hummingbird/Router/TrieRouter.swift b/Sources/Hummingbird/Router/TrieRouter.swift index d805bef12..71ef6b378 100644 --- a/Sources/Hummingbird/Router/TrieRouter.swift +++ b/Sources/Hummingbird/Router/TrieRouter.swift @@ -16,6 +16,7 @@ import HummingbirdCore /// URI Path Trie Builder @_spi(Internal) public struct RouterPathTrieBuilder { + @usableFromInline var root: Node public init() { @@ -40,7 +41,7 @@ import HummingbirdCore } } - @_spi(Internal) public func build() -> BinaryTrie { + @_spi(Internal) public func build() -> RouterTrie { .init(base: self) } @@ -50,8 +51,13 @@ import HummingbirdCore /// Trie Node. Each node represents one component of a URI path @_spi(Internal) public final class Node { + @usableFromInline let key: RouterPath.Element + + @usableFromInline var children: [Node] + + @usableFromInline var value: Value? init(key: RouterPath.Element, output: Value?) {