Skip to content

Commit 1b59ff6

Browse files
committed
Add route controller for better composability. (#565)
1 parent 913e6a0 commit 1b59ff6

File tree

4 files changed

+184
-0
lines changed

4 files changed

+184
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Hummingbird server framework project
4+
//
5+
// Copyright (c) 2023-2024 the Hummingbird authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// MARK: - MiddlewareController
16+
17+
/// A type that represents part of your app's middleware and routes.
18+
///
19+
/// You create custom controllers by declaring types that conform to the `MiddlewareController`
20+
/// protocol. Implement the required ``MiddlewareController/body-swift.property`` computed
21+
/// property to provide the content for your custom controller.
22+
///
23+
/// struct MyController: MiddlewareController {
24+
/// typealias Input = Request
25+
/// typealias Output = Response
26+
/// typealias Context = BasicRequestContext
27+
///
28+
/// var body: some MiddlewareProtocol<Input, Output, Context> {
29+
/// Get("foo") { _,_ in "foo" }
30+
/// }
31+
/// }
32+
///
33+
/// Assemble the controller's body by combining one or more of the built-in controllers or middleware.
34+
/// provided by Hummingbird, plus other custom controllers that you define, into a hierarchy of controllers.
35+
public protocol MiddlewareController<Input, Output, Context> {
36+
associatedtype Input
37+
associatedtype Output
38+
associatedtype Context
39+
associatedtype Body: MiddlewareProtocol<Input, Output, Context>
40+
@MiddlewareFixedTypeBuilder<Input, Output, Context> var body: Body { get }
41+
}
42+
43+
// MARK: MiddlewareFixedTypeBuilder + MiddlewareController Builders
44+
45+
extension MiddlewareFixedTypeBuilder {
46+
public static func buildExpression<C0: MiddlewareController>(_ c0: C0) -> C0.Body where C0.Body.Input == Input, C0.Body.Output == Output, C0.Body.Context == Context {
47+
return c0.body
48+
}
49+
50+
public static func buildBlock<C0: MiddlewareController>(_ c0: C0) -> C0.Body {
51+
return c0.body
52+
}
53+
54+
public static func buildPartialBlock<C0: MiddlewareController>(first: C0) -> C0.Body {
55+
first.body
56+
}
57+
58+
public static func buildPartialBlock<M0: MiddlewareProtocol, C0: MiddlewareController>(
59+
accumulated m0: M0,
60+
next c0: C0
61+
) -> _Middleware2<M0, C0.Body> where M0.Input == C0.Body.Input, M0.Output == C0.Body.Output, M0.Context == C0.Body.Context {
62+
_Middleware2(m0, c0.body)
63+
}
64+
}

Sources/Hummingbird/Middleware/MiddlewareModule/MiddlewareStack.swift

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
// MARK: - Middleware2
16+
1517
public struct _Middleware2<M0: MiddlewareProtocol, M1: MiddlewareProtocol>: MiddlewareProtocol where M0.Input == M1.Input, M0.Context == M1.Context, M0.Output == M1.Output {
1618
public typealias Input = M0.Input
1719
public typealias Output = M0.Output
@@ -34,6 +36,10 @@ public struct _Middleware2<M0: MiddlewareProtocol, M1: MiddlewareProtocol>: Midd
3436
}
3537
}
3638

39+
extension _Middleware2: RouterMiddleware where M0.Input == Request, M0.Output == Response {}
40+
41+
// MARK: - MiddlewareFixedTypeBuilder
42+
3743
/// Middleware stack result builder
3844
///
3945
/// Generates a middleware stack from the elements inside the result builder. The input,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Hummingbird server framework project
4+
//
5+
// Copyright (c) 2023-2024 the Hummingbird authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// MARK: - RouterController
16+
17+
/// A type that represents part of your app's middleware and routes where their ``MiddlewareController/Input`` is
18+
/// ``Request`` and ``MiddlewareController/Output`` is ``Response``.
19+
///
20+
/// You create custom controllers by declaring types that conform to the `RouterController`
21+
/// protocol. Implement the required ``RouterController/body-swift.property`` computed
22+
/// property to provide the content for your custom controller.
23+
///
24+
/// struct MyController: RouterController {
25+
/// typealias Context = BasicRouterRequestContext
26+
///
27+
/// var body: some RouterMiddleware<Context> {
28+
/// Get("foo") { _,_ in "foo" }
29+
/// }
30+
/// }
31+
///
32+
/// Assemble the controller's body by combining one or more of the built-in controllers or middleware.
33+
/// provided by Hummingbird, plus other custom controllers that you define, into a hierarchy of controllers.
34+
public protocol RouterController<Context>: MiddlewareController where Body: RouterMiddleware<Context> {
35+
associatedtype Context
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Hummingbird server framework project
4+
//
5+
// Copyright (c) 2021-2023 the Hummingbird authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Hummingbird
16+
import HummingbirdRouter
17+
import HummingbirdTesting
18+
import XCTest
19+
20+
final class ControllerTests: XCTestCase {
21+
22+
// MARK: MiddlewareController
23+
24+
func testMiddlewareController() async throws {
25+
struct TestController: MiddlewareController {
26+
typealias Input = Request
27+
typealias Output = Response
28+
typealias Context = BasicRouterRequestContext
29+
30+
var body: some MiddlewareProtocol<Input, Output, Context> {
31+
Get("foo") { _,_ in "foo" }
32+
Get("bar") { _,_ in "bar" }
33+
}
34+
}
35+
36+
let router = RouterBuilder(context: BasicRouterRequestContext.self) {
37+
TestController()
38+
}
39+
40+
let app = Application(responder: router)
41+
try await app.test(.router) { client in
42+
try await client.execute(uri: "/foo", method: .get) {
43+
XCTAssertEqual(String(buffer: $0.body), "foo")
44+
}
45+
46+
try await client.execute(uri: "/bar", method: .get) {
47+
XCTAssertEqual(String(buffer: $0.body), "bar")
48+
}
49+
}
50+
}
51+
52+
// MARK: RouterController
53+
54+
func testRouterController() async throws {
55+
struct TestController: RouterController {
56+
typealias Context = BasicRouterRequestContext
57+
var body: some RouterMiddleware<Context> {
58+
Get("foo") { _,_ in "foo" }
59+
Get("bar") { _,_ in "bar" }
60+
}
61+
}
62+
63+
let router = RouterBuilder(context: BasicRouterRequestContext.self) {
64+
TestController()
65+
}
66+
67+
let app = Application(responder: router)
68+
try await app.test(.router) { client in
69+
try await client.execute(uri: "/foo", method: .get) {
70+
XCTAssertEqual(String(buffer: $0.body), "foo")
71+
}
72+
73+
try await client.execute(uri: "/bar", method: .get) {
74+
XCTAssertEqual(String(buffer: $0.body), "bar")
75+
}
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)