-
Notifications
You must be signed in to change notification settings - Fork 463
Add direct-style AwsSyncServerInterpreter for AWS Lambda #5149
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
c117ab2
3ebd136
1afc110
3162586
649592c
91362b4
3182a82
a7147c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package sttp.tapir.serverless.aws.lambda | ||
|
|
||
| import sttp.monad.MonadError | ||
| import sttp.monad.IdentityMonad | ||
| import sttp.shared.Identity | ||
| import AwsSyncServerInterpreter._ | ||
|
|
||
| abstract class AwsSyncServerInterpreter extends AwsServerInterpreter[Identity] | ||
|
|
||
| object AwsSyncServerInterpreter { | ||
|
|
||
| implicit val idMonadError: MonadError[Identity] = IdentityMonad | ||
|
|
||
| def apply(serverOptions: AwsServerOptions[Identity]): AwsSyncServerInterpreter = { | ||
| new AwsSyncServerInterpreter { | ||
| override def awsServerOptions: AwsServerOptions[Identity] = serverOptions | ||
| } | ||
| } | ||
|
|
||
| def apply(): AwsSyncServerInterpreter = { | ||
| new AwsSyncServerInterpreter { | ||
| override def awsServerOptions: AwsServerOptions[Identity] = AwsSyncServerOptions.default | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package sttp.tapir.serverless.aws.lambda | ||
|
|
||
| import sttp.shared.Identity | ||
| import sttp.tapir.server.interceptor.CustomiseInterceptors | ||
|
|
||
| object AwsSyncServerOptions { | ||
|
|
||
| /** Allows customising the interceptors used by the server interpreter. */ | ||
| def customiseInterceptors: CustomiseInterceptors[Identity, AwsServerOptions[Identity]] = | ||
| CustomiseInterceptors( | ||
| createOptions = | ||
| (ci: CustomiseInterceptors[Identity, AwsServerOptions[Identity]]) => AwsServerOptions(encodeResponseBody = true, ci.interceptors) | ||
| ) | ||
|
|
||
| def default: AwsServerOptions[Identity] = customiseInterceptors.options | ||
|
|
||
| def noEncoding: AwsServerOptions[Identity] = | ||
| default.copy(encodeResponseBody = false) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||
| package sttp.tapir.serverless.aws.lambda | ||||||
|
|
||||||
| import com.amazonaws.services.lambda.runtime.{Context, RequestStreamHandler} | ||||||
| import io.circe._ | ||||||
| import io.circe.generic.auto._ | ||||||
| import io.circe.parser.decode | ||||||
| import io.circe.syntax._ | ||||||
| import sttp.shared.Identity | ||||||
| import sttp.tapir.server.ServerEndpoint | ||||||
|
|
||||||
| import java.io.{BufferedWriter, InputStream, OutputStream, OutputStreamWriter} | ||||||
| import java.nio.charset.StandardCharsets | ||||||
|
|
||||||
| /** [[SyncLambdaHandler]] is a direct-style entry point for handling requests sent to AWS Lambda application which exposes Tapir endpoints. | ||||||
| * | ||||||
| * @tparam R | ||||||
| * AWS API Gateway request type [[AwsRequestV1]] or [[AwsRequest]]. | ||||||
| * @param options | ||||||
| * Server options of type AwsServerOptions. | ||||||
| */ | ||||||
| abstract class SyncLambdaHandler[R: Decoder](options: AwsServerOptions[Identity]) extends RequestStreamHandler { | ||||||
|
|
||||||
| protected def getAllEndpoints: List[ServerEndpoint[Any, Identity]] | ||||||
|
|
||||||
| override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit = { | ||||||
| val server: AwsSyncServerInterpreter = AwsSyncServerInterpreter(options) | ||||||
|
|
||||||
| val allBytes = input.readAllBytes() | ||||||
| val decoded = decode[R](new String(allBytes, StandardCharsets.UTF_8)) | ||||||
|
||||||
| val response = decoded match { | ||||||
| case Left(e) => AwsResponse.badRequest(s"Invalid AWS request: ${e.getMessage}") | ||||||
|
||||||
| case Right(awsRequest) => | ||||||
| awsRequest match { | ||||||
| case r: AwsRequestV1 => server.toRoute(getAllEndpoints)(r.toV2) | ||||||
| case r: AwsRequest => server.toRoute(getAllEndpoints)(r) | ||||||
| case r => | ||||||
| throw new IllegalArgumentException(s"Request of type ${r.getClass.getCanonicalName} is not supported") | ||||||
|
||||||
| throw new IllegalArgumentException(s"Request of type ${r.getClass.getCanonicalName} is not supported") | |
| AwsResponse.badRequest(s"Request of type ${r.getClass.getCanonicalName} is not supported") |
Outdated
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SyncLambdaHandler.default currently uses AwsSyncServerOptions.noEncoding, which makes the method name misleading (it doesn't return the actual AwsSyncServerOptions.default). Consider either switching to AwsSyncServerOptions.default here, or renaming the method to reflect that it disables encoding (e.g., noEncoding).
| apply(endpoints, AwsSyncServerOptions.noEncoding) | |
| apply(endpoints, AwsSyncServerOptions.default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleRequestlargely duplicates the request decoding / V1->V2 mapping / response writing logic that already exists inLambdaHandler(cats-effect) andZioLambdaHandler. With multiple copies, behavior can easily drift over time (e.g., error handling, encoding defaults). Consider extracting the shared JSON decode + request normalization + response rendering into a small helper inlambda-corethat the various handlers can reuse.