Skip to content

Commit

Permalink
Merge pull request #2587 from gzhk/aws-cdk-support-poc
Browse files Browse the repository at this point in the history
AWS CDK support for Tapir endpoints
  • Loading branch information
adamw authored Feb 10, 2023
2 parents 2ccd6b9 + d2bb8be commit d27d295
Show file tree
Hide file tree
Showing 57 changed files with 2,087 additions and 138 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ jobs:
unzip -q aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install --update
sam --version
- name: Install NPM
run: |
sudo apt install npm
npm --version
- name: Install AWS CDK
run: |
npm install -g aws-cdk
cdk --version
- name: Install libidn2-dev libcurl3-dev
if: matrix.target-platform == 'Native'
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
.cache
.history
.lib/
/cdk
/aws-cdk-tests
.vscode/
dist/*
target/
Expand Down
99 changes: 94 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ lazy val rawAllAggregates = core.projectRefs ++
openapiCodegenSbt.projectRefs ++
openapiCodegenCli.projectRefs ++
clientTestServer.projectRefs ++
derevo.projectRefs
derevo.projectRefs ++
awsCdk.projectRefs

lazy val allAggregates: Seq[ProjectReference] = {
if (sys.env.isDefinedAt("STTP_NATIVE")) {
Expand Down Expand Up @@ -1373,7 +1374,8 @@ lazy val awsLambda: ProjectMatrix = (projectMatrix in file("serverless/aws/lambd
name := "tapir-aws-lambda",
libraryDependencies ++= loggerDependencies,
libraryDependencies ++= Seq(
"com.softwaremill.sttp.client3" %% "fs2" % Versions.sttp
"com.softwaremill.sttp.client3" %% "fs2" % Versions.sttp,
"com.amazonaws" % "aws-lambda-java-runtime-interface-client" % Versions.awsLambdaInterface
)
)
.jvmPlatform(scalaVersions = scala2And3Versions)
Expand All @@ -1387,7 +1389,6 @@ lazy val awsLambdaTests: ProjectMatrix = (projectMatrix in file("serverless/aws/
.settings(commonJvmSettings)
.settings(
name := "tapir-aws-lambda-tests",
libraryDependencies += "com.amazonaws" % "aws-lambda-java-runtime-interface-client" % Versions.awsLambdaInterface,
assembly / assemblyJarName := "tapir-aws-lambda-tests.jar",
assembly / test := {}, // no tests before building jar
assembly / assemblyMergeStrategy := {
Expand Down Expand Up @@ -1436,6 +1437,77 @@ lazy val awsLambdaTests: ProjectMatrix = (projectMatrix in file("serverless/aws/
.jvmPlatform(scalaVersions = scala2Versions)
.dependsOn(core, cats, circeJson, awsLambda, awsSam, sttpStubServer, serverTests)

// integration tests for aws cdk interpreter
// it's a separate project since it needs a fat jar with lambda code which cannot be build from tests sources
// runs sam local cmd line tool to start AWS Api Gateway with lambda proxy
lazy val awsCdkTests: ProjectMatrix = (projectMatrix in file("serverless/aws/cdk-tests"))
.settings(commonJvmSettings)
.settings(
name := "tapir-aws-cdk-tests",
assembly / assemblyJarName := "tapir-aws-cdk-tests.jar",
assembly / test := {}, // no tests before building jar
assembly / assemblyMergeStrategy := {
case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.first
case PathList(ps @ _*) if ps.last contains "FlowAdapters" => MergeStrategy.first
case PathList(ps @ _*) if ps.last == "module-info.class" => MergeStrategy.first
case _ @("scala/annotation/nowarn.class" | "scala/annotation/nowarn$.class") => MergeStrategy.first
case x => (assembly / assemblyMergeStrategy).value(x)
},
Test / test := {
if (scalaVersion.value == scala2_13) { // only one test can run concurrently, as it starts a local sam instance
(Test / test)
.dependsOn(
Def.sequential(
(Compile / runMain).toTask(" sttp.tapir.serverless.aws.cdk.tests.AwsCdkAppTemplate"),
assembly
)
)
.value
}
},
Test / testOptions ++= {
val log = sLog.value
val awsCdkTestAppDir = "aws-cdk-tests"
// processes use files which are generated by `AwsCdkAppTemplate` called above
lazy val nmpInstall = Process("npm i", new java.io.File(awsCdkTestAppDir)).run()
lazy val cdkSynth = Process("cdk synth", new java.io.File(awsCdkTestAppDir)).run()
lazy val sam = Process(s"sam local start-api -t $awsCdkTestAppDir/cdk.out/TapirCdkStack.template.json -p 3010 --warm-containers EAGER").run()
Seq(
Tests.Setup(() => {
val npmExit = nmpInstall.exitValue()
if (npmExit != 0) {
log.error(s"Failed to run npm install for aws cdk tests (exit code: $npmExit)")
} else {
val cdkExit = cdkSynth.exitValue()
if (cdkExit != 0) {
log.error(s"Failed to run cdk synth for aws cdk tests (exit code: $cdkExit)")
} else {
val samReady = PollingUtils.poll(60.seconds, 1.second) {
sam.isAlive() && PollingUtils.urlConnectionAvailable(new URL(s"http://127.0.0.1:3010/health"))
}
if (!samReady) {
sam.destroy()
val exit = sam.exitValue()
log.error(s"failed to start sam local within 60 seconds (exit code: $exit)")
}
}
}
}),
Tests.Cleanup(() => {
sam.destroy()
val exit = sam.exitValue()
log.info(s"stopped sam local (exit code: $exit)")

val deleted = new scala.reflect.io.Directory(new File(awsCdkTestAppDir).getAbsoluteFile).deleteRecursively()
log.info(s"Removed tmp files: $deleted")
}),
)
},
Test / parallelExecution := false
)
.jvmPlatform(scalaVersions = scala2Versions)
.dependsOn(core, cats, circeJson, awsCdk, serverTests)

lazy val awsSam: ProjectMatrix = (projectMatrix in file("serverless/aws/sam"))
.settings(commonJvmSettings)
.settings(
Expand All @@ -1448,6 +1520,22 @@ lazy val awsSam: ProjectMatrix = (projectMatrix in file("serverless/aws/sam"))
.jvmPlatform(scalaVersions = scala2And3Versions)
.dependsOn(core, tests % Test)

lazy val awsCdk: ProjectMatrix = (projectMatrix in file("serverless/aws/cdk"))
.settings(commonJvmSettings)
.settings(
name := "tapir-aws-cdk",
assembly / assemblyJarName := "tapir-aws-cdk.jar",
libraryDependencies ++= Seq(
"io.circe" %% "circe-yaml" % Versions.circeYaml,
"io.circe" %% "circe-generic" % Versions.circe,
"io.circe" %%% "circe-parser" % Versions.circe,
"org.typelevel" %%% "cats-effect" % Versions.catsEffect,
"com.amazonaws" % "aws-lambda-java-runtime-interface-client" % Versions.awsLambdaInterface
)
)
.jvmPlatform(scalaVersions = scala2And3Versions)
.dependsOn(core, tests % Test, awsLambda)

lazy val awsTerraform: ProjectMatrix = (projectMatrix in file("serverless/aws/terraform"))
.settings(commonJvmSettings)
.settings(
Expand Down Expand Up @@ -1478,6 +1566,7 @@ lazy val awsExamples: ProjectMatrix = (projectMatrix in file("serverless/aws/exa
case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.first
case PathList(ps @ _*) if ps.last contains "FlowAdapters" => MergeStrategy.first
case _ @("scala/annotation/nowarn.class" | "scala/annotation/nowarn$.class") => MergeStrategy.first
case PathList(ps @ _*) if ps.last == "module-info.class" => MergeStrategy.first
case x => (assembly / assemblyMergeStrategy).value(x)
},
libraryDependencies += "com.amazonaws" % "aws-lambda-java-runtime-interface-client" % Versions.awsLambdaInterface
Expand All @@ -1492,8 +1581,8 @@ lazy val awsExamples: ProjectMatrix = (projectMatrix in file("serverless/aws/exa
)
.dependsOn(awsLambda)

lazy val awsExamples2_12 = awsExamples.jvm(scala2_12).dependsOn(awsSam.jvm(scala2_12), awsTerraform.jvm(scala2_12))
lazy val awsExamples2_13 = awsExamples.jvm(scala2_13).dependsOn(awsSam.jvm(scala2_13), awsTerraform.jvm(scala2_13))
lazy val awsExamples2_12 = awsExamples.jvm(scala2_12).dependsOn(awsSam.jvm(scala2_12), awsTerraform.jvm(scala2_12), awsCdk.jvm(scala2_12))
lazy val awsExamples2_13 = awsExamples.jvm(scala2_13).dependsOn(awsSam.jvm(scala2_13), awsTerraform.jvm(scala2_13), awsCdk.jvm(scala2_13))

// client

Expand Down
10 changes: 9 additions & 1 deletion core/src/test/scala/sttp/tapir/EndpointTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ class EndpointTest extends AnyFlatSpec with EndpointTestExtensions with Matchers
(
endpoint.in("p1" / "p2".schema(_.hidden(true)) / query[String]("par1") / query[String]("par2").schema(_.hidden(true))),
"/p1?par1={par1}"
)
),
(endpoint.in("not" / "allowed" / "chars" / "hi?hello"), "/not/allowed/chars/hi%3Fhello")
)

for ((testEndpoint, expectedShownPath) <- showPathTemplateTestData) {
Expand All @@ -295,6 +296,13 @@ class EndpointTest extends AnyFlatSpec with EndpointTestExtensions with Matchers
) shouldBe "/p1/{par1}?param={par2}"
}

"showPathTemplate" should "skip query parameters" in {
val testEndpoint = endpoint.in("p1" / path[String] / query[String]("param"))
testEndpoint.showPathTemplate(
showQueryParam = None
) shouldBe "/p1/{param1}"
}

"validate" should "accumulate validators" in {
val input = query[Int]("x").validate(Validator.min(1)).validate(Validator.max(3))
input.codec.schema.applyValidation(0) should not be empty
Expand Down
Loading

0 comments on commit d27d295

Please sign in to comment.