Skip to content
Merged

V5 #141

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Benchmarks/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "benchmarks",
platforms: [
.macOS(.v13)
.macOS(.v15)
],
dependencies: [
.package(path: "../"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,119 +3,146 @@ import RoutingKit

let benchmarks = { @Sendable () -> Void in
Benchmark.defaultConfiguration = .init(
metrics: [.mallocCountTotal, .peakMemoryResident, .throughput]
metrics: [.mallocCountTotal, .peakMemoryResident, .throughput],
thresholds: [
.mallocCountTotal: .init(
relative: [.p90: 1],
absolute: [.p90: 2]
),
.peakMemoryResident: .init(
relative: [.p90: 5],
absolute: [.p90: 1_000_000]
),
.throughput: .init(
relative: [.p90: 10]
),
]
)

Benchmark("Case-sensitive") { benchmark in
let router = TrieRouter(String.self)
var builder = TrieRouterBuilder(String.self)
for letter in ["a", "b", "c", "d", "e", "f", "g"] {
router.register(
builder.register(
letter,
at: [
.constant(letter),
.parameter("\(letter)_id"),
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["a", "42"], parameters: &params)
}
}

Benchmark("Case-insensitive") { benchmark in
let router = TrieRouter.init(String.self, options: [.caseInsensitive])
var builder = TrieRouterBuilder(String.self, options: [.caseInsensitive])
for letter in ["a", "b", "c", "d", "e", "f", "g"] {
router.register(
builder.register(
letter,
at: [
.constant(letter),
.parameter("\(letter)_id"),
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["a", "42"], parameters: &params)
}
}

Benchmark("Case-insensitive Match First") { benchmark in
let router = TrieRouter.init(String.self, options: [.caseInsensitive])
Benchmark("Case-insensitive_Match_First") { benchmark in
var builder = TrieRouterBuilder(String.self, options: [.caseInsensitive])
for letter in ["aaaaaaaa", "aaaaaaab", "aaaaaaac", "aaaaaaad", "aaaaaaae", "aaaaaaaf", "aaaaaaag"] {
router.register(
builder.register(
letter,
at: [
.constant(letter),
.parameter("\(letter)_id"),
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["aaaaaaaa", "42"], parameters: &params)
}
}

Benchmark("Case-insensitive Match Last") { benchmark in
let router = TrieRouter.init(String.self, options: [.caseInsensitive])
Benchmark("Case-insensitive_Match_Last") { benchmark in
var builder = TrieRouterBuilder(String.self, options: [.caseInsensitive])
for letter in ["aaaaaaaa", "aaaaaaab", "aaaaaaac", "aaaaaaad", "aaaaaaae", "aaaaaaaf", "aaaaaaag"] {
router.register(
builder.register(
letter,
at: [
.constant(letter),
.parameter("\(letter)_id"),
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["aaaaaaag", "42"], parameters: &params)
}
}

Benchmark("Case-sensitive Minimal") { benchmark in
let router = TrieRouter.init(String.self)
Benchmark("Case-sensitive_Minimal") { benchmark in
var builder = TrieRouterBuilder(String.self)
for letter in ["a"] {
router.register(
builder.register(
letter,
at: [
.constant(letter)
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["a"], parameters: &params)
}
}

Benchmark("Case-insensitive Minimal") { benchmark in
let router = TrieRouter.init(String.self, options: [.caseInsensitive])
Benchmark("Case-insensitive_Minimal") { benchmark in
var builder = TrieRouterBuilder(String.self, options: [.caseInsensitive])
for letter in ["a"] {
router.register(
builder.register(
letter,
at: [
.constant(letter)
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["a"], parameters: &params)
}
}

Benchmark("Minimal Early Fail") { benchmark in
let router = TrieRouter.init(String.self)
Benchmark("Minimal_Early_Fail") { benchmark in
var builder = TrieRouterBuilder(String.self)
for letter in ["aaaaaaaaaaaaaa"] {
router.register(
builder.register(
letter,
at: [
.constant(letter)
])
}

let router = builder.build()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
var params = Parameters()
_ = router.route(path: ["baaaaaaaaaaaaa"], parameters: &params)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 58,
"peakMemoryResident": 24000000,
"throughput": 67
"mallocCountTotal": 22,
"peakMemoryResident": 23000000,
"throughput": 194000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 58,
"peakMemoryResident": 24000000,
"throughput": 68
"mallocCountTotal": 22,
"peakMemoryResident": 23000000,
"throughput": 194000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 58,
"peakMemoryResident": 24000000,
"throughput": 69
"mallocCountTotal": 22,
"peakMemoryResident": 23000000,
"throughput": 184000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 24,
"peakMemoryResident": 23000000,
"throughput": 254
"mallocCountTotal": 18,
"peakMemoryResident": 24000000,
"throughput": 493000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 57,
"peakMemoryResident": 24000000,
"throughput": 71
"mallocCountTotal": 22,
"peakMemoryResident": 23000000,
"throughput": 196000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 23,
"mallocCountTotal": 18,
"peakMemoryResident": 23000000,
"throughput": 263
"throughput": 501000
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mallocCountTotal": 23,
"mallocCountTotal": 18,
"peakMemoryResident": 23000000,
"throughput": 267
"throughput": 512000
}
21 changes: 13 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
// swift-tools-version:5.10
// swift-tools-version:6.1
import PackageDescription

let package = Package(
name: "routing-kit",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macOS(.v15),
.iOS(.v18),
.tvOS(.v18),
.watchOS(.v11),
],
products: [
.library(name: "RoutingKit", targets: ["RoutingKit"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3")
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.4"),
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.1"),
],
targets: [
.target(
name: "RoutingKit",
dependencies: [
.product(name: "Logging", package: "swift-log")
.product(name: "Logging", package: "swift-log"),
.product(name: "Algorithms", package: "swift-algorithms"),
],
swiftSettings: swiftSettings
),
Expand All @@ -37,6 +39,9 @@ var swiftSettings: [SwiftSetting] {
[
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("MemberImportVisibility"),
.enableExperimentalFeature("StrictConcurrency=complete"),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("ImmutableWeakCaptures"),
]
}
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,76 @@
<a href="https://discord.gg/vapor"><img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat"></a>
<a href="LICENSE"><img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License"></a>
<a href="https://github.com/vapor/routing-kit/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/vapor/routing-kit/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration"></a>
<a href="https://codecov.io/github/vapor/routing-kit"><img src="https://img.shields.io/codecov/c/github/vapor/routing-kit?style=plastic&logo=codecov&label=codecov"></a>
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift510up.svg" alt="Swift 5.10+"></a>
<a href="https://codecov.io/github/vapor/routing-kit"><img src="https://img.shields.io/codecov/c/github/vapor/routing-kit?style=plastic&logo=codecov&label=codecov" alt="Code Coverage></a>
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift61up.svg" alt="Swift 6.1+"></a>
</p>

<br>

RoutingKit is a high-performance, trie-node router to route HTTP requests to the correct route handler. It allows for dynamic path parameters to make building web frameworks easy.

### Installation

Manually add the following to your `Package.swift` file:

```swift
dependencies: [
.package(url: "https://github.com/vapor/routing-kit.git", from: "5.0.0")
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "RoutingKit", package: "routing-kit"),
]
),
]
```

### Usage

To use RoutingKit, first import the module:

```swift
import RoutingKit
```

Then, create a router builder instance:

```swift
let builder = TrieRouterBuilder(String.self)
// or
let builder = TrieRouterBuilder<Int>()
```

Next, register routes with associated handlers:

```swift
builder.register("handler1", at: ["users", ":id"])
builder.register("handler2", at: ["posts", ":postId", "comments"])
```

Finally, build the router and use it to route incoming paths:

```swift
let router = builder.build()
var params = Parameters()
if let handler = router.route(["users", "123"], parameters: &params) {
print("Routed to handler: \(handler) with params: \(params)")
}
```

> [!NOTE]
> To preserve both speed and safety, once you create a router using the `build()` method, it becomes immutable. Any further modifications require creating a new builder instance.

There's different types of path components you can use when registering routes:
- Constant path components: e.g. `"users"`, `"posts"`
- Parameter path components: e.g. `":id"`, `":postId"`
- Wildcard path components: e.g. `"*"`
- Partial parameter path components: e.g. `":{file-name}.{ext}"`

All of these can be used simply by passing the appropriate strings to the `register` method.

#### Custom Router

You can also create your own custom router by conforming to the `Router` protocol. This allows you to define your own routing logic rather than using the built-in trie-based router.
Loading
Loading