Skip to content

Commit 46639ef

Browse files
committed
Make teh router and the Lambda service sendable
1 parent 8d3e822 commit 46639ef

File tree

8 files changed

+87
-26
lines changed

8 files changed

+87
-26
lines changed

.swift-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"UseLetInEveryBoundCaseVariable" : false,
5353
"UseShorthandTypeNames" : true,
5454
"UseSingleLinePropertyGetter" : false,
55-
"UseSynthesizedInitializer" : true,
55+
"UseSynthesizedInitializer" : false,
5656
"UseWhereClausesInForLoops" : false
5757
},
5858
"spacesAroundRangeFormationOperators" : false,

Examples/quoteapi/Sources/QuoteAPI/QuoteService.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ import OpenAPILambda
2121
@main
2222
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
2323

24-
init(transport: OpenAPILambdaTransport) throws {
24+
func register(transport: OpenAPILambdaTransport) throws {
2525
try self.registerHandlers(on: transport)
2626
}
2727

2828
static func main() async throws {
2929

3030
// when you just need to run the Lambda function, call Self.run()
31-
try await Self.run()
31+
// try await Self.run()
32+
33+
// =============================
3234

3335
// when you need to have access to the runtime for advanced usage (dependency injection, Service LifeCycle, etc)
3436
// create the LambdaRuntime and run it
@@ -49,8 +51,29 @@ struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
4951
// logger: Logger(label: "ServiceGroup")
5052
// )
5153
// try await serviceGroup.run()
54+
55+
// =============================
56+
57+
// When you need full control on this struct lifecycle, for example to inject dependencies,
58+
// you can create your own instance of `OpenAPILambda`
59+
// 1. add your custom init() function
60+
// 2. create the class
61+
// 3. create the lambda handler and pass this class to the initializer
62+
63+
// let openAPIService = QuoteServiceImpl(i: 42)
64+
// let lambda = try OpenAPILambdaHandler(service: openAPIService)
65+
// let lambdaHandler = lambda.handler
66+
// let lambdaRuntime = LambdaRuntime(body: lambdaHandler)
67+
// try await lambdaRuntime.run()
5268
}
5369

70+
// example of dependency injection when using a custom initializer
71+
// let i: Int
72+
// init(i: Int) {
73+
// self.i = i
74+
// }
75+
// init() { self.i = 0 } // to comply with protocol
76+
5477
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
5578

5679
let symbol = input.path.symbol

Sources/HttpApi/OpenAPILambdaHttpApi.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public enum OpenAPILambdaHttpError: Error {
2424
}
2525

2626
/// An specialization of the `OpenAPILambda` protocol that works with Amazon API Gateway HTTP Mode, aka API Gateway v2
27-
public protocol OpenAPILambdaHttpApi: OpenAPILambda
27+
public protocol OpenAPILambdaHttpApi: OpenAPILambdaService
2828
where
2929
Event == APIGatewayV2Request,
3030
Output == APIGatewayV2Response

Sources/OpenAPILambda.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@ import OpenAPIRuntime
1919
import HTTPTypes
2020

2121
/// A Lambda function implemented with a OpenAPI server (implementing `APIProtocol` from Swift OpenAPIRuntime)
22-
public protocol OpenAPILambda: Sendable {
22+
public protocol OpenAPILambdaService: Sendable {
2323

2424
associatedtype Event: Decodable, Sendable
2525
associatedtype Output: Encodable, Sendable
2626

27-
/// Initialize application.
27+
/// Create a new instance of the OpenAPILambdaService.
28+
init()
29+
30+
/// Injects the transport.
2831
///
29-
/// This is where you create your OpenAPI service implementation and register the transport
30-
init(transport: OpenAPILambdaTransport) throws
32+
/// This is where your OpenAPILambdaService implementation must register the transport
33+
func register(transport: OpenAPILambdaTransport) throws
3134

3235
/// Convert from `Event` type to `OpenAPILambdaRequest`
3336
/// - Parameters:
@@ -40,8 +43,18 @@ public protocol OpenAPILambda: Sendable {
4043
func output(from: OpenAPILambdaResponse) -> Output
4144
}
4245

43-
extension OpenAPILambda {
44-
/// Returns the Lambda handler function for this OpenAPI Lambda implementation.
46+
extension OpenAPILambdaService {
47+
/// Returns the Lambda handler function for this OpenAPILambdaService implementation.
48+
/// Use this function when you create a vanilla OpenAPILambdaService and just need to access its handler
49+
/// If you need to inject dependencies into your OpenAPILambdaService implementation,
50+
/// write your own initializer, such as `init(dbConnection:)` on the OpenAPILambdaService implementation,
51+
/// then create the OpenAPILambdaHandler and the LambdaRuntime manually.
52+
/// For example:
53+
/// let openAPIService = QuoteServiceImpl(i: 42) // my custom OpenAPI service initializer
54+
/// let lambda = try OpenAPILambdaHandler(service: openAPIService)
55+
/// let lambdaRuntime = LambdaRuntime(body: lambda.handler)
56+
/// try await lambdaRuntime.run()
57+
///
4558
/// - Returns: A handler function that can be used with AWS Lambda Runtime
4659
public static func handler() throws -> (Event, LambdaContext) async throws -> Output {
4760
try OpenAPILambdaHandler<Self>().handler
@@ -51,7 +64,7 @@ extension OpenAPILambda {
5164
/// when one is given.
5265
/// - Parameter logger: The logger to use for Lambda runtime logging
5366
public static func run(logger: Logger? = nil) async throws {
54-
let _logger = logger ?? Logger(label: "OpenAPILambda")
67+
let _logger = logger ?? Logger(label: "OpenAPILambdaService")
5568
let lambdaRuntime = LambdaRuntime(logger: _logger, body: try Self.handler())
5669
try await lambdaRuntime.run()
5770
}

Sources/OpenAPILambdaHandler.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ import OpenAPIRuntime
1818
import HTTPTypes
1919

2020
/// Specialization of LambdaHandler which runs an OpenAPILambda
21-
struct OpenAPILambdaHandler<L: OpenAPILambda> {
21+
public struct OpenAPILambdaHandler<OALS: OpenAPILambdaService>: Sendable {
2222

2323
private let router: OpenAPILambdaRouter
2424
private let transport: OpenAPILambdaTransport
25-
private let lambda: L
25+
private let openAPIService: OALS
2626

2727
/// the input type for this Lambda handler (received from the `OpenAPILambda`)
28-
public typealias Event = L.Event
28+
public typealias Event = OALS.Event
2929

3030
/// the output type for this Lambda handler (received from the `OpenAPILambda`)
31-
public typealias Output = L.Output
31+
public typealias Output = OALS.Output
3232

3333
/// Initialize `OpenAPILambdaHandler`.
3434
///
@@ -38,7 +38,23 @@ struct OpenAPILambdaHandler<L: OpenAPILambda> {
3838
init() throws {
3939
self.router = TrieRouter()
4040
self.transport = OpenAPILambdaTransport(router: self.router)
41-
self.lambda = try .init(transport: self.transport)
41+
42+
// decouple the OpenAPILambda creation from the registration of teh transport
43+
// this allows users to provide their own overloaded init() function to inject dependencies.
44+
self.openAPIService = OALS()
45+
try self.openAPIService.register(transport: self.transport)
46+
}
47+
48+
/// Initialize an `OpenAPILambdaHandler` with your own `OpenAPILambda` object.
49+
///
50+
/// Use this function when you need to inject dependencies in your OpenAPILambda
51+
/// - Parameters:
52+
/// - lambda: The `OpenAPILambda` instance to use.
53+
public init(service: OALS) throws {
54+
self.router = TrieRouter()
55+
self.transport = OpenAPILambdaTransport(router: self.router)
56+
self.openAPIService = service
57+
try self.openAPIService.register(transport: self.transport)
4258
}
4359

4460
/// The Lambda handling method.
@@ -49,14 +65,14 @@ struct OpenAPILambdaHandler<L: OpenAPILambda> {
4965
/// - context: Runtime ``LambdaContext``.
5066
///
5167
/// - Returns: A Lambda result ot type `Output`.
52-
func handler(event: L.Event, context: LambdaContext) async throws -> L.Output {
68+
public func handler(event: OALS.Event, context: LambdaContext) async throws -> OALS.Output {
5369

5470
// by default returns HTTP 500
5571
var lambdaResponse: OpenAPILambdaResponse = (HTTPResponse(status: .internalServerError), "unknown error")
5672

5773
do {
5874
// convert Lambda event source to OpenAPILambdaRequest
59-
let request = try lambda.request(context: context, from: event)
75+
let request = try openAPIService.request(context: context, from: event)
6076

6177
// route the request to find the handlers and extract the paramaters
6278
let (handler, parameters) = try router.route(method: request.0.method, path: request.0.path!)
@@ -105,6 +121,6 @@ struct OpenAPILambdaHandler<L: OpenAPILambda> {
105121
}
106122

107123
// transform the OpenAPILambdaResponse to the Lambda Output
108-
return lambda.output(from: lambdaResponse)
124+
return openAPIService.output(from: lambdaResponse)
109125
}
110126
}

Sources/OpenAPILambdaTransport.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,20 @@ public typealias OpenAPILambdaResponse = (HTTPResponse, String?)
3030
public typealias OpenAPILambdaRequestParameters = [String: Substring]
3131

3232
/// an OpenAPI handler
33-
public typealias OpenAPIHandler = (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
33+
public typealias OpenAPIHandler = @Sendable (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
3434
HTTPResponse, HTTPBody?
3535
)
3636

3737
/// Lambda Transport for OpenAPI generator
38-
public struct OpenAPILambdaTransport: ServerTransport {
38+
public struct OpenAPILambdaTransport: ServerTransport, Sendable {
3939

40+
/// The router for the OpenAPILambdaTransport
41+
/// Use this router to register your OpenAPI handlers
42+
/// and add your own route, such as /health
4043
public let router: OpenAPILambdaRouter
4144

4245
/// Create a `OpenAPILambdaTransport` with the given `OpenAPILambdaRouter`
46+
/// - Parameter router: The router to use for the transport.
4347
init(router: OpenAPILambdaRouter) { self.router = router }
4448

4549
/// Registers an HTTP operation handler at the provided path and method.

Sources/Router/OpenAPILambdaRouter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public enum OpenAPILambdaRouterError: Error {
2424
}
2525

2626
/// A router API
27-
public protocol OpenAPILambdaRouter {
27+
public protocol OpenAPILambdaRouter: Sendable {
2828
/// add a route for a given HTTP method and path and associate a handler
2929
func add(method: HTTPRequest.Method, path: String, handler: @escaping OpenAPIHandler) throws
3030

Sources/Router/OpenAPILambdaRouterTrie.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,25 @@
1313
//
1414
//===----------------------------------------------------------------------===//
1515
import HTTPTypes
16+
import Synchronization
1617

1718
/// A Trie router implementation
18-
struct TrieRouter: OpenAPILambdaRouter {
19-
private let uriPath: URIPathCollection = URIPath()
19+
final class TrieRouter: OpenAPILambdaRouter {
20+
private let uriPath = Mutex<any URIPathCollection>(URIPath())
2021

2122
/// add a route for a given HTTP method and path and associate a handler
2223
func add(method: HTTPRequest.Method, path: String, handler: @escaping OpenAPIHandler) throws {
23-
try uriPath.add(method: method, path: path, handler: handler)
24+
try self.uriPath.withLock { @Sendable in
25+
try $0.add(method: method, path: path, handler: handler)
26+
}
2427
}
2528

2629
/// Retrieve the handler and path parameter for a given HTTP method and path
2730
func route(method: HTTPRequest.Method, path: String) throws -> (
2831
OpenAPIHandler, OpenAPILambdaRequestParameters
29-
) { try uriPath.find(method: method, path: path) }
32+
) {
33+
try self.uriPath.withLock { try $0.find(method: method, path: path) }
34+
}
3035
}
3136

3237
enum URIPathCollectionError: Error {

0 commit comments

Comments
 (0)