diff --git a/Benchmarks/Benchmarks/Router/RouterBenchmarks.swift b/Benchmarks/Benchmarks/Router/RouterBenchmarks.swift index 082615549..4c35f67ed 100644 --- a/Benchmarks/Benchmarks/Router/RouterBenchmarks.swift +++ b/Benchmarks/Benchmarks/Router/RouterBenchmarks.swift @@ -22,7 +22,7 @@ import NIOCore import NIOPosix /// Implementation of a basic request context that supports everything the Hummingbird library needs -struct BasicBenchmarkContext: HBBaseRequestContext { +struct BasicBenchmarkContext: HBRequestContext { var coreContext: HBCoreRequestContext init( @@ -40,7 +40,7 @@ struct BenchmarkBodyWriter: Sendable, HBResponseBodyWriter { extension Benchmark { @discardableResult - convenience init?( + convenience init?( name: String, context: Context.Type = BasicBenchmarkContext.self, configuration: Benchmark.Configuration = Benchmark.defaultConfiguration, diff --git a/Sources/Hummingbird/Middleware/SetCodableMiddleware.swift b/Sources/Hummingbird/Middleware/SetCodableMiddleware.swift deleted file mode 100644 index f8bb51619..000000000 --- a/Sources/Hummingbird/Middleware/SetCodableMiddleware.swift +++ /dev/null @@ -1,30 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Hummingbird server framework project -// -// Copyright (c) 2023 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 -// -//===----------------------------------------------------------------------===// - -public struct HBSetCodableMiddleware: HBMiddlewareProtocol { - let decoder: @Sendable () -> Decoder - let encoder: @Sendable () -> Encoder - - public init(decoder: @autoclosure @escaping @Sendable () -> Decoder, encoder: @autoclosure @escaping @Sendable () -> Encoder) { - self.decoder = decoder - self.encoder = encoder - } - - public func handle(_ request: HBRequest, context: Context, next: (HBRequest, Context) async throws -> HBResponse) async throws -> HBResponse { - var context = context - context.coreContext.requestDecoder = self.decoder() - context.coreContext.responseEncoder = self.encoder() - return try await next(request, context) - } -} diff --git a/Sources/Hummingbird/Middleware/TracingMiddleware.swift b/Sources/Hummingbird/Middleware/TracingMiddleware.swift index 8223d67c0..b1f050f4b 100644 --- a/Sources/Hummingbird/Middleware/TracingMiddleware.swift +++ b/Sources/Hummingbird/Middleware/TracingMiddleware.swift @@ -63,7 +63,7 @@ public struct HBTracingMiddleware: HBMiddlewarePr attributes["http.user_agent"] = request.headers[.userAgent] attributes["http.request_content_length"] = request.headers[.contentLength].map { Int($0) } ?? nil - if let remoteAddress = (context as? HBRemoteAddressRequestContext)?.remoteAddress { + if let remoteAddress = (context as? any HBRemoteAddressRequestContext)?.remoteAddress { attributes["net.sock.peer.port"] = remoteAddress.port switch remoteAddress.protocol { diff --git a/Sources/Hummingbird/Server/RequestContext.swift b/Sources/Hummingbird/Server/RequestContext.swift index 75363dff1..c0878a1a5 100644 --- a/Sources/Hummingbird/Server/RequestContext.swift +++ b/Sources/Hummingbird/Server/RequestContext.swift @@ -35,12 +35,6 @@ public struct EndpointPath: Sendable { /// Request context values required by Hummingbird itself. public struct HBCoreRequestContext: Sendable { - /// Request decoder - @usableFromInline - var requestDecoder: HBRequestDecoder - /// Response encoder - @usableFromInline - var responseEncoder: HBResponseEncoder /// ByteBuffer allocator used by request @usableFromInline let allocator: ByteBufferAllocator @@ -54,13 +48,9 @@ public struct HBCoreRequestContext: Sendable { @inlinable public init( - requestDecoder: HBRequestDecoder = NullDecoder(), - responseEncoder: HBResponseEncoder = NullEncoder(), allocator: ByteBufferAllocator, logger: Logger ) { - self.requestDecoder = requestDecoder - self.responseEncoder = responseEncoder self.allocator = allocator self.logger = logger self.endpointPath = .init() @@ -71,21 +61,21 @@ public struct HBCoreRequestContext: Sendable { /// Protocol that all request contexts should conform to. Holds data associated with /// a request. Provides context for request processing public protocol HBBaseRequestContext: Sendable { + associatedtype Decoder: HBRequestDecoder = NullDecoder + associatedtype Encoder: HBResponseEncoder = NullEncoder + /// Core context var coreContext: HBCoreRequestContext { get set } /// Maximum upload size allowed for routes that don't stream the request payload. This /// limits how much memory would be used for one request var maxUploadSize: Int { get } + /// Request decoder + var requestDecoder: Decoder { get } + /// Response encoder + var responseEncoder: Encoder { get } } extension HBBaseRequestContext { - /// Request decoder - @inlinable - public var requestDecoder: HBRequestDecoder { coreContext.requestDecoder } - /// Response encoder - @inlinable - public var responseEncoder: HBResponseEncoder { coreContext.responseEncoder } - /// ByteBuffer allocator used by request @inlinable public var allocator: ByteBufferAllocator { coreContext.allocator } /// Logger to use with Request @@ -109,6 +99,14 @@ extension HBBaseRequestContext { public var id: String { self.logger[metadataKey: "hb_id"]!.description } } +extension HBBaseRequestContext where Decoder == NullDecoder { + public var requestDecoder: Decoder { NullDecoder() } +} + +extension HBBaseRequestContext where Encoder == NullEncoder { + public var responseEncoder: Encoder { NullEncoder() } +} + /// Protocol for a request context that can be created from a NIO Channel public protocol HBRequestContext: HBBaseRequestContext { /// initialize an `HBRequestContext` diff --git a/Sources/PerformanceTest/main.swift b/Sources/PerformanceTest/main.swift index 8d4a74fca..12a65aea1 100644 --- a/Sources/PerformanceTest/main.swift +++ b/Sources/PerformanceTest/main.swift @@ -23,12 +23,13 @@ struct PerformanceTestRequestContext: HBRequestContext { init(allocator: ByteBufferAllocator, logger: Logger) { self.coreContext = .init( - requestDecoder: JSONDecoder(), - responseEncoder: JSONEncoder(), allocator: allocator, logger: logger ) } + + var requestDecoder: JSONDecoder { .init() } + var responseEncoder: JSONEncoder { .init() } } // get environment diff --git a/Tests/HummingbirdFoundationTests/HummingBirdJSONTests.swift b/Tests/HummingbirdFoundationTests/HummingBirdJSONTests.swift index fc1fdda4d..e61998b40 100644 --- a/Tests/HummingbirdFoundationTests/HummingBirdJSONTests.swift +++ b/Tests/HummingbirdFoundationTests/HummingBirdJSONTests.swift @@ -15,6 +15,7 @@ import Hummingbird import HummingbirdFoundation import HummingbirdXCT +import Logging import XCTest class HummingbirdJSONTests: XCTestCase { @@ -26,9 +27,22 @@ class HummingbirdJSONTests: XCTestCase { struct Error: Swift.Error {} + struct JSONCodingRequestContext: HBRequestContext { + var coreContext: HBCoreRequestContext + + init(allocator: ByteBufferAllocator, logger: Logger) { + self.coreContext = .init( + allocator: allocator, + logger: logger + ) + } + + var requestDecoder: JSONDecoder { .init() } + var responseEncoder: JSONEncoder { .init() } + } + func testDecode() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.put("/user") { request, context -> HTTPResponse.Status in guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) } XCTAssertEqual(user.name, "John Smith") @@ -46,8 +60,7 @@ class HummingbirdJSONTests: XCTestCase { } func testEncode() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.get("/user") { _, _ -> User in return User(name: "John Smith", email: "john.smith@email.com", age: 25) } @@ -63,8 +76,7 @@ class HummingbirdJSONTests: XCTestCase { } func testEncode2() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.get("/json") { _, _ in return ["message": "Hello, world!"] } diff --git a/Tests/HummingbirdFoundationTests/URLEncodedForm/Application+URLEncodedFormTests.swift b/Tests/HummingbirdFoundationTests/URLEncodedForm/Application+URLEncodedFormTests.swift index b6da8571d..3cc9eae84 100644 --- a/Tests/HummingbirdFoundationTests/URLEncodedForm/Application+URLEncodedFormTests.swift +++ b/Tests/HummingbirdFoundationTests/URLEncodedForm/Application+URLEncodedFormTests.swift @@ -15,6 +15,7 @@ import Hummingbird import HummingbirdFoundation import HummingbirdXCT +import Logging import XCTest class HummingBirdURLEncodedTests: XCTestCase { @@ -24,11 +25,24 @@ class HummingBirdURLEncodedTests: XCTestCase { let age: Int } + struct URLEncodedCodingRequestContext: HBRequestContext { + var coreContext: HBCoreRequestContext + + init(allocator: ByteBufferAllocator, logger: Logger) { + self.coreContext = .init( + allocator: allocator, + logger: logger + ) + } + + var requestDecoder: URLEncodedFormDecoder { .init() } + var responseEncoder: URLEncodedFormEncoder { .init() } + } + struct Error: Swift.Error {} func testDecode() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: URLEncodedFormDecoder(), encoder: URLEncodedFormEncoder())) + let router = HBRouter(context: URLEncodedCodingRequestContext.self) router.put("/user") { request, context -> HTTPResponse.Status in guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) } XCTAssertEqual(user.name, "John Smith") @@ -46,8 +60,7 @@ class HummingBirdURLEncodedTests: XCTestCase { } func testEncode() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: URLEncodedFormDecoder(), encoder: URLEncodedFormEncoder())) + let router = HBRouter(context: URLEncodedCodingRequestContext.self) router.get("/user") { _, _ -> User in return User(name: "John Smith", email: "john.smith@email.com", age: 25) } diff --git a/Tests/HummingbirdFoundationTests/UUIDTests.swift b/Tests/HummingbirdFoundationTests/UUIDTests.swift index bea13cf62..494c0b296 100644 --- a/Tests/HummingbirdFoundationTests/UUIDTests.swift +++ b/Tests/HummingbirdFoundationTests/UUIDTests.swift @@ -25,12 +25,13 @@ final class UUIDTests: XCTestCase { init(allocator: ByteBufferAllocator, logger: Logger) { self.coreContext = .init( - requestDecoder: JSONDecoder(), - responseEncoder: JSONEncoder(), allocator: allocator, logger: logger ) } + + var requestDecoder: JSONDecoder { .init() } + var responseEncoder: JSONEncoder { .init() } } func testGetUUID() async throws { diff --git a/Tests/HummingbirdTests/ApplicationTests.swift b/Tests/HummingbirdTests/ApplicationTests.swift index 8041b1aed..6a957e503 100644 --- a/Tests/HummingbirdTests/ApplicationTests.swift +++ b/Tests/HummingbirdTests/ApplicationTests.swift @@ -26,6 +26,19 @@ import ServiceLifecycle import XCTest final class ApplicationTests: XCTestCase { + struct JSONCodingRequestContext: HBRequestContext { + var coreContext: HBCoreRequestContext + + init(allocator: ByteBufferAllocator, logger: Logger) { + self.coreContext = .init( + allocator: allocator, + logger: logger + ) + } + + var requestDecoder: JSONDecoder { .init() } + var responseEncoder: JSONEncoder { .init() } + } func randomBuffer(size: Int) -> ByteBuffer { var data = [UInt8](repeating: 0, count: size) data = data.map { _ in UInt8.random(in: 0...255) } @@ -325,7 +338,7 @@ final class ApplicationTests: XCTestCase { } func testTypedResponse() async throws { - let router = HBRouter() + let router = HBRouter(context: JSONCodingRequestContext.self) router.delete("/hello") { _, _ in return HBEditedResponse( status: .preconditionRequired, @@ -349,8 +362,7 @@ final class ApplicationTests: XCTestCase { struct Result: HBResponseEncodable { let value: String } - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.patch("/hello") { _, _ in return HBEditedResponse( status: .multipleChoices, diff --git a/Tests/HummingbirdTests/HandlerTests.swift b/Tests/HummingbirdTests/HandlerTests.swift index 1e087d5fa..fcaadba9b 100644 --- a/Tests/HummingbirdTests/HandlerTests.swift +++ b/Tests/HummingbirdTests/HandlerTests.swift @@ -15,9 +15,24 @@ import Hummingbird import HummingbirdFoundation import HummingbirdXCT +import Logging import XCTest final class HandlerTests: XCTestCase { + struct JSONCodingRequestContext: HBRequestContext { + var coreContext: HBCoreRequestContext + + init(allocator: ByteBufferAllocator, logger: Logger) { + self.coreContext = .init( + allocator: allocator, + logger: logger + ) + } + + var requestDecoder: JSONDecoder { .init() } + var responseEncoder: JSONEncoder { .init() } + } + struct DecodeTest: HBRouteHandler, Decodable { let value: Value @@ -31,8 +46,7 @@ final class HandlerTests: XCTestCase { } func testDecodeKeyError() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.post("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder()) @@ -52,8 +66,7 @@ final class HandlerTests: XCTestCase { } func testDecodeTypeError() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.post("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder()) @@ -73,8 +86,7 @@ final class HandlerTests: XCTestCase { } func testDecodeValueError() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.post("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder()) @@ -99,8 +111,7 @@ final class HandlerTests: XCTestCase { } func testDecodeInputError() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.post("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder()) @@ -120,8 +131,7 @@ final class HandlerTests: XCTestCase { } func testDecode() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.post("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder()) @@ -134,8 +144,7 @@ final class HandlerTests: XCTestCase { } func testDecodeFail() async throws { - let router = HBRouter() - router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder())) + let router = HBRouter(context: JSONCodingRequestContext.self) router.get("/hello", use: DecodeTest.self) let app = HBApplication(responder: router.buildResponder())