Skip to content

Commit 2dfa33c

Browse files
committed
withRoute(, matching:)
1 parent 1f5c2ea commit 2dfa33c

File tree

4 files changed

+146
-53
lines changed

4 files changed

+146
-53
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// HTTPRoute+withRoute.swift
3+
// FlyingFox
4+
//
5+
// Created by Simon Whitty on 15/09/2025.
6+
// Copyright © 2025 Simon Whitty. All rights reserved.
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/FlyingFox
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
// SOFTWARE.
30+
//
31+
32+
import Foundation
33+
34+
#if compiler(>=6.2)
35+
public func withRoute<T, Failure: Error>(
36+
_ route: HTTPRoute,
37+
matching request: HTTPRequest,
38+
execute body: () async throws(Failure) -> T
39+
) async throws(Failure) -> T? {
40+
guard await route ~= request else { return nil }
41+
do {
42+
return try await HTTPRequest.$matchedRoute.withValue(route) {
43+
return try await body()
44+
}
45+
} catch let error as Failure {
46+
throw error
47+
} catch {
48+
preconditionFailure("cannot occur")
49+
}
50+
}
51+
#elseif compiler(>=6.0)
52+
import struct FlyingSocks.Transferring
53+
54+
public func withRoute<T, Failure: Error>(
55+
isolation: isolated (any Actor)? = #isolation,
56+
_ route: HTTPRoute,
57+
matching request: HTTPRequest,
58+
execute body: () async throws(Failure) -> sending T
59+
) async throws(Failure) -> sending T? {
60+
guard await route ~= request else { return nil }
61+
do {
62+
nonisolated(unsafe) let body = body
63+
return try await HTTPRequest.$matchedRoute.withValue(route) {
64+
_ = isolation
65+
return try await Transferring(body())
66+
}.value
67+
} catch let error as Failure {
68+
throw error
69+
} catch {
70+
preconditionFailure("cannot occur")
71+
}
72+
}
73+
#endif

FlyingFox/Sources/Handlers/RoutedHTTPHandler.swift

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -136,26 +136,6 @@ public extension RoutedHTTPHandler {
136136
}
137137
}
138138

139-
#if compiler(>=6.0)
140-
public extension RoutedHTTPHandler {
141-
142-
static func handleMatchedRequest(
143-
isolation: isolated (any Actor)? = #isolation,
144-
_ request: HTTPRequest,
145-
to route: HTTPRoute,
146-
handler: (HTTPRequest) async throws -> HTTPResponse
147-
) async throws -> HTTPResponse? {
148-
if await route ~= request {
149-
return try await HTTPRequest.$matchedRoute.withValue(route) {
150-
_ = isolation
151-
return try await handler(request)
152-
}
153-
}
154-
return nil
155-
}
156-
}
157-
#endif
158-
159139
extension RoutedHTTPHandler: RangeReplaceableCollection {
160140
public typealias Index = Array<Element>.Index
161141
public typealias Element = (route: HTTPRoute, handler: any HTTPHandler)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// HTTPRoute+withRouteTests.swift
3+
// FlyingFox
4+
//
5+
// Created by Simon Whitty on 15/09/2025.
6+
// Copyright © 2025 Simon Whitty. All rights reserved.
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/FlyingFox
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
// SOFTWARE.
30+
//
31+
32+
@testable import FlyingFox
33+
import Foundation
34+
import Testing
35+
36+
struct HTTPRouteWithRouteTests {
37+
38+
@Test
39+
func handleMatchedRequest_matches() async throws {
40+
// given
41+
let request = HTTPRequest.make("/10/hello?food=fish&qty=🐟")
42+
let route = HTTPRoute("GET /:id/hello?food=:food&qty=:qty")
43+
44+
// when
45+
let res = await withRoute(route, matching: request) {
46+
(
47+
id: request.routeParameters["id"],
48+
food: request.routeParameters["food"],
49+
qty: request.routeParameters["qty"]
50+
)
51+
}
52+
53+
// then
54+
#expect(res?.id == "10")
55+
#expect(res?.food == "fish")
56+
#expect(res?.qty == "🐟")
57+
}
58+
59+
@Test
60+
func handleMatchedRequest_skips() async {
61+
// given
62+
let request = HTTPRequest.make("/chips")
63+
let route = HTTPRoute("GET /fish")
64+
65+
// when
66+
let didMatch = await withRoute(route, matching: request) {
67+
true
68+
}
69+
70+
// then
71+
#expect(didMatch == nil)
72+
}
73+
}

FlyingFox/Tests/Handlers/RoutedHTTPHandlerTests.swift

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -124,39 +124,6 @@ struct RoutedHTTPHandlerTests {
124124
try await handler.handleRequest(.make("/450/hello?food=shrimp&qty=🍤")).bodyString == "900 shrimp 🍤"
125125
)
126126
}
127-
128-
@Test
129-
func handleMatchedRequest_matches() async throws {
130-
// given
131-
let request = HTTPRequest.make("/10/hello?food=fish&qty=🐟")
132-
let route = HTTPRoute("GET /:id/hello?food=:food&qty=:qty")
133-
134-
// when
135-
let response = try await RoutedHTTPHandler.handleMatchedRequest(request, to: route) {
136-
#expect($0.routeParameters["id"] == "10")
137-
#expect($0.routeParameters["food"] == "fish")
138-
#expect($0.routeParameters["qty"] == "🐟")
139-
return HTTPResponse(statusCode: .ok)
140-
}
141-
142-
// then
143-
#expect(response?.statusCode == .ok)
144-
}
145-
146-
@Test
147-
func handleMatchedRequest_skips() async throws {
148-
// given
149-
let request = HTTPRequest.make("/chips")
150-
let route = HTTPRoute("GET /fish")
151-
152-
// when
153-
let response = try await RoutedHTTPHandler.handleMatchedRequest(request, to: route) { _ in
154-
return HTTPResponse(statusCode: .ok)
155-
}
156-
157-
// then
158-
#expect(response?.statusCode == nil)
159-
}
160127
}
161128

162129
private struct MockHandler: HTTPHandler {

0 commit comments

Comments
 (0)