Skip to content

[Runtime] Async bodies + swift-http-types adoption #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 60 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
fd47bd2
[WIP] Async body currency type
czechboy0 Sep 1, 2023
beca630
Rename Body to HTTPBody to avoid confusion with the generated Body enums
czechboy0 Sep 4, 2023
34f7c59
[WIP] Adopt swift-http-types
czechboy0 Sep 4, 2023
d046bf9
WIP
czechboy0 Sep 4, 2023
164f84e
wip
czechboy0 Sep 5, 2023
3db782e
Remove mapChunks, rename initializer parameters
czechboy0 Sep 5, 2023
6c97d48
wip
czechboy0 Sep 5, 2023
466b701
Remove String coder, fix up client unit tests
czechboy0 Sep 5, 2023
00ec189
Got the other tests working
czechboy0 Sep 5, 2023
fe08164
Just use Substring in more places
czechboy0 Sep 5, 2023
63f05d6
wip
czechboy0 Sep 6, 2023
982fe13
wip
czechboy0 Sep 6, 2023
1317631
Added more docs
czechboy0 Sep 6, 2023
faba5ce
Add back docs
czechboy0 Sep 6, 2023
6a71613
Improve loggability of the currency types
czechboy0 Sep 7, 2023
1c07795
Bring back comments on ServerError
czechboy0 Sep 7, 2023
2540b77
WIP
czechboy0 Sep 7, 2023
a165558
Add comments back
czechboy0 Sep 7, 2023
c4186f8
Prefix extensions on types we don't own
czechboy0 Sep 7, 2023
5e1d21e
WIP on documentation
czechboy0 Sep 7, 2023
0ee065f
Add docs for HTTPBody
czechboy0 Sep 7, 2023
99d8252
Add doc comments
czechboy0 Sep 7, 2023
d85eb55
More docs
czechboy0 Sep 7, 2023
844be3a
Merge branch 'main' into hd-adopt-http-types
czechboy0 Sep 7, 2023
1406537
Fixes
czechboy0 Sep 7, 2023
84361a8
Make iterator next() method mutating
czechboy0 Sep 8, 2023
9f10325
Represent no responses as a nil HTTPBody in server transport and midd…
czechboy0 Sep 8, 2023
dbda450
Feedback: make the AsyncSequences Sendable, which adds the requiremen…
czechboy0 Sep 12, 2023
fbe8eea
Feedback: further cleanup of the HTTPBody API
czechboy0 Sep 12, 2023
8b432b2
Remove unnecessary initializers
czechboy0 Sep 12, 2023
3f59f44
Review feedback:
czechboy0 Sep 13, 2023
16917ce
Review feedback: make response body optional
czechboy0 Sep 13, 2023
4830821
A few more changes
czechboy0 Sep 13, 2023
213addf
Wording and a refactor fix
czechboy0 Sep 14, 2023
a5e22b6
Merge branch 'main' into hd-adopt-http-types
czechboy0 Sep 18, 2023
c314914
Fix a few more missing sendable annotations
czechboy0 Sep 18, 2023
0cf76f2
Update docs
czechboy0 Sep 18, 2023
af10f7d
Update HTTPBody.swift
czechboy0 Sep 18, 2023
b2fb79e
Update HTTPBody.swift
czechboy0 Sep 18, 2023
1778209
Update HTTPBody.swift
czechboy0 Sep 18, 2023
b58a3b6
Update HTTPBody.swift
czechboy0 Sep 18, 2023
b2d4ec3
Update HTTPBody.swift
czechboy0 Sep 18, 2023
66b602f
Update HTTPBody.swift
czechboy0 Sep 18, 2023
b0ccaae
Update ServerTransport.swift
czechboy0 Sep 18, 2023
3f5b3d3
Merge remote-tracking branch 'apple/main' into hd-adopt-http-types
czechboy0 Sep 25, 2023
a129b6b
Fixing up the merge from main
czechboy0 Sep 25, 2023
a3516b7
Merge branch 'hd-adopt-http-types' of github.com:czechboy0/swift-open…
czechboy0 Sep 25, 2023
f5d8c1c
Merge branch 'main' into hd-adopt-http-types
czechboy0 Sep 25, 2023
a89bced
Use swift-http-types 1.0
czechboy0 Sep 25, 2023
b19a787
Provide a closure for accessing the locked value HTTPBody.iteratorCre…
czechboy0 Sep 25, 2023
481e6bb
Fix up encoding comment
czechboy0 Sep 25, 2023
dc209d3
Update Sources/OpenAPIRuntime/Interface/HTTPBody.swift
czechboy0 Sep 25, 2023
05082f9
Update Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift
czechboy0 Sep 25, 2023
03a9f2d
Update Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift
czechboy0 Sep 26, 2023
ed46fa5
Explicitly test substring support
czechboy0 Sep 26, 2023
669a7f3
Add a test case with an unknown length
czechboy0 Sep 26, 2023
72d241d
PR feedback: improve the iteration behavior checking in HTTPBody, ren…
czechboy0 Sep 26, 2023
0704673
Added a test for the collect method based on a known length
czechboy0 Sep 26, 2023
492cf0c
Move extensions of http types into the generated SPI
czechboy0 Sep 26, 2023
24f8870
Update version in docs to 0.3.0
czechboy0 Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add doc comments
  • Loading branch information
czechboy0 committed Sep 7, 2023
commit 99d8252fb14c6fbdd585d510b71dcd23b5f91f47
20 changes: 10 additions & 10 deletions Sources/OpenAPIRuntime/Errors/ServerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ public struct ServerError: Error {
/// Identifier of the operation that threw the error.
public var operationID: String

/// HTTP request provided to the server.
/// The HTTP request provided to the server.
public var request: HTTPRequest

/// HTTP request body provided to the server.
/// The HTTP request body provided to the server.
public var requestBody: HTTPBody?

/// Request metadata extracted by the server.
/// The request metadata extracted by the server.
public var metadata: ServerRequestMetadata

/// Operation-specific Input value.
/// An operation-specific Input value.
///
/// Is nil if error was thrown during request -> Input conversion.
public var operationInput: (any Sendable)?

/// Operation-specific Output value.
/// An operation-specific Output value.
///
/// Is nil if error was thrown before/during Output -> response conversion.
public var operationOutput: (any Sendable)?
Expand All @@ -46,11 +46,11 @@ public struct ServerError: Error {
/// Creates a new error.
/// - Parameters:
/// - operationID: The OpenAPI operation identifier.
/// - request: HTTP request provided to the server.
/// - requestBody: HTTP request body provided to the server.
/// - metadata: Request metadata extracted by the server.
/// - operationInput: Operation-specific Input value.
/// - operationOutput: Operation-specific Output value.
/// - request: The HTTP request provided to the server.
/// - requestBody: The HTTP request body provided to the server.
/// - metadata: The request metadata extracted by the server.
/// - operationInput: An operation-specific Input value.
/// - operationOutput: An operation-specific Output value.
/// - underlyingError: The underlying error that caused the operation
/// to fail.
public init(
Expand Down
6 changes: 3 additions & 3 deletions Sources/OpenAPIRuntime/Interface/ClientTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public protocol ClientTransport: Sendable {
/// HTTP response.
/// - Parameters:
/// - request: An HTTP request.
/// - body: HTTP request body.
/// - body: An HTTP request body.
/// - baseURL: A server base URL.
/// - operationID: The identifier of the OpenAPI operation.
/// - Returns: An HTTP response and its body.
Expand Down Expand Up @@ -236,8 +236,8 @@ public protocol ClientMiddleware: Sendable {
/// Intercepts an outgoing HTTP request and an incoming HTTP response.
/// - Parameters:
/// - request: An HTTP request.
/// - body: HTTP request body.
/// - baseURL: baseURL: A server base URL.
/// - body: An HTTP request body.
/// - baseURL: A server base URL.
/// - operationID: The identifier of the OpenAPI operation.
/// - next: A closure that calls the next middleware, or the transport.
/// - Returns: An HTTP response and its body.
Expand Down
191 changes: 191 additions & 0 deletions Sources/OpenAPIRuntime/Interface/ServerTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,105 @@

import HTTPTypes

/// A type that registers and handles HTTP operations.
///
/// Decouples the HTTP server framework from the generated server code.
///
/// ### Choose between a transport and a middleware
///
/// The ``ServerTransport`` and ``ServerMiddleware`` protocols look similar,
/// however each serves a different purpose.
///
/// A _transport_ abstracts over the underlying HTTP library that actually
/// receives the HTTP requests from the network. An implemented _handler_
/// (a type implemented by you that conforms to the generated `APIProtocol`
/// protocol) is generally configured with exactly one server transport.
///
/// A _middleware_ intercepts the HTTP request and response, without being
/// responsible for receiving the HTTP operations itself. That's why
/// middlewares take the extra `next` parameter, to delegate calling the handler
/// to the transport at the top of the middleware stack.
///
/// ### Use an existing server transport
///
/// Instantiate the transport using the parameters required by the specific
/// implementation. For example, using the server transport for the
/// `Vapor` web framework, first create the `Application` object provided by
/// Vapor, and provided it to the initializer of `VaporTransport`:
///
/// let app = Vapor.Application()
/// let transport = VaporTransport(routesBuilder: app)
///
/// Implement a new type that conforms to the generated `APIProtocol`, which
/// serves as the request handler of your server's business logic. For example,
/// this is what a simple implementation of a server that has a single
/// HTTP operation called `checkHealth` defined in the OpenAPI document, and
/// it always returns the 200 HTTP status code:
///
/// struct MyAPIImplementation: APIProtocol {
/// func checkHealth(
/// _ input: Operations.checkHealth.Input
/// ) async throws -> Operations.checkHealth.Output {
/// .ok(.init())
/// }
/// }
///
/// The generated operation method takes an `Input` type unique to
/// the operation, and returns an `Output` type unique to the operation.
///
/// > Note: You use the `Input` type to provide parameters such as HTTP request
/// headers, query items, path parameters, and request bodies; and inspect
/// the `Output` type to handle the received HTTP response status code,
/// response header and body.
///
/// Create an instance of your handler:
///
/// let handler = MyAPIImplementation()
///
/// Create the URL where the server will run. The path of the URL is extracted
/// by the transport to create a common prefix (such as `/api/v1`) that might
/// be expected by the clients. If the server URL is defined in the OpenAPI
/// document, find the generated method for it on the `Servers` type,
/// for example:
///
/// let serverURL = try Servers.server1()
///
/// Register the generated request handlers by calling the method generated
/// on the `APIProtocol` protocol:
///
/// try handler.registerHandlers(on: transport, serverURL: serverURL)
///
/// Start the server by following the documentation of your chosen transport:
///
/// try app.run()
///
/// ### Implement a custom server transport
///
/// If a server transport implementation for your preferred web framework
/// doesn't yet exist, or you need to simulate rare network conditions in
/// your tests, consider implementing a custom server transport.
///
/// Define a new type that conforms to the `ServerTransport` protocol by
/// registering request handlers with the underlying web framework, to be
/// later called when the web framework receives an HTTP request to one
/// of the HTTP routes.
///
/// In tests, this might require using the web framework's specific test
/// APIs to allow for simulating incoming HTTP requests.
///
/// Implementing a test server transport is just one way to help test your
/// code that integrates with your handler. Another is to implement
/// a type conforming to the generated protocol `APIProtocol`, and to implement
/// a custom ``ServerMiddleware``.
public protocol ServerTransport {

/// Registers an HTTP operation handler at the provided path and method.
/// - Parameters:
/// - handler: A handler to be invoked when an HTTP request is received.
/// - method: An HTTP request method.
/// - path: A URL template for the path, for example `/pets/{petId}`.
/// - Important: The `path` can have mixed component, such
/// as `/file/{name}.zip`.
func register(
_ handler: @Sendable @escaping (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
HTTPResponse, HTTPBody
Expand All @@ -25,8 +122,102 @@ public protocol ServerTransport {
) throws
}

/// A type that intercepts HTTP requests and responses.
///
/// It allows you to customize the request after it was provided by
/// the transport, but before it was parsed, validated, and provided to
/// the request handler; and the response after it was provided by the request
/// handler, but before it was handed back to the transport.
///
/// Appropriate for verifying authentication, performing logging, metrics,
/// tracing, injecting custom headers such as "user-agent", and more.
///
/// ### Choose between a transport and a middleware
///
/// The ``ServerTransport`` and ``ServerMiddleware`` protocols look similar,
/// however each serves a different purpose.
///
/// A _transport_ abstracts over the underlying HTTP library that actually
/// receives the HTTP requests from the network. An implemented _handler_
/// (a type implemented by you that conforms to the generated `APIProtocol`
/// protocol) is generally configured with exactly one server transport.
///
/// A _middleware_ intercepts the HTTP request and response, without being
/// responsible for receiving the HTTP operations itself. That's why
/// middlewares take the extra `next` parameter, to delegate calling the handler
/// to the transport at the top of the middleware stack.
///
/// ### Use an existing server middleware
///
/// Instantiate the middleware using the parameters required by the specific
/// implementation. For example, using a hypothetical existing middleware
/// that logs every request and response:
///
/// let loggingMiddleware = LoggingMiddleware()
///
/// Similarly to the process of using an existing ``ServerTransport``, provide
/// the middleware to the call to register handlers:
///
/// try handler.registerHandlers(
/// on: transport,
/// serverURL: serverURL,
/// middlewares: [
/// loggingMiddleware,
/// ]
/// )
///
/// Then when an HTTP request is received, the server first invokes
/// the middlewares in the order you provided them, and then passes
/// the parsed request to your handler. When a response is received from
/// the handler, the last middleware handles the response first, and it goes
/// back in the reverse order of the `middlewares` array. At the end,
/// the transport sends the final response back to the client.
///
/// ### Implement a custom server middleware
///
/// If a server middleware implementation with your desired behavior doesn't
/// yet exist, or you need to simulate rare requests in your tests,
/// consider implementing a custom server middleware.
///
/// For example, an implementation a middleware that prints only basic
/// information about the incoming request and outgoing response:
///
/// /// A middleware that prints request and response metadata.
/// struct PrintingMiddleware: ServerMiddleware {
/// func intercept(
/// _ request: HTTPRequest,
/// body: HTTPBody?,
/// metadata: ServerRequestMetadata,
/// operationID: String,
/// next: (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody)
/// ) async throws -> (HTTPResponse, HTTPBody) {
/// print(">>>: \(request.method.rawValue) \(request.soar_pathOnly)")
/// do {
/// let (response, responseBody) = try await next(request, body, metadata)
/// print("<<<: \(response.status.code)")
/// return (response, responseBody)
/// } catch {
/// print("!!!: \(error.localizedDescription)")
/// throw error
/// }
/// }
/// }
///
/// Implementing a test server middleware is just one way to help test your
/// code that integrates with your handler. Another is to implement
/// a type conforming to the generated protocol `APIProtocol`, and to implement
/// a custom ``ServerTransport``.
public protocol ServerMiddleware: Sendable {

/// Intercepts an incoming HTTP request and an outgoing HTTP response.
/// - Parameters:
/// - request: An HTTP request.
/// - body: An HTTP request body.
/// - metadata: The metadata parsed from the HTTP request, including path
/// parameters.
/// - operationID: The identifier of the OpenAPI operation.
/// - next: A closure that calls the next middleware, or the transport.
/// - Returns: An HTTP response and its body.
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
Expand Down