Skip to content

Commit 27e6f46

Browse files
committed
complete README with instruction to implement mapping to other Lambda event types
1 parent ef2e29f commit 27e6f46

File tree

1 file changed

+109
-3
lines changed

1 file changed

+109
-3
lines changed

README.md

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ import Foundation
6262
import OpenAPIRuntime
6363
import OpenAPILambda // <-- add this line
6464

65-
@main
65+
@main // <-- Flag this struct as the executable target entrypoint
6666
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi { // <-- add the OpenAPILambdaHttpApi protocol
6767

6868
init(transport: LambdaOpenAPITransport) throws { // <-- add this constructor (don't remove the call to `registerHandlers(on:)`)
@@ -406,9 +406,115 @@ LOCAL_LAMBDA_SERVER_ENABLED=true swift run
406406
curl -v -X POST --header "Content-Type: application/json" --data @events/GetQuote.json http://127.0.0.1:7000/invoke
407407
```
408408
409-
## Implement your own `OpenAPILambda` to support other event types
409+
## Implement your own `OpenAPILambda` to support other event types
410+
411+
When you expose your AWS Lambda function to other event types, you have to specialize the `OpenAPILambda` protocol and implement the two methods that transform your Lambda function input data to an `OpenAPIRequest` and the other way around, transform an `OpenAPIResponse` to your Lambda function output type.
412+
413+
Here is an example with the [Amazon API Gateway (Rest Api)](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.htm), (aka the original API Gateway).
414+
415+
I start with an OpenAPI stub implementation - unmodified.
416+
417+
```swift
418+
import Foundation
419+
import OpenAPIRuntime
420+
421+
struct QuoteServiceImpl: APIProtocol {
422+
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
423+
424+
let symbol = input.path.symbol
425+
426+
let price = Components.Schemas.quote(
427+
symbol: symbol,
428+
price: Double.random(in: 100..<150).rounded(),
429+
change: Double.random(in: -5..<5).rounded(),
430+
changePercent: Double.random(in: -0.05..<0.05),
431+
volume: Double.random(in: 10000..<100000).rounded(),
432+
timestamp: Date())
433+
434+
return .ok(.init(body: .json(price)))
435+
}
436+
}
437+
```
438+
439+
Next, I implement a `struct` that conforms to `OpenAPILambda`. This `struct` defines:
440+
441+
- the event input and output the Lambda function will work on (from [AWS Lambda Event Types](https://github.com/swift-server/swift-aws-lambda-events) library).
442+
- the mandatory constructor `init(transport:)`
443+
- the executable target entrypoint (`@main`)
444+
445+
Here is an example using `APIGatewayRequest` and `APIGatewayResponse`:
446+
447+
```swift
448+
@main
449+
struct QuoteServiceLambda: OpenAPILambda {
450+
typealias Event = APIGatewayRequest
451+
typealias Output = APIGatewayResponse
452+
public init(transport: LambdaOpenAPITransport) throws {
453+
let openAPIHandler = QuoteServiceImpl()
454+
try openAPIHandler.registerHandlers(on: transport)
455+
}
456+
}
457+
```
458+
459+
Next step is to implement two methods from `OpenAPILambda` protocol to convert your Lambda function input data (`APIGatewayRequest`) to an `OpenAPIRequest` and the other way around, transform an `OpenAPIResponse` to your Lambda function output type (`APIGatewayResponses`).
460+
461+
```swift
462+
extension OpenAPILambda where Event == APIGatewayRequest {
463+
/// Transform a Lambda input (`APIGatewayRequest` and `LambdaContext`) to an OpenAPILambdaRequest (`HTTPRequest`, `String?`)
464+
public func request(context: LambdaContext, from request: Event) throws -> OpenAPILambdaRequest {
465+
(try request.httpRequest(), request.body)
466+
}
467+
}
468+
469+
extension OpenAPILambda where Output == APIGatewayResponse {
470+
/// Transform an OpenAPI response (`HTTPResponse`, `String?`) to a Lambda Output (`APIGatewayResponse`)
471+
public func output(from response: OpenAPILambdaResponse) -> Output {
472+
var apiResponse = APIGatewayResponse(from: response.0)
473+
apiResponse.body = response.1
474+
return apiResponse
475+
}
476+
}
477+
```
478+
479+
To keep the above code short, simple, and readable, we suggest to implement whatever extension on the Lambda event type. Here are the extensions required to support the above code. These are simple data transformation methods from one type to the other.
480+
481+
```swift
482+
extension APIGatewayRequest {
483+
484+
/// Return an `HTTPRequest.Method` for this `APIGatewayRequest`
485+
public func httpRequestMethod() throws -> HTTPRequest.Method {
486+
guard let method = HTTPRequest.Method(rawValue: self.httpMethod.rawValue) else {
487+
throw LambdaOpenAPIHttpError.invalidMethod(self.httpMethod.rawValue)
488+
}
489+
return method
490+
}
491+
492+
/// Return an `HTTPRequest` for this `APIGatewayV2Request`
493+
public func httpRequest() throws -> HTTPRequest {
494+
try HTTPRequest(
495+
method: self.httpRequestMethod(),
496+
scheme: "https",
497+
authority: "",
498+
path: self.path,
499+
headerFields: self.headers.httpFields()
500+
)
501+
}
502+
}
503+
504+
extension APIGatewayResponse {
505+
506+
/// Create a `APIGatewayV2Response` from an `HTTPResponse`
507+
public init(from response: HTTPResponse) {
508+
self = APIGatewayResponse(
509+
statusCode: .init(code: UInt(response.status.code)),
510+
headers: .init(from: response.headerFields),
511+
isBase64Encoded: false
512+
)
513+
}
514+
}
515+
```
410516
411-
TBD
517+
You can apply the same design to support other AWS Lambda event types. However, keep in mind that the `OpenAPILAmbda` implementation is heavily biased towards receiving, routing, and responding to HTTP requests.
412518
413519
## References
414520

0 commit comments

Comments
 (0)