mirror of
https://github.com/swift-server/swift-openapi-lambda.git
synced 2026-06-02 07:27:32 +00:00
b60c42224452001ddfe7b8a99a1fbbc1adb5b2c0
swift-openapi-lambda
An AWS Lambda Transport for Swift OpenAPI generator
This library allows to expose server side Swift OpenAPI implementation generated by the Swift OpenAPI generator as an AWS Lambda function and an AWS API Gateway.
Prerequisites
To write and deploy AWS Lambda functions based on an OpenAPI API definition, you need the following:
- an AWS Account
- the AWS Command Line Interface (AWS CLI) - install the CLI and configure it with credentials to your AWS account
- the AWS SAM CLI - a command-line tool used to create serverless workloads on AWS
- the Docker Desktop - to compile your Swift code for Linux deployment to AWS Lambda
TL;DR (aka Quick Start with a Stock Quote API service example)
Part 1 - the Swift OpenAPI part
- Create a Swift executable project
mkdir quoteapi && cd quoteapi
swift package init --name quoteapi --type executable
- Write or import an OpenAI API definition in YAML or JSON
cat << EOF > Sources/openapi.yaml
openapi: 3.1.0
info:
title: StockQuoteService
version: 1.0.0
components:
schemas:
quote:
type: object
properties:
symbol:
type: string
price:
type: number
change:
type: number
changePercent:
type: number
volume:
type: number
timestamp:
type: string
format: date-time
paths:
/stocks/{symbol}:
get:
summary: Get the latest quote for a stock
operationId: getQuote
parameters:
- name: symbol
in: path
required: true
schema:
type: string
tags:
- stocks
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/quote'
400:
description: Bad Request
404:
description: Not Found
EOF
- Add a Swift OpenAPI generator configuration file to generate only the server side
cat << EOF > Sources/openapi-generator-config.yaml
generate:
- types
- server
EOF
- Use this
Package.swiftfile to define targets and their dependencies
cat << EOF > Package.swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "QuoteService",
platforms: [
.macOS(.v13), .iOS(.v15), .tvOS(.v15), .watchOS(.v6),
],
products: [
.executable(name: "QuoteService", targets: ["QuoteService"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", .upToNextMinor(from: "1.0.0-alpha.1")),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", .upToNextMinor(from: "1.0.0")),
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "1.0.0-alpha.1"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main"),
.package(url: "https://github.com/sebsto/swift-openapi-lambda", branch: "main")
],
targets: [
.executableTarget(
name: "QuoteService",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPIRuntime",package: "swift-openapi-runtime"),
.product(name: "OpenAPILambda",package: "swift-openapi-lambda"),
],
path: "Sources",
resources: [
.copy("openapi.yaml"),
.copy("openapi-generator-config.yaml")
],
plugins: [
.plugin(
name: "OpenAPIGenerator",
package: "swift-openapi-generator"
)
]
),
]
)
EOF
- Generate server side Swift stub of the OpenAPI API definition
swift build
- Replace
main.swiftwith your own implementation
rm Sources/main.swift
cat << EOF > Sources/QuoteService.swift
import Foundation
import OpenAPIRuntime
struct QuoteServiceImpl: APIProtocol {
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
let symbol = input.path.symbol
// in real life, don't use random values 🤣
let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: Date())
return .ok(.init(body: .json(price)))
}
}
EOF
Part 2 - the Lambda part
- Add a Lambda function that consumes your OpenAPI service implementation. The Lambda is invoked by an AWS API Gateway.
cat << EOF > Sources/Lambda.swift
mport AWSLambdaEvents
import OpenAPILambda
@main
struct QuoteServiceLambda: OpenAPILambda {
typealias Event = APIGatewayV2Request
typealias Output = APIGatewayV2Response
public init(transport: LambdaOpenAPITransport) throws {
let openAPIHandler = QuoteServiceImpl()
try openAPIHandler.registerHandlers(on: transport)
}
}
- Add the build instructions as a Docker file and a Makefile. We build for Swift 5.9 on Amazon Linux 2
cat << EOF > Dockerfile
# image used to compile your Swift code
FROM public.ecr.aws/docker/library/swift:5.9.1-amazonlinux2
RUN yum -y install git jq tar zip openssl-devel
EOF
cat << EOF > Makefile
### Add functions here and link them to builder-bot format MUST BE "build-FunctionResourceName in template.yaml"
build-QuoteService: builder-bot
# Helper commands
deploy:
sam deploy
logs:
sam logs --stack-name QuoteService --name QuoteService
tail:
sam logs --stack-name QuoteService --name QuoteService --tail
###################### No Change required below this line ##########################
builder-bot:
$(eval $@PRODUCT = $(subst build-,,$(MAKECMDGOALS)))
$(eval $@BUILD_DIR = $(PWD)/.aws-sam/build-swift)
$(eval $@STAGE = $($@BUILD_DIR)/lambda)
$(eval $@ARTIFACTS_DIR = $(PWD)/.aws-sam/build/$($@PRODUCT))
# build docker image to compile Swift for Linux
docker build -f Dockerfile . -t swift-builder
# prep directories
mkdir -p $($@BUILD_DIR)/lambda $($@ARTIFACTS_DIR)
# compile application inside Docker image using source code from local project folder
docker run --rm -v $($@BUILD_DIR):/build-target -v `pwd`:/build-src -w /build-src swift-builder bash -cl "swift build --static-swift-stdlib --product $($@PRODUCT) -c release --build-path /build-target"
# create lambda bootstrap file
docker run --rm -v $($@BUILD_DIR):/build-target -v `pwd`:/build-src -w /build-src swift-builder bash -cl "cd /build-target/lambda && ln -s $($@PRODUCT) /bootstrap"
# copy binary to stage
cp $($@BUILD_DIR)/release/$($@PRODUCT) $($@STAGE)/bootstrap
# copy app from stage to artifacts dir
cp $($@STAGE)/* $($@ARTIFACTS_DIR)
EOF
- Build the executable
sam build
- Deploy the Lambda function and creates an API Gateway in front of it
# use --guided for the first deployment. SAM cli collects a few parameters and store them in `samconfig.toml`
sam deploy --guided
This command outputs the URL of the API GAteway, for example:
Outputs
-----------------------------------------------------------------------------------------------------------------------------
Key SwiftAPIEndpoint
Description API Gateway endpoint URL for your application
Value https://747ukfmah7.execute-api.us-east-1.amazonaws.com
-----------------------------------------------------------------------------------------------------------------------------
- Test your setup
curl https://747ukfmah7.execute-api.us-east-1.amazonaws.com/stocks/AAPL
{
"change" : -4,
"changePercent" : -0.030052760210257923,
"price" : 111,
"symbol" : "AAPL",
"timestamp" : "2023-12-13T03:12:35Z",
"volume" : 63812
}
Local Testing
git clone https://github.com/sebsto/swift-openapi-lambda.git && cd swift-openapi-lambda
# In the directory of the Swift OpenAPI Lambda transport project
LOCAL_LAMBDA_SERVER_ENABLED=true swift run
# from another terminal, in the directory of the QuoteAPI sample project
curl -v -X POST --header "Content-Type: application/json" --data @events/GetQuote.json http://127.0.0.1:7000/invoke
References
Swift OpenAPI generator
To get started with the Swift OpenAPI generator, check out the full documentation, which contains a step-by-step tutorial.
Swift on AWS Lambda
The Swift Runtime for AWS Lambda allows you to write AWS Lambda functions in the Swift programming language.
To get started, check out this step-by-step tutorial and the documentation.
Serverless Application Model (SAM)
Read "What is SAM" to understand and get started with SAM.
Languages
Swift
89.3%
Shell
10.6%