Skip to content

Commit 3a49f47

Browse files
committed
add support for middleware
1 parent be478cc commit 3a49f47

File tree

4 files changed

+151
-2
lines changed

4 files changed

+151
-2
lines changed

Examples/quoteapi/Sources/QuoteAPI/QuoteService.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ import ServiceLifecycle
2323
@main
2424
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
2525

26+
let logger: Logger
27+
2628
func register(transport: OpenAPILambdaTransport) throws {
27-
try self.registerHandlers(on: transport)
29+
30+
31+
let loggingMiddleware = LoggingMiddleware(logger: logger)
32+
try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
2833

2934
// you have a chance here to customize the routes, for example
3035
try transport.router.get("/health") { _, _ in
3136
"OK"
3237
}
38+
39+
print(transport.router)
3340
}
3441

3542
static func main() async throws {
@@ -82,6 +89,9 @@ struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
8289
let i: Int
8390
init(i: Int) {
8491
self.i = i
92+
var logger = Logger(label: "QuoteService")
93+
logger.logLevel = .trace
94+
self.logger = logger
8595
}
8696

8797
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenAPI Lambda open source project
4+
//
5+
// Copyright (c) 2023 Amazon.com, Inc. or its affiliates
6+
// and the Swift OpenAPI Lambda project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of Swift OpenAPI Lambda project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
//===----------------------------------------------------------------------===//
17+
//
18+
// This source file is part of the SwiftOpenAPIGenerator open source project
19+
//
20+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
21+
// Licensed under Apache License v2.0
22+
//
23+
// See LICENSE.txt for license information
24+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
25+
//
26+
// SPDX-License-Identifier: Apache-2.0
27+
//
28+
//===----------------------------------------------------------------------===//
29+
import OpenAPIRuntime
30+
import HTTPTypes
31+
32+
/// A server middleware that authenticates the incoming user based on the value of
33+
/// the `Authorization` header field and injects the identifier `User` information
34+
/// into a task local value, allowing the request handler to use it.
35+
package struct AuthenticationServerMiddleware: Sendable {
36+
37+
/// Information about an authenticated user.
38+
package struct User: Hashable {
39+
40+
/// The name of the authenticated user.
41+
package var name: String
42+
43+
/// Creates a new user.
44+
/// - Parameter name: The name of the authenticated user.
45+
package init(name: String) { self.name = name }
46+
47+
/// The task local value of the currently authenticated user.
48+
@TaskLocal package static var current: User?
49+
}
50+
51+
/// The closure that authenticates the user based on the value of the `Authorization`
52+
/// header field.
53+
private let authenticate: @Sendable (String) -> User?
54+
55+
/// Creates a new middleware.
56+
/// - Parameter authenticate: The closure that authenticates the user based on the value
57+
/// of the `Authorization` header field.
58+
package init(authenticate: @Sendable @escaping (String) -> User?) { self.authenticate = authenticate }
59+
}
60+
61+
extension AuthenticationServerMiddleware: ServerMiddleware {
62+
package func intercept(
63+
_ request: HTTPRequest,
64+
body: HTTPBody?,
65+
metadata: ServerRequestMetadata,
66+
operationID: String,
67+
next: @Sendable (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody?)
68+
) async throws -> (HTTPResponse, HTTPBody?) {
69+
// Extracts the `Authorization` value, if present.
70+
// If no `Authorization` header field value was provided, no User is injected into
71+
// the task local.
72+
guard let authorizationHeaderFieldValue = request.headerFields[.authorization] else {
73+
return try await next(request, body, metadata)
74+
}
75+
// Delegate the authentication logic to the closure.
76+
let user = authenticate(authorizationHeaderFieldValue)
77+
// Inject the authenticated user into the task local and call the next middleware.
78+
return try await User.$current.withValue(user) { try await next(request, body, metadata) }
79+
}
80+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenAPI Lambda open source project
4+
//
5+
// Copyright (c) 2023 Amazon.com, Inc. or its affiliates
6+
// and the Swift OpenAPI Lambda project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of Swift OpenAPI Lambda project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
import HTTPTypes
17+
import Logging
18+
import OpenAPIRuntime
19+
20+
/// A middleware that logs request and response metadata.
21+
/// Only active at .trace level
22+
public struct LoggingMiddleware: ServerMiddleware {
23+
24+
private let logger: Logger
25+
public init(logger: Logger) {
26+
self.logger = logger
27+
}
28+
29+
public func intercept(
30+
_ request: HTTPRequest,
31+
body: HTTPBody?,
32+
metadata: ServerRequestMetadata,
33+
operationID: String,
34+
next: (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody?)
35+
) async throws -> (HTTPResponse, HTTPBody?) {
36+
37+
logger.trace(">>>: \(request.method.rawValue) \(request.path ?? "")")
38+
do {
39+
let (response, responseBody) = try await next(request, body, metadata)
40+
logger.trace("<<<: \(response.status.code)")
41+
return (response, responseBody)
42+
} catch {
43+
logger.trace("!!!: \(error.localizedDescription)")
44+
throw error
45+
}
46+
}
47+
48+
}

Sources/OpenAPILambdaService.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public protocol OpenAPILambdaService: Sendable {
2727
/// Injects the transport.
2828
///
2929
/// This is where your OpenAPILambdaService implementation must register the transport
30-
func register(transport: OpenAPILambdaTransport) throws
30+
func register(transport: OpenAPILambdaTransport, middleware: [ServerMiddleware]) throws
3131

3232
/// Convert from `Event` type to `OpenAPILambdaRequest`
3333
/// - Parameters:
@@ -54,3 +54,14 @@ extension OpenAPILambdaService {
5454
try await lambdaRuntime.run()
5555
}
5656
}
57+
58+
extension OpenAPILambdaService {
59+
60+
/// Injects the transport.
61+
/// This is a convenience method that provides an empty middleware list by default
62+
///
63+
/// This is where your OpenAPILambdaService implementation must register the transport
64+
public func register(transport: OpenAPILambdaTransport, middleware: [ServerMiddleware] = []) throws {
65+
try register(transport: transport, middleware: middleware)
66+
}
67+
}

0 commit comments

Comments
 (0)