From f9e4fd1b81ec17b3c4d4b1676917cd8e5d1a5181 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 10 Jun 2024 11:23:18 +0100 Subject: [PATCH 1/4] Replace RequestContext initialization parameters with associatedtype --- Sources/Hummingbird/Application.swift | 6 ++- .../Hummingbird/Server/RequestContext.swift | 43 ++++++++++++------- .../RouterBuilderContext.swift | 4 +- .../RouterTestFramework.swift | 6 ++- .../HummingbirdRouterTests/RouterTests.swift | 6 +-- Tests/HummingbirdTests/ApplicationTests.swift | 17 +++----- Tests/HummingbirdTests/RouterTests.swift | 4 +- .../Application+URLEncodedFormTests.swift | 4 +- 8 files changed, 51 insertions(+), 39 deletions(-) diff --git a/Sources/Hummingbird/Application.swift b/Sources/Hummingbird/Application.swift index 0e2394f99..905ec607e 100644 --- a/Sources/Hummingbird/Application.swift +++ b/Sources/Hummingbird/Application.swift @@ -101,8 +101,10 @@ extension ApplicationProtocol { logger: self.logger ) { request, channel in let context = Self.Responder.Context( - channel: channel, - logger: self.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) + source: .init( + channel: channel, + logger: self.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) + ) ) // respond to request var response: Response diff --git a/Sources/Hummingbird/Server/RequestContext.swift b/Sources/Hummingbird/Server/RequestContext.swift index 74571d929..564cdda80 100644 --- a/Sources/Hummingbird/Server/RequestContext.swift +++ b/Sources/Hummingbird/Server/RequestContext.swift @@ -34,6 +34,14 @@ public struct EndpointPath: Sendable { private let _value: NIOLockedValueBox } +/// Protocol for request context source +public protocol RequestContextSource { + /// ByteBuffer allocator + var allocator: ByteBufferAllocator { get } + /// Request Logger + var logger: Logger { get } +} + /// Request context values required by Hummingbird itself. public struct CoreRequestContext: Sendable { /// ByteBuffer allocator used by request @@ -49,11 +57,10 @@ public struct CoreRequestContext: Sendable { @inlinable public init( - allocator: ByteBufferAllocator, - logger: Logger + source: some RequestContextSource ) { - self.allocator = allocator - self.logger = logger + self.allocator = source.allocator + self.logger = source.logger self.endpointPath = .init() self.parameters = .init() } @@ -62,9 +69,12 @@ public struct CoreRequestContext: Sendable { /// Protocol that all request contexts should conform to. Holds data associated with /// a request. Provides context for request processing public protocol BaseRequestContext: Sendable { + associatedtype Source: RequestContextSource associatedtype Decoder: RequestDecoder = JSONDecoder associatedtype Encoder: ResponseEncoder = JSONEncoder + /// Initialise RequestContext from source + init(source: Source) /// Core context var coreContext: CoreRequestContext { get set } /// Maximum upload size allowed for routes that don't stream the request payload. This @@ -116,13 +126,19 @@ extension BaseRequestContext where Encoder == JSONEncoder { } } +/// RequestContext source for server applications +public struct ServerRequestContextSource: RequestContextSource { + public init(channel: any Channel, logger: Logger) { + self.channel = channel + self.logger = logger + } + + public let channel: Channel + public let logger: Logger + public var allocator: ByteBufferAllocator { channel.allocator } +} /// Protocol for a request context that can be created from a NIO Channel -public protocol RequestContext: BaseRequestContext { - /// initialize an `RequestContext` - /// - Parameters: - /// - channel: Channel that initiated this request - /// - logger: Logger used for this request - init(channel: Channel, logger: Logger) +public protocol RequestContext: BaseRequestContext where Source == ServerRequestContextSource { } /// Implementation of a basic request context that supports everything the Hummingbird library needs @@ -134,10 +150,7 @@ public struct BasicRequestContext: RequestContext { /// - Parameters: /// - allocator: Allocator /// - logger: Logger - public init(channel: Channel, logger: Logger) { - self.coreContext = .init( - allocator: channel.allocator, - logger: logger - ) + public init(source: Source) { + self.coreContext = .init(source: source) } } diff --git a/Sources/HummingbirdRouter/RouterBuilderContext.swift b/Sources/HummingbirdRouter/RouterBuilderContext.swift index 1a01b4027..554192adc 100644 --- a/Sources/HummingbirdRouter/RouterBuilderContext.swift +++ b/Sources/HummingbirdRouter/RouterBuilderContext.swift @@ -37,8 +37,8 @@ public struct BasicRouterRequestContext: RequestContext, RouterRequestContext { public var routerContext: RouterBuilderContext public var coreContext: CoreRequestContext - public init(channel: Channel, logger: Logger) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) + public init(source: Source) { + self.coreContext = .init(source: source) self.routerContext = .init() } } diff --git a/Sources/HummingbirdTesting/RouterTestFramework.swift b/Sources/HummingbirdTesting/RouterTestFramework.swift index 8b64e033c..d01b10885 100644 --- a/Sources/HummingbirdTesting/RouterTestFramework.swift +++ b/Sources/HummingbirdTesting/RouterTestFramework.swift @@ -39,8 +39,10 @@ struct RouterTestFramework: ApplicationTestFramework w self.logger = app.logger self.makeContext = { logger in Responder.Context( - channel: NIOAsyncTestingChannel(), - logger: logger + source: .init( + channel: NIOAsyncTestingChannel(), + logger: logger + ) ) } } diff --git a/Tests/HummingbirdRouterTests/RouterTests.swift b/Tests/HummingbirdRouterTests/RouterTests.swift index 70b1ffdfc..21a154ae7 100644 --- a/Tests/HummingbirdRouterTests/RouterTests.swift +++ b/Tests/HummingbirdRouterTests/RouterTests.swift @@ -450,15 +450,13 @@ public struct TestRouterContext2: RouterRequestContext, RequestContext { public var routerContext: RouterBuilderContext /// core context public var coreContext: CoreRequestContext - /// Connected remote host - public var remoteAddress: SocketAddress? { nil } /// additional data public var string: String - public init(channel: Channel, logger: Logger) { + public init(source: Source) { self.routerContext = .init() - self.coreContext = .init(allocator: channel.allocator, logger: logger) + self.coreContext = .init(source: source) self.string = "" } } diff --git a/Tests/HummingbirdTests/ApplicationTests.swift b/Tests/HummingbirdTests/ApplicationTests.swift index e93a4a90e..305f3eabb 100644 --- a/Tests/HummingbirdTests/ApplicationTests.swift +++ b/Tests/HummingbirdTests/ApplicationTests.swift @@ -379,8 +379,8 @@ final class ApplicationTests: XCTestCase { return encoder } - init(channel: Channel, logger: Logger) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) + init(source: Source) { + self.coreContext = .init(source: source) } } struct Name: ResponseCodable { @@ -449,8 +449,8 @@ final class ApplicationTests: XCTestCase { func testMaxUploadSize() async throws { struct MaxUploadRequestContext: RequestContext { - init(channel: Channel, logger: Logger) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) + init(source: Source) { + self.coreContext = .init(source: source) } var coreContext: CoreRequestContext @@ -486,12 +486,9 @@ final class ApplicationTests: XCTestCase { // socket address let remoteAddress: SocketAddress? - init( - channel: Channel, - logger: Logger - ) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) - self.remoteAddress = channel.remoteAddress + init(source: Source) { + self.coreContext = .init(source: source) + self.remoteAddress = source.channel.remoteAddress } } let router = Router(context: SocketAddressRequestContext.self) diff --git a/Tests/HummingbirdTests/RouterTests.swift b/Tests/HummingbirdTests/RouterTests.swift index 9cf8e6f20..3b57dccdf 100644 --- a/Tests/HummingbirdTests/RouterTests.swift +++ b/Tests/HummingbirdTests/RouterTests.swift @@ -603,8 +603,8 @@ final class RouterTests: XCTestCase { } struct TestRouterContext2: RequestContext { - init(channel: Channel, logger: Logger) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) + init(source: Source) { + self.coreContext = .init(source: source) self.string = "" } diff --git a/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift b/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift index 2dfa48795..c9357058c 100644 --- a/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift +++ b/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift @@ -28,8 +28,8 @@ class HummingBirdURLEncodedTests: XCTestCase { struct URLEncodedCodingRequestContext: RequestContext { var coreContext: CoreRequestContext - init(channel: Channel, logger: Logger) { - self.coreContext = .init(allocator: channel.allocator, logger: logger) + init(source: Source) { + self.coreContext = .init(source: source) } var requestDecoder: URLEncodedFormDecoder { .init() } From 2475e72ba0d181b1c6ef181a5f5ccf071c56d2d6 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 10 Jun 2024 11:30:09 +0100 Subject: [PATCH 2/4] swift format --- Sources/Hummingbird/Server/RequestContext.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Hummingbird/Server/RequestContext.swift b/Sources/Hummingbird/Server/RequestContext.swift index 564cdda80..102a9df25 100644 --- a/Sources/Hummingbird/Server/RequestContext.swift +++ b/Sources/Hummingbird/Server/RequestContext.swift @@ -135,11 +135,11 @@ public struct ServerRequestContextSource: RequestContextSource { public let channel: Channel public let logger: Logger - public var allocator: ByteBufferAllocator { channel.allocator } + public var allocator: ByteBufferAllocator { self.channel.allocator } } + /// Protocol for a request context that can be created from a NIO Channel -public protocol RequestContext: BaseRequestContext where Source == ServerRequestContextSource { -} +public protocol RequestContext: BaseRequestContext where Source == ServerRequestContextSource {} /// Implementation of a basic request context that supports everything the Hummingbird library needs public struct BasicRequestContext: RequestContext { From ec1bc51acd73166731bd3a41efcc076f8d619bc4 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 15 Jun 2024 10:40:44 +0100 Subject: [PATCH 3/4] Collapse BaseRequestContext into RequestContext (#474) * Merge BaseRequestContext and RequestContext into one protocol * swiftformat * Add InstantiableRequestContext --- Sources/Hummingbird/Application.swift | 13 +++--- .../Codable/CodableProtocols.swift | 4 +- .../Hummingbird/Codable/JSON/JSONCoding.swift | 4 +- .../Codable/ResponseEncodable.swift | 6 +-- .../URLEncodedForm+Request.swift | 4 +- Sources/Hummingbird/Deprecations.swift | 4 +- Sources/Hummingbird/Files/FileIO.swift | 10 ++-- Sources/Hummingbird/Files/FileProvider.swift | 4 +- .../Hummingbird/Files/LocalFileSystem.swift | 4 +- .../Middleware/CORSMiddleware.swift | 2 +- .../Middleware/FileMiddleware.swift | 2 +- .../Middleware/LogRequestMiddleware.swift | 2 +- .../Middleware/MetricsMiddleware.swift | 2 +- .../Middleware/TracingMiddleware.swift | 4 +- .../Router/EndpointResponder.swift | 2 +- .../Router/ResponseGenerator.swift | 16 +++---- .../Hummingbird/Router/RouteCollection.swift | 2 +- Sources/Hummingbird/Router/Router.swift | 4 +- Sources/Hummingbird/Router/RouterGroup.swift | 2 +- .../Hummingbird/Router/RouterMethods.swift | 2 +- .../Hummingbird/Router/RouterResponder.swift | 2 +- .../Hummingbird/Server/EditedHTTPError.swift | 2 +- Sources/Hummingbird/Server/Request.swift | 4 +- .../Hummingbird/Server/RequestContext.swift | 46 ++++++++++--------- .../RouterBuilderContext.swift | 2 +- .../HummingbirdTesting/Application+Test.swift | 2 +- .../RouterTestFramework.swift | 4 +- .../MiddlewareTests.swift | 10 ++-- .../HummingbirdRouterTests/RouterTests.swift | 8 ++-- Tests/HummingbirdTests/ApplicationTests.swift | 21 ++++++++- .../FileMiddlewareTests.swift | 4 +- Tests/HummingbirdTests/MiddlewareTests.swift | 12 ++--- Tests/HummingbirdTests/RouterTests.swift | 8 ++-- Tests/HummingbirdTests/TracingTests.swift | 4 +- 34 files changed, 121 insertions(+), 101 deletions(-) diff --git a/Sources/Hummingbird/Application.swift b/Sources/Hummingbird/Application.swift index 905ec607e..7d2640c44 100644 --- a/Sources/Hummingbird/Application.swift +++ b/Sources/Hummingbird/Application.swift @@ -41,7 +41,8 @@ public enum EventLoopGroupProvider { } } -public protocol ApplicationProtocol: Service where Context: RequestContext { +/// Protocol for an Application. Brings together all the components of Hummingbird together +public protocol ApplicationProtocol: Service where Context: InstantiableRequestContext, Context.Source == ServerRequestContextSource { /// Responder that generates a response from a requests and context associatedtype Responder: HTTPResponder /// Context passed with Request to responder @@ -100,10 +101,11 @@ extension ApplicationProtocol { eventLoopGroup: self.eventLoopGroup, logger: self.logger ) { request, channel in + let logger = self.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) let context = Self.Responder.Context( source: .init( channel: channel, - logger: self.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) + logger: logger ) ) // respond to request @@ -117,7 +119,7 @@ extension ApplicationProtocol { response = httpError.response(allocator: channel.allocator) default: // this error has not been recognised - context.logger.debug("Unrecognised Error", metadata: ["error": "\(error)"]) + logger.debug("Unrecognised Error", metadata: ["error": "\(error)"]) response = Response( status: .internalServerError, body: .init() @@ -171,10 +173,7 @@ extension ApplicationProtocol { /// try await app.runService() /// ``` /// Editing the application setup after calling `runService` will produce undefined behaviour. -public struct Application: ApplicationProtocol where Responder.Context: RequestContext { - public typealias Context = Responder.Context - public typealias Responder = Responder - +public struct Application: ApplicationProtocol where Responder.Context: InstantiableRequestContext, Responder.Context.Source == ServerRequestContextSource { // MARK: Member variables /// event loop group used by application diff --git a/Sources/Hummingbird/Codable/CodableProtocols.swift b/Sources/Hummingbird/Codable/CodableProtocols.swift index 64bea8674..c5d05facd 100644 --- a/Sources/Hummingbird/Codable/CodableProtocols.swift +++ b/Sources/Hummingbird/Codable/CodableProtocols.swift @@ -21,7 +21,7 @@ public protocol ResponseEncoder { /// - Parameters: /// - value: value to encode /// - request: request that generated this value - func encode(_ value: some Encodable, from request: Request, context: some BaseRequestContext) throws -> Response + func encode(_ value: some Encodable, from request: Request, context: some RequestContext) throws -> Response } /// protocol for decoder deserializing from a Request body @@ -30,5 +30,5 @@ public protocol RequestDecoder { /// - Parameters: /// - type: type to decode to /// - request: request - func decode(_ type: T.Type, from request: Request, context: some BaseRequestContext) async throws -> T + func decode(_ type: T.Type, from request: Request, context: some RequestContext) async throws -> T } diff --git a/Sources/Hummingbird/Codable/JSON/JSONCoding.swift b/Sources/Hummingbird/Codable/JSON/JSONCoding.swift index 563355e97..bf1e17597 100644 --- a/Sources/Hummingbird/Codable/JSON/JSONCoding.swift +++ b/Sources/Hummingbird/Codable/JSON/JSONCoding.swift @@ -22,7 +22,7 @@ extension JSONEncoder: ResponseEncoder { /// - Parameters: /// - value: Value to encode /// - request: Request used to generate response - public func encode(_ value: some Encodable, from request: Request, context: some BaseRequestContext) throws -> Response { + public func encode(_ value: some Encodable, from request: Request, context: some RequestContext) throws -> Response { let data = try self.encode(value) let buffer = context.allocator.buffer(data: data) return Response( @@ -41,7 +41,7 @@ extension JSONDecoder: RequestDecoder { /// - Parameters: /// - type: Type to decode /// - request: Request to decode from - public func decode(_ type: T.Type, from request: Request, context: some BaseRequestContext) async throws -> T { + public func decode(_ type: T.Type, from request: Request, context: some RequestContext) async throws -> T { let buffer = try await request.body.collect(upTo: context.maxUploadSize) return try self.decode(T.self, from: buffer) } diff --git a/Sources/Hummingbird/Codable/ResponseEncodable.swift b/Sources/Hummingbird/Codable/ResponseEncodable.swift index e24628314..63c2e7512 100644 --- a/Sources/Hummingbird/Codable/ResponseEncodable.swift +++ b/Sources/Hummingbird/Codable/ResponseEncodable.swift @@ -23,7 +23,7 @@ public protocol ResponseCodable: ResponseEncodable, Decodable {} /// Extend ResponseEncodable to conform to ResponseGenerator extension ResponseEncodable { - public func response(from request: Request, context: some BaseRequestContext) throws -> Response { + public func response(from request: Request, context: some RequestContext) throws -> Response { return try context.responseEncoder.encode(self, from: request, context: context) } } @@ -33,7 +33,7 @@ extension Array: ResponseGenerator where Element: Encodable {} /// Extend Array to conform to ResponseEncodable extension Array: ResponseEncodable where Element: Encodable { - public func response(from request: Request, context: some BaseRequestContext) throws -> Response { + public func response(from request: Request, context: some RequestContext) throws -> Response { return try context.responseEncoder.encode(self, from: request, context: context) } } @@ -43,7 +43,7 @@ extension Dictionary: ResponseGenerator where Key: Encodable, Value: Encodable { /// Extend Array to conform to ResponseEncodable extension Dictionary: ResponseEncodable where Key: Encodable, Value: Encodable { - public func response(from request: Request, context: some BaseRequestContext) throws -> Response { + public func response(from request: Request, context: some RequestContext) throws -> Response { return try context.responseEncoder.encode(self, from: request, context: context) } } diff --git a/Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedForm+Request.swift b/Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedForm+Request.swift index 07a0cb6ff..f67a5cecc 100644 --- a/Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedForm+Request.swift +++ b/Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedForm+Request.swift @@ -17,7 +17,7 @@ extension URLEncodedFormEncoder: ResponseEncoder { /// - Parameters: /// - value: Value to encode /// - request: Request used to generate response - public func encode(_ value: some Encodable, from request: Request, context: some BaseRequestContext) throws -> Response { + public func encode(_ value: some Encodable, from request: Request, context: some RequestContext) throws -> Response { let string = try self.encode(value) let buffer = context.allocator.buffer(string: string) return Response( @@ -36,7 +36,7 @@ extension URLEncodedFormDecoder: RequestDecoder { /// - Parameters: /// - type: Type to decode /// - request: Request to decode from - public func decode(_ type: T.Type, from request: Request, context: some BaseRequestContext) async throws -> T { + public func decode(_ type: T.Type, from request: Request, context: some RequestContext) async throws -> T { let buffer = try await request.body.collect(upTo: context.maxUploadSize) let string = String(buffer: buffer) return try self.decode(T.self, from: string) diff --git a/Sources/Hummingbird/Deprecations.swift b/Sources/Hummingbird/Deprecations.swift index 3f100392c..189605a60 100644 --- a/Sources/Hummingbird/Deprecations.swift +++ b/Sources/Hummingbird/Deprecations.swift @@ -29,8 +29,8 @@ public typealias HBEnvironment = Environment @_documentation(visibility: internal) @available(*, deprecated, renamed: "FileIO") public typealias HBFileIO = FileIO -@_documentation(visibility: internal) @available(*, deprecated, renamed: "BaseRequestContext") -public typealias HBBaseRequestContext = BaseRequestContext +@_documentation(visibility: internal) @available(*, deprecated, renamed: "RequestContext") +public typealias HBBaseRequestContext = RequestContext @_documentation(visibility: internal) @available(*, deprecated, renamed: "BasicRequestContext") public typealias HBBasicRequestContext = BasicRequestContext @_documentation(visibility: internal) @available(*, deprecated, renamed: "CoreRequestContext") diff --git a/Sources/Hummingbird/Files/FileIO.swift b/Sources/Hummingbird/Files/FileIO.swift index 8641c4cab..132ad644f 100644 --- a/Sources/Hummingbird/Files/FileIO.swift +++ b/Sources/Hummingbird/Files/FileIO.swift @@ -36,7 +36,7 @@ public struct FileIO: Sendable { /// - context: Context this request is being called in /// - chunkLength: Size of the chunks read from disk and loaded into memory (in bytes). Defaults to the value suggested by `swift-nio`. /// - Returns: Response body - public func loadFile(path: String, context: some BaseRequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) async throws -> ResponseBody { + public func loadFile(path: String, context: some RequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) async throws -> ResponseBody { do { let stat = try await fileIO.lstat(path: path) return self.readFile(path: path, range: 0...numericCast(stat.st_size - 1), context: context, chunkLength: chunkLength) @@ -55,7 +55,7 @@ public struct FileIO: Sendable { /// - context: Context this request is being called in /// - chunkLength: Size of the chunks read from disk and loaded into memory (in bytes). Defaults to the value suggested by `swift-nio`. /// - Returns: Response body plus file size - public func loadFile(path: String, range: ClosedRange, context: some BaseRequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) async throws -> ResponseBody { + public func loadFile(path: String, range: ClosedRange, context: some RequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) async throws -> ResponseBody { do { let stat = try await fileIO.lstat(path: path) let fileRange: ClosedRange = 0...numericCast(stat.st_size - 1) @@ -75,7 +75,7 @@ public struct FileIO: Sendable { public func writeFile( contents: AS, path: String, - context: some BaseRequestContext + context: some RequestContext ) async throws where AS.Element == ByteBuffer { context.logger.debug("[FileIO] PUT", metadata: ["file": .string(path)]) try await self.fileIO.withFileHandle(path: path, mode: .write, flags: .allowFileCreation()) { handle in @@ -94,7 +94,7 @@ public struct FileIO: Sendable { public func writeFile( buffer: ByteBuffer, path: String, - context: some BaseRequestContext + context: some RequestContext ) async throws { context.logger.debug("[FileIO] PUT", metadata: ["file": .string(path)]) try await self.fileIO.withFileHandle(path: path, mode: .write, flags: .allowFileCreation()) { handle in @@ -103,7 +103,7 @@ public struct FileIO: Sendable { } /// Return response body that will read file - func readFile(path: String, range: ClosedRange, context: some BaseRequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) -> ResponseBody { + func readFile(path: String, range: ClosedRange, context: some RequestContext, chunkLength: Int = NonBlockingFileIO.defaultChunkSize) -> ResponseBody { return ResponseBody(contentLength: range.count) { writer in try await self.fileIO.withFileHandle(path: path, mode: .read) { handle in let endOffset = range.endIndex diff --git a/Sources/Hummingbird/Files/FileProvider.swift b/Sources/Hummingbird/Files/FileProvider.swift index c48b546a9..a57dd6076 100644 --- a/Sources/Hummingbird/Files/FileProvider.swift +++ b/Sources/Hummingbird/Files/FileProvider.swift @@ -35,7 +35,7 @@ public protocol FileProvider: Sendable { /// - path: Full path to file /// - context: Request context /// - Returns: Response body - func loadFile(path: String, context: some BaseRequestContext) async throws -> ResponseBody + func loadFile(path: String, context: some RequestContext) async throws -> ResponseBody /// Return a reponse body that will write a partial file body /// - Parameters: @@ -43,5 +43,5 @@ public protocol FileProvider: Sendable { /// - range: Part of file to return /// - context: Request context /// - Returns: Response body - func loadFile(path: String, range: ClosedRange, context: some BaseRequestContext) async throws -> ResponseBody + func loadFile(path: String, range: ClosedRange, context: some RequestContext) async throws -> ResponseBody } diff --git a/Sources/Hummingbird/Files/LocalFileSystem.swift b/Sources/Hummingbird/Files/LocalFileSystem.swift index 042c37958..fd54cfabc 100644 --- a/Sources/Hummingbird/Files/LocalFileSystem.swift +++ b/Sources/Hummingbird/Files/LocalFileSystem.swift @@ -103,7 +103,7 @@ public struct LocalFileSystem: FileProvider { /// - path: Full path to file /// - context: Request context /// - Returns: Response body - public func loadFile(path: String, context: some BaseRequestContext) async throws -> ResponseBody { + public func loadFile(path: String, context: some RequestContext) async throws -> ResponseBody { try await self.fileIO.loadFile(path: path, context: context) } @@ -113,7 +113,7 @@ public struct LocalFileSystem: FileProvider { /// - range: Part of file to return /// - context: Request context /// - Returns: Response body - public func loadFile(path: String, range: ClosedRange, context: some BaseRequestContext) async throws -> ResponseBody { + public func loadFile(path: String, range: ClosedRange, context: some RequestContext) async throws -> ResponseBody { try await self.fileIO.loadFile(path: path, range: range, context: context) } } diff --git a/Sources/Hummingbird/Middleware/CORSMiddleware.swift b/Sources/Hummingbird/Middleware/CORSMiddleware.swift index c401e9231..a073049d9 100644 --- a/Sources/Hummingbird/Middleware/CORSMiddleware.swift +++ b/Sources/Hummingbird/Middleware/CORSMiddleware.swift @@ -21,7 +21,7 @@ import NIOCore /// then return an empty body with all the standard CORS headers otherwise send /// request onto the next handler and when you receive the response add a /// "access-control-allow-origin" header -public struct CORSMiddleware: RouterMiddleware { +public struct CORSMiddleware: RouterMiddleware { /// Defines what origins are allowed public enum AllowOrigin: Sendable { case none diff --git a/Sources/Hummingbird/Middleware/FileMiddleware.swift b/Sources/Hummingbird/Middleware/FileMiddleware.swift index 6a573ea1c..5884d142e 100644 --- a/Sources/Hummingbird/Middleware/FileMiddleware.swift +++ b/Sources/Hummingbird/Middleware/FileMiddleware.swift @@ -41,7 +41,7 @@ public protocol FileMiddlewareFileAttributes { /// "if-modified-since", "if-none-match", "if-range" and 'range" headers. It will output "content-length", /// "modified-date", "eTag", "content-type", "cache-control" and "content-range" headers where /// they are relevant. -public struct FileMiddleware: RouterMiddleware where Provider.FileAttributes: FileMiddlewareFileAttributes { +public struct FileMiddleware: RouterMiddleware where Provider.FileAttributes: FileMiddlewareFileAttributes { let cacheControl: CacheControl let searchForIndexHtml: Bool let fileProvider: Provider diff --git a/Sources/Hummingbird/Middleware/LogRequestMiddleware.swift b/Sources/Hummingbird/Middleware/LogRequestMiddleware.swift index c4f8024bb..bac40b7f3 100644 --- a/Sources/Hummingbird/Middleware/LogRequestMiddleware.swift +++ b/Sources/Hummingbird/Middleware/LogRequestMiddleware.swift @@ -16,7 +16,7 @@ import HTTPTypes import Logging /// Middleware outputting to log for every call to server -public struct LogRequestsMiddleware: RouterMiddleware { +public struct LogRequestsMiddleware: RouterMiddleware { /// Header filter public struct HeaderFilter: Sendable, ExpressibleByArrayLiteral { fileprivate enum _Internal: Sendable { diff --git a/Sources/Hummingbird/Middleware/MetricsMiddleware.swift b/Sources/Hummingbird/Middleware/MetricsMiddleware.swift index a8ee1dbc9..c9ee689f9 100644 --- a/Sources/Hummingbird/Middleware/MetricsMiddleware.swift +++ b/Sources/Hummingbird/Middleware/MetricsMiddleware.swift @@ -19,7 +19,7 @@ import Metrics /// /// Records the number of requests, the request duration and how many errors were thrown. Each metric has additional /// dimensions URI and method. -public struct MetricsMiddleware: RouterMiddleware { +public struct MetricsMiddleware: RouterMiddleware { public init() {} public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { diff --git a/Sources/Hummingbird/Middleware/TracingMiddleware.swift b/Sources/Hummingbird/Middleware/TracingMiddleware.swift index 9a39ded5d..eb834662d 100644 --- a/Sources/Hummingbird/Middleware/TracingMiddleware.swift +++ b/Sources/Hummingbird/Middleware/TracingMiddleware.swift @@ -23,7 +23,7 @@ import Tracing /// You may opt in to recording a specific subset of HTTP request/response header values by passing /// a set of header names. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public struct TracingMiddleware: RouterMiddleware { +public struct TracingMiddleware: RouterMiddleware { private let headerNamesToRecord: Set private let attributes: SpanAttributes? @@ -164,7 +164,7 @@ extension UnsafeTransfer: @unchecked Sendable {} /// /// If you want the TracingMiddleware to record the remote address of requests /// then your request context will need to conform to this protocol -public protocol RemoteAddressRequestContext: BaseRequestContext { +public protocol RemoteAddressRequestContext: RequestContext { /// Connected host address var remoteAddress: SocketAddress? { get } } diff --git a/Sources/Hummingbird/Router/EndpointResponder.swift b/Sources/Hummingbird/Router/EndpointResponder.swift index ef84c7d38..ca203b520 100644 --- a/Sources/Hummingbird/Router/EndpointResponder.swift +++ b/Sources/Hummingbird/Router/EndpointResponder.swift @@ -16,7 +16,7 @@ import HTTPTypes /// Stores endpoint responders for each HTTP method @usableFromInline -struct EndpointResponders: Sendable { +struct EndpointResponders: Sendable { init(path: String) { self.path = path self.methods = [:] diff --git a/Sources/Hummingbird/Router/ResponseGenerator.swift b/Sources/Hummingbird/Router/ResponseGenerator.swift index a1422cff7..e60945b15 100644 --- a/Sources/Hummingbird/Router/ResponseGenerator.swift +++ b/Sources/Hummingbird/Router/ResponseGenerator.swift @@ -19,19 +19,19 @@ import HTTPTypes /// This is used by `Router` to convert handler return values into a `Response`. public protocol ResponseGenerator { /// Generate response based on the request this object came from - func response(from request: Request, context: some BaseRequestContext) throws -> Response + func response(from request: Request, context: some RequestContext) throws -> Response } /// Extend Response to conform to ResponseGenerator extension Response: ResponseGenerator { /// Return self as the response - public func response(from request: Request, context: some BaseRequestContext) -> Response { self } + public func response(from request: Request, context: some RequestContext) -> Response { self } } /// Extend String to conform to ResponseGenerator extension String: ResponseGenerator { /// Generate response holding string - public func response(from request: Request, context: some BaseRequestContext) -> Response { + public func response(from request: Request, context: some RequestContext) -> Response { let buffer = context.allocator.buffer(string: self) return Response( status: .ok, @@ -47,7 +47,7 @@ extension String: ResponseGenerator { /// Extend String to conform to ResponseGenerator extension Substring: ResponseGenerator { /// Generate response holding string - public func response(from request: Request, context: some BaseRequestContext) -> Response { + public func response(from request: Request, context: some RequestContext) -> Response { let buffer = context.allocator.buffer(substring: self) return Response( status: .ok, @@ -63,7 +63,7 @@ extension Substring: ResponseGenerator { /// Extend ByteBuffer to conform to ResponseGenerator extension ByteBuffer: ResponseGenerator { /// Generate response holding bytebuffer - public func response(from request: Request, context: some BaseRequestContext) -> Response { + public func response(from request: Request, context: some RequestContext) -> Response { Response( status: .ok, headers: .defaultHummingbirdHeaders( @@ -78,14 +78,14 @@ extension ByteBuffer: ResponseGenerator { /// Extend HTTPResponse.Status to conform to ResponseGenerator extension HTTPResponse.Status: ResponseGenerator { /// Generate response with this response status code - public func response(from request: Request, context: some BaseRequestContext) -> Response { + public func response(from request: Request, context: some RequestContext) -> Response { Response(status: self, headers: [:], body: .init()) } } /// Extend Optional to conform to ResponseGenerator extension Optional: ResponseGenerator where Wrapped: ResponseGenerator { - public func response(from request: Request, context: some BaseRequestContext) throws -> Response { + public func response(from request: Request, context: some RequestContext) throws -> Response { switch self { case .some(let wrapped): return try wrapped.response(from: request, context: context) @@ -110,7 +110,7 @@ public struct EditedResponse: ResponseGenerator { self.responseGenerator = response } - public func response(from request: Request, context: some BaseRequestContext) throws -> Response { + public func response(from request: Request, context: some RequestContext) throws -> Response { var response = try responseGenerator.response(from: request, context: context) if let status = self.status { response.status = status diff --git a/Sources/Hummingbird/Router/RouteCollection.swift b/Sources/Hummingbird/Router/RouteCollection.swift index fdc50d793..e9d2d109d 100644 --- a/Sources/Hummingbird/Router/RouteCollection.swift +++ b/Sources/Hummingbird/Router/RouteCollection.swift @@ -15,7 +15,7 @@ import HTTPTypes /// Collection of routes -public final class RouteCollection: RouterMethods { +public final class RouteCollection: RouterMethods { /// Initialize RouteCollection public init(context: Context.Type = BasicRequestContext.self) { self.routes = .init() diff --git a/Sources/Hummingbird/Router/Router.swift b/Sources/Hummingbird/Router/Router.swift index c1f85c267..2b1f2baf5 100644 --- a/Sources/Hummingbird/Router/Router.swift +++ b/Sources/Hummingbird/Router/Router.swift @@ -43,7 +43,7 @@ import NIOCore /// Both of these match routes which start with "/user" and the next path segment being anything. /// The second version extracts the path segment out and adds it to `Request.parameters` with the /// key "id". -public final class Router: RouterMethods, HTTPResponderBuilder { +public final class Router: RouterMethods, HTTPResponderBuilder { var trie: RouterPathTrieBuilder> public let middlewares: MiddlewareGroup let options: RouterOptions @@ -99,7 +99,7 @@ public final class Router: RouterMethods, HTTPRespo } /// Responder that return a not found error -struct NotFoundResponder: HTTPResponder { +struct NotFoundResponder: HTTPResponder { func respond(to request: Request, context: Context) throws -> Response { throw HTTPError(.notFound) } diff --git a/Sources/Hummingbird/Router/RouterGroup.swift b/Sources/Hummingbird/Router/RouterGroup.swift index 26b45c298..03ca7ca2c 100644 --- a/Sources/Hummingbird/Router/RouterGroup.swift +++ b/Sources/Hummingbird/Router/RouterGroup.swift @@ -30,7 +30,7 @@ import NIOCore /// .put(":id", use: todoController.update) /// .delete(":id", use: todoController.delete) /// ``` -public struct RouterGroup: RouterMethods { +public struct RouterGroup: RouterMethods { let path: String let router: any RouterMethods let middlewares: MiddlewareGroup diff --git a/Sources/Hummingbird/Router/RouterMethods.swift b/Sources/Hummingbird/Router/RouterMethods.swift index b655c851c..b38d38c23 100644 --- a/Sources/Hummingbird/Router/RouterMethods.swift +++ b/Sources/Hummingbird/Router/RouterMethods.swift @@ -17,7 +17,7 @@ import NIOCore /// Conform to `RouterMethods` to add standard router verb (get, post ...) methods public protocol RouterMethods { - associatedtype Context: BaseRequestContext + associatedtype Context: RequestContext /// Add responder to call when path and method are matched /// diff --git a/Sources/Hummingbird/Router/RouterResponder.swift b/Sources/Hummingbird/Router/RouterResponder.swift index 07257af4b..917a9c8b9 100644 --- a/Sources/Hummingbird/Router/RouterResponder.swift +++ b/Sources/Hummingbird/Router/RouterResponder.swift @@ -14,7 +14,7 @@ import NIOCore -public struct RouterResponder: HTTPResponder { +public struct RouterResponder: HTTPResponder { @usableFromInline let trie: RouterTrie> diff --git a/Sources/Hummingbird/Server/EditedHTTPError.swift b/Sources/Hummingbird/Server/EditedHTTPError.swift index 21b239714..cddb2d82d 100644 --- a/Sources/Hummingbird/Server/EditedHTTPError.swift +++ b/Sources/Hummingbird/Server/EditedHTTPError.swift @@ -21,7 +21,7 @@ struct EditedHTTPError: HTTPResponseError { let headers: HTTPFields let body: ByteBuffer? - init(originalError: Error, additionalHeaders: HTTPFields, context: some BaseRequestContext) { + init(originalError: Error, additionalHeaders: HTTPFields, context: some RequestContext) { if let httpError = originalError as? HTTPResponseError { self.status = httpError.status self.headers = httpError.headers + additionalHeaders diff --git a/Sources/Hummingbird/Server/Request.swift b/Sources/Hummingbird/Server/Request.swift index 130923154..8c5ffde9d 100644 --- a/Sources/Hummingbird/Server/Request.swift +++ b/Sources/Hummingbird/Server/Request.swift @@ -24,13 +24,13 @@ extension Request { /// - Parameter context: Request context /// - Returns: Collated body @_documentation(visibility: internal) @available(*, deprecated, message: "Use Request.collectBody(upTo:) instead") - public mutating func collateBody(context: some BaseRequestContext) async throws -> ByteBuffer { + public mutating func collateBody(context: some RequestContext) async throws -> ByteBuffer { try await self.collectBody(upTo: context.maxUploadSize) } /// Decode request using decoder stored at `Application.decoder`. /// - Parameter type: Type you want to decode to - public func decode(as type: Type.Type, context: some BaseRequestContext) async throws -> Type { + public func decode(as type: Type.Type, context: some RequestContext) async throws -> Type { do { return try await context.requestDecoder.decode(type, from: self, context: context) } catch DecodingError.dataCorrupted(_) { diff --git a/Sources/Hummingbird/Server/RequestContext.swift b/Sources/Hummingbird/Server/RequestContext.swift index 102a9df25..04e0abc64 100644 --- a/Sources/Hummingbird/Server/RequestContext.swift +++ b/Sources/Hummingbird/Server/RequestContext.swift @@ -42,6 +42,18 @@ public protocol RequestContextSource { var logger: Logger { get } } +/// RequestContext source for server applications +public struct ServerRequestContextSource: RequestContextSource { + public init(channel: any Channel, logger: Logger) { + self.channel = channel + self.logger = logger + } + + public let channel: Channel + public let logger: Logger + public var allocator: ByteBufferAllocator { self.channel.allocator } +} + /// Request context values required by Hummingbird itself. public struct CoreRequestContext: Sendable { /// ByteBuffer allocator used by request @@ -66,15 +78,20 @@ public struct CoreRequestContext: Sendable { } } +/// A RequestContext that can be built from some source +public protocol InstantiableRequestContext: Sendable { + associatedtype Source + /// Initialise RequestContext from source + init(source: Source) +} + /// Protocol that all request contexts should conform to. Holds data associated with /// a request. Provides context for request processing -public protocol BaseRequestContext: Sendable { - associatedtype Source: RequestContextSource +public protocol RequestContext: InstantiableRequestContext { + associatedtype Source: RequestContextSource = ServerRequestContextSource associatedtype Decoder: RequestDecoder = JSONDecoder associatedtype Encoder: ResponseEncoder = JSONEncoder - /// Initialise RequestContext from source - init(source: Source) /// Core context var coreContext: CoreRequestContext { get set } /// Maximum upload size allowed for routes that don't stream the request payload. This @@ -86,7 +103,7 @@ public protocol BaseRequestContext: Sendable { var responseEncoder: Encoder { get } } -extension BaseRequestContext { +extension RequestContext { @inlinable public var allocator: ByteBufferAllocator { coreContext.allocator } /// Logger to use with Request @@ -110,7 +127,7 @@ extension BaseRequestContext { public var id: String { self.logger[metadataKey: "hb_id"]!.description } } -extension BaseRequestContext where Decoder == JSONDecoder { +extension RequestContext where Decoder == JSONDecoder { public var requestDecoder: Decoder { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 @@ -118,7 +135,7 @@ extension BaseRequestContext where Decoder == JSONDecoder { } } -extension BaseRequestContext where Encoder == JSONEncoder { +extension RequestContext where Encoder == JSONEncoder { public var responseEncoder: Encoder { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 @@ -126,21 +143,6 @@ extension BaseRequestContext where Encoder == JSONEncoder { } } -/// RequestContext source for server applications -public struct ServerRequestContextSource: RequestContextSource { - public init(channel: any Channel, logger: Logger) { - self.channel = channel - self.logger = logger - } - - public let channel: Channel - public let logger: Logger - public var allocator: ByteBufferAllocator { self.channel.allocator } -} - -/// Protocol for a request context that can be created from a NIO Channel -public protocol RequestContext: BaseRequestContext where Source == ServerRequestContextSource {} - /// Implementation of a basic request context that supports everything the Hummingbird library needs public struct BasicRequestContext: RequestContext { /// core context diff --git a/Sources/HummingbirdRouter/RouterBuilderContext.swift b/Sources/HummingbirdRouter/RouterBuilderContext.swift index 554192adc..96f5853db 100644 --- a/Sources/HummingbirdRouter/RouterBuilderContext.swift +++ b/Sources/HummingbirdRouter/RouterBuilderContext.swift @@ -28,7 +28,7 @@ public struct RouterBuilderContext: Sendable { } /// Protocol that all request contexts used with RouterBuilder should conform to. -public protocol RouterRequestContext: BaseRequestContext { +public protocol RouterRequestContext: RequestContext { var routerContext: RouterBuilderContext { get set } } diff --git a/Sources/HummingbirdTesting/Application+Test.swift b/Sources/HummingbirdTesting/Application+Test.swift index bd30b12b8..3ca7ebc4c 100644 --- a/Sources/HummingbirdTesting/Application+Test.swift +++ b/Sources/HummingbirdTesting/Application+Test.swift @@ -44,7 +44,7 @@ public struct TestingSetup { } /// Extends `ApplicationProtocol` to support testing of applications -extension ApplicationProtocol where Responder.Context: RequestContext { +extension ApplicationProtocol { // MARK: Initialization /// Test `Application` diff --git a/Sources/HummingbirdTesting/RouterTestFramework.swift b/Sources/HummingbirdTesting/RouterTestFramework.swift index d01b10885..52927df61 100644 --- a/Sources/HummingbirdTesting/RouterTestFramework.swift +++ b/Sources/HummingbirdTesting/RouterTestFramework.swift @@ -25,14 +25,14 @@ import NIOPosix import ServiceLifecycle /// Test sending requests directly to router. This does not setup a live server -struct RouterTestFramework: ApplicationTestFramework where Responder.Context: BaseRequestContext { +struct RouterTestFramework: ApplicationTestFramework where Responder.Context: InstantiableRequestContext { let responder: Responder let makeContext: @Sendable (Logger) -> Responder.Context let services: [any Service] let logger: Logger let processesRunBeforeServerStart: [@Sendable () async throws -> Void] - init(app: App) async throws where App.Responder == Responder, Responder.Context: RequestContext { + init(app: App) async throws where App.Responder == Responder, Responder.Context: InstantiableRequestContext { self.responder = try await app.responder self.processesRunBeforeServerStart = app.processesRunBeforeServerStart self.services = app.services diff --git a/Tests/HummingbirdRouterTests/MiddlewareTests.swift b/Tests/HummingbirdRouterTests/MiddlewareTests.swift index 03c036ef2..26d652d1d 100644 --- a/Tests/HummingbirdRouterTests/MiddlewareTests.swift +++ b/Tests/HummingbirdRouterTests/MiddlewareTests.swift @@ -28,7 +28,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddleware() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) response.headers[.middleware] = "TestMiddleware" @@ -50,7 +50,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddlewareOrder() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { let string: String func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) @@ -76,7 +76,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddlewareRunOnce() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) XCTAssertNil(response.headers[.alreadyRun]) @@ -106,7 +106,7 @@ final class MiddlewareTests: XCTestCase { let error: Details } - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { do { return try await next(request, context) @@ -139,7 +139,7 @@ final class MiddlewareTests: XCTestCase { try await self.parentWriter.write(output) } } - struct TransformMiddleware: RouterMiddleware { + struct TransformMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { let response = try await next(request, context) var editedResponse = response diff --git a/Tests/HummingbirdRouterTests/RouterTests.swift b/Tests/HummingbirdRouterTests/RouterTests.swift index 21a154ae7..3e327b95b 100644 --- a/Tests/HummingbirdRouterTests/RouterTests.swift +++ b/Tests/HummingbirdRouterTests/RouterTests.swift @@ -20,7 +20,7 @@ import NIOCore import XCTest final class RouterTests: XCTestCase { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { let output: String init(_ output: String = "TestMiddleware") { @@ -36,7 +36,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath is set func testEndpointPath() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { _ = try await next(request, context) guard let endpointPath = context.endpointPath else { return try await next(request, context) } @@ -61,7 +61,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath is prefixed with a "/" func testEndpointPathPrefix() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { _ = try await next(request, context) guard let endpointPath = context.endpointPath else { return try await next(request, context) } @@ -98,7 +98,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath doesn't have "/" at end func testEndpointPathSuffix() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { guard let endpointPath = context.endpointPath else { return try await next(request, context) } return .init(status: .ok, body: .init(byteBuffer: ByteBuffer(string: endpointPath))) diff --git a/Tests/HummingbirdTests/ApplicationTests.swift b/Tests/HummingbirdTests/ApplicationTests.swift index 305f3eabb..5527039bd 100644 --- a/Tests/HummingbirdTests/ApplicationTests.swift +++ b/Tests/HummingbirdTests/ApplicationTests.swift @@ -300,7 +300,7 @@ final class ApplicationTests: XCTestCase { } func testCollectBody() async throws { - struct CollateMiddleware: RouterMiddleware { + struct CollateMiddleware: RouterMiddleware { public func handle( _ request: Request, context: Context, next: (Request, Context) async throws -> Response @@ -554,6 +554,25 @@ final class ApplicationTests: XCTestCase { } } + /// test we can create an application that accepts a responder with an empty context + func testEmptyRequestContext() async throws { + struct EmptyRequestContext: InstantiableRequestContext { + typealias Source = ServerRequestContextSource + + init(source: Source) {} + } + let app = Application( + responder: CallbackResponder { (_: Request, _: EmptyRequestContext) in + return Response(status: .ok) + } + ) + try await app.test(.live) { client in + try await client.execute(uri: "/hello", method: .get) { response in + XCTAssertEqual(response.status, .ok) + } + } + } + func testHummingbirdServices() async throws { struct MyService: Service { static let started = ManagedAtomic(false) diff --git a/Tests/HummingbirdTests/FileMiddlewareTests.swift b/Tests/HummingbirdTests/FileMiddlewareTests.swift index fc95e614e..2d17268bc 100644 --- a/Tests/HummingbirdTests/FileMiddlewareTests.swift +++ b/Tests/HummingbirdTests/FileMiddlewareTests.swift @@ -350,12 +350,12 @@ class FileMiddlewareTests: XCTestCase { return .init(size: file.readableBytes) } - func loadFile(path: String, context: some BaseRequestContext) async throws -> ResponseBody { + func loadFile(path: String, context: some RequestContext) async throws -> ResponseBody { guard let file = files[path] else { throw HTTPError(.notFound) } return .init(byteBuffer: file) } - func loadFile(path: String, range: ClosedRange, context: some BaseRequestContext) async throws -> ResponseBody { + func loadFile(path: String, range: ClosedRange, context: some RequestContext) async throws -> ResponseBody { guard let file = files[path] else { throw HTTPError(.notFound) } guard let slice = file.getSlice(at: range.lowerBound, length: range.count) else { throw HTTPError(.rangeNotSatisfiable) } return .init(byteBuffer: slice) diff --git a/Tests/HummingbirdTests/MiddlewareTests.swift b/Tests/HummingbirdTests/MiddlewareTests.swift index 82ca8b134..d69f67388 100644 --- a/Tests/HummingbirdTests/MiddlewareTests.swift +++ b/Tests/HummingbirdTests/MiddlewareTests.swift @@ -26,7 +26,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddleware() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) response.headers[.test] = "TestMiddleware" @@ -47,7 +47,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddlewareOrder() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { let string: String public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) @@ -72,7 +72,7 @@ final class MiddlewareTests: XCTestCase { } func testMiddlewareRunOnce() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var response = try await next(request, context) XCTAssertNil(response.headers[.test]) @@ -101,7 +101,7 @@ final class MiddlewareTests: XCTestCase { let error: Details } - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { do { return try await next(request, context) @@ -124,7 +124,7 @@ final class MiddlewareTests: XCTestCase { } func testEndpointPathInGroup() async throws { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { XCTAssertNotNil(context.endpointPath) return try await next(request, context) @@ -153,7 +153,7 @@ final class MiddlewareTests: XCTestCase { try await self.parentWriter.write(output) } } - struct TransformMiddleware: RouterMiddleware { + struct TransformMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { let response = try await next(request, context) var editedResponse = response diff --git a/Tests/HummingbirdTests/RouterTests.swift b/Tests/HummingbirdTests/RouterTests.swift index 3b57dccdf..6096732e4 100644 --- a/Tests/HummingbirdTests/RouterTests.swift +++ b/Tests/HummingbirdTests/RouterTests.swift @@ -21,7 +21,7 @@ import Tracing import XCTest final class RouterTests: XCTestCase { - struct TestMiddleware: RouterMiddleware { + struct TestMiddleware: RouterMiddleware { let output: String init(_ output: String = "TestMiddleware") { @@ -37,7 +37,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath is set func testEndpointPath() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { guard let endpointPath = context.endpointPath else { return try await next(request, context) } return .init(status: .ok, body: .init(byteBuffer: ByteBuffer(string: endpointPath))) @@ -58,7 +58,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath is prefixed with a "/" func testEndpointPathPrefix() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { guard let endpointPath = context.endpointPath else { return try await next(request, context) } return .init(status: .ok, body: .init(byteBuffer: ByteBuffer(string: endpointPath))) @@ -93,7 +93,7 @@ final class RouterTests: XCTestCase { /// Test endpointPath doesn't have "/" at end func testEndpointPathSuffix() async throws { - struct TestEndpointMiddleware: RouterMiddleware { + struct TestEndpointMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { guard let endpointPath = context.endpointPath else { return try await next(request, context) } return .init(status: .ok, body: .init(byteBuffer: ByteBuffer(string: endpointPath))) diff --git a/Tests/HummingbirdTests/TracingTests.swift b/Tests/HummingbirdTests/TracingTests.swift index d951bd2ad..d53868b75 100644 --- a/Tests/HummingbirdTests/TracingTests.swift +++ b/Tests/HummingbirdTests/TracingTests.swift @@ -268,7 +268,7 @@ final class TracingTests: XCTestCase { /// Test span is ended even if the response body with the span end is not run func testTracingMiddlewareDropResponse() async throws { let expectation = expectation(description: "Expected span to be ended.") - struct ErrorMiddleware: RouterMiddleware { + struct ErrorMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { _ = try await next(request, context) throw HTTPError(.badRequest) @@ -412,7 +412,7 @@ final class TracingTests: XCTestCase { let expectation = expectation(description: "Expected span to be ended.") expectation.expectedFulfillmentCount = 2 - struct SpanMiddleware: RouterMiddleware { + struct SpanMiddleware: RouterMiddleware { public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { var serviceContext = ServiceContext.current ?? ServiceContext.topLevel serviceContext.testID = "testMiddleware" From b2fdf6cfa3b48cb9c4190c3a8bfe528c405ab561 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 15 Jun 2024 10:41:03 +0100 Subject: [PATCH 4/4] Rename CoreRequestContext to CoreRequestContextStorage (#473) --- Sources/Hummingbird/Deprecations.swift | 6 ++++-- Sources/Hummingbird/Server/RequestContext.swift | 6 +++--- Sources/HummingbirdRouter/RouterBuilderContext.swift | 2 +- Tests/HummingbirdRouterTests/RouterTests.swift | 2 +- Tests/HummingbirdTests/ApplicationTests.swift | 6 +++--- Tests/HummingbirdTests/RouterTests.swift | 2 +- .../URLEncodedForm/Application+URLEncodedFormTests.swift | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Sources/Hummingbird/Deprecations.swift b/Sources/Hummingbird/Deprecations.swift index 189605a60..44d38bde1 100644 --- a/Sources/Hummingbird/Deprecations.swift +++ b/Sources/Hummingbird/Deprecations.swift @@ -33,8 +33,10 @@ public typealias HBFileIO = FileIO public typealias HBBaseRequestContext = RequestContext @_documentation(visibility: internal) @available(*, deprecated, renamed: "BasicRequestContext") public typealias HBBasicRequestContext = BasicRequestContext -@_documentation(visibility: internal) @available(*, deprecated, renamed: "CoreRequestContext") -public typealias HBCoreRequestContext = CoreRequestContext +@_documentation(visibility: internal) @available(*, deprecated, renamed: "CoreRequestContextStorage") +public typealias HBCoreRequestContext = CoreRequestContextStorage +@_documentation(visibility: internal) @available(*, deprecated, renamed: "CoreRequestContextStorage") +public typealias CoreRequestContext = CoreRequestContextStorage @_documentation(visibility: internal) @available(*, deprecated, renamed: "RequestContext") public typealias HBRequestContext = RequestContext @_documentation(visibility: internal) @available(*, deprecated, renamed: "RequestDecoder") diff --git a/Sources/Hummingbird/Server/RequestContext.swift b/Sources/Hummingbird/Server/RequestContext.swift index 04e0abc64..d680ea064 100644 --- a/Sources/Hummingbird/Server/RequestContext.swift +++ b/Sources/Hummingbird/Server/RequestContext.swift @@ -55,7 +55,7 @@ public struct ServerRequestContextSource: RequestContextSource { } /// Request context values required by Hummingbird itself. -public struct CoreRequestContext: Sendable { +public struct CoreRequestContextStorage: Sendable { /// ByteBuffer allocator used by request @usableFromInline let allocator: ByteBufferAllocator @@ -93,7 +93,7 @@ public protocol RequestContext: InstantiableRequestContext { associatedtype Encoder: ResponseEncoder = JSONEncoder /// Core context - var coreContext: CoreRequestContext { get set } + var coreContext: CoreRequestContextStorage { 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 } @@ -146,7 +146,7 @@ extension RequestContext where Encoder == JSONEncoder { /// Implementation of a basic request context that supports everything the Hummingbird library needs public struct BasicRequestContext: RequestContext { /// core context - public var coreContext: CoreRequestContext + public var coreContext: CoreRequestContextStorage /// Initialize an `RequestContext` /// - Parameters: diff --git a/Sources/HummingbirdRouter/RouterBuilderContext.swift b/Sources/HummingbirdRouter/RouterBuilderContext.swift index 96f5853db..a70cc378b 100644 --- a/Sources/HummingbirdRouter/RouterBuilderContext.swift +++ b/Sources/HummingbirdRouter/RouterBuilderContext.swift @@ -35,7 +35,7 @@ public protocol RouterRequestContext: RequestContext { /// Basic implementation of a context that can be used with `RouterBuilder`` public struct BasicRouterRequestContext: RequestContext, RouterRequestContext { public var routerContext: RouterBuilderContext - public var coreContext: CoreRequestContext + public var coreContext: CoreRequestContextStorage public init(source: Source) { self.coreContext = .init(source: source) diff --git a/Tests/HummingbirdRouterTests/RouterTests.swift b/Tests/HummingbirdRouterTests/RouterTests.swift index 3e327b95b..1b998c6b5 100644 --- a/Tests/HummingbirdRouterTests/RouterTests.swift +++ b/Tests/HummingbirdRouterTests/RouterTests.swift @@ -449,7 +449,7 @@ public struct TestRouterContext2: RouterRequestContext, RequestContext { /// router context public var routerContext: RouterBuilderContext /// core context - public var coreContext: CoreRequestContext + public var coreContext: CoreRequestContextStorage /// additional data public var string: String diff --git a/Tests/HummingbirdTests/ApplicationTests.swift b/Tests/HummingbirdTests/ApplicationTests.swift index 5527039bd..5aea8f628 100644 --- a/Tests/HummingbirdTests/ApplicationTests.swift +++ b/Tests/HummingbirdTests/ApplicationTests.swift @@ -372,7 +372,7 @@ final class ApplicationTests: XCTestCase { func testOptionalCodable() async throws { struct SortedJSONRequestContext: RequestContext { - var coreContext: CoreRequestContext + var coreContext: CoreRequestContextStorage var responseEncoder: JSONEncoder { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys @@ -453,7 +453,7 @@ final class ApplicationTests: XCTestCase { self.coreContext = .init(source: source) } - var coreContext: CoreRequestContext + var coreContext: CoreRequestContextStorage var maxUploadSize: Int { 64 * 1024 } } let router = Router(context: MaxUploadRequestContext.self) @@ -482,7 +482,7 @@ final class ApplicationTests: XCTestCase { /// Implementation of a basic request context that supports everything the Hummingbird library needs struct SocketAddressRequestContext: RequestContext { /// core context - var coreContext: CoreRequestContext + var coreContext: CoreRequestContextStorage // socket address let remoteAddress: SocketAddress? diff --git a/Tests/HummingbirdTests/RouterTests.swift b/Tests/HummingbirdTests/RouterTests.swift index 6096732e4..39d9e3da3 100644 --- a/Tests/HummingbirdTests/RouterTests.swift +++ b/Tests/HummingbirdTests/RouterTests.swift @@ -609,7 +609,7 @@ struct TestRouterContext2: RequestContext { } /// parameters - var coreContext: CoreRequestContext + var coreContext: CoreRequestContextStorage /// additional data var string: String diff --git a/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift b/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift index c9357058c..4245ba3b0 100644 --- a/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift +++ b/Tests/HummingbirdTests/URLEncodedForm/Application+URLEncodedFormTests.swift @@ -26,7 +26,7 @@ class HummingBirdURLEncodedTests: XCTestCase { } struct URLEncodedCodingRequestContext: RequestContext { - var coreContext: CoreRequestContext + var coreContext: CoreRequestContextStorage init(source: Source) { self.coreContext = .init(source: source)