Add ApiGateway accept header customization (#287)

* Add ApiGateway accept header customization

* Update Java version in CI to match local

* Fix bug in dependency relative paths

* Add path validation
This commit is contained in:
Russell Cohen 2021-04-01 13:16:13 -04:00 committed by GitHub
parent 9aa7881a1d
commit 6b605892c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 254 additions and 29 deletions

View File

@ -4,7 +4,7 @@ name: CI
env:
rust_version: 1.50.0
java_version: 9
java_version: 14
jobs:
style:

1
aws/sdk-codegen-test/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
smithy-build.json

View File

@ -0,0 +1,10 @@
# Codegen Integration Test
This module defines an integration test of the code generation machinery for AWS services. `.build.gradle.kts` will generate a `smithy-build.json` file as part of the build. The Smithy build plugin then invokes our codegen machinery and generates Rust crates.
This module exists to code generate and execute service specific protocol tests like [ApiGateway](https://github.com/awslabs/smithy/blob/main/smithy-aws-protocol-tests/model/restJson1/services/apigateway.smithy).
## Usage
```
# From repo root:
./gradlew :aws:sdk-codegen-test:test
```

View File

@ -0,0 +1,123 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
extra["displayName"] = "Smithy :: Rust :: Codegen :: Test"
extra["moduleName"] = "software.amazon.smithy.kotlin.codegen.test"
tasks["jar"].enabled = false
plugins {
id("software.amazon.smithy").version("0.5.2")
}
val smithyVersion: String by project
dependencies {
implementation(project(":aws:sdk-codegen"))
implementation("software.amazon.smithy:smithy-aws-protocol-tests:$smithyVersion")
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
}
data class CodegenTest(val service: String, val module: String, val extraConfig: String? = null)
val CodegenTests = listOf(
CodegenTest("com.amazonaws.apigateway#BackplaneControlService", "apigateway")
)
fun generateSmithyBuild(tests: List<CodegenTest>): String {
val projections = tests.joinToString(",\n") {
"""
"${it.module}": {
"plugins": {
"rust-codegen": {
"runtimeConfig": {
"relativePath": "${rootProject.projectDir.absolutePath}/rust-runtime"
},
"service": "${it.service}",
"module": "${it.module}",
"moduleVersion": "0.0.1",
"build": {
"rootProject": true
}
${it.extraConfig ?: ""}
}
}
}
""".trimIndent()
}
return """
{
"version": "1.0",
"projections": { $projections }
}
"""
}
task("generateSmithyBuild") {
description = "generate smithy-build.json"
doFirst {
projectDir.resolve("smithy-build.json").writeText(generateSmithyBuild(CodegenTests))
}
}
fun generateCargoWorkspace(tests: List<CodegenTest>): String {
return """
[workspace]
members = [
${tests.joinToString(",") { "\"${it.module}/rust-codegen\"" }}
]
""".trimIndent()
}
task("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
buildDir.resolve("smithyprojections/sdk-codegen-test/Cargo.toml").writeText(generateCargoWorkspace(CodegenTests))
}
}
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
tasks["assemble"].finalizedBy("generateCargoWorkspace")
tasks.register<Exec>("cargoCheck") {
workingDir("build/smithyprojections/sdk-codegen-test/")
// disallow warnings
environment("RUSTFLAGS", "-D warnings")
commandLine("cargo", "check")
dependsOn("assemble")
}
tasks.register<Exec>("cargoTest") {
workingDir("build/smithyprojections/sdk-codegen-test/")
// disallow warnings
environment("RUSTFLAGS", "-D warnings")
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks.register<Exec>("cargoDocs") {
workingDir("build/smithyprojections/sdk-codegen-test/")
// disallow warnings
environment("RUSTFLAGS", "-D warnings")
commandLine("cargo", "doc", "--no-deps")
dependsOn("assemble")
}
tasks.register<Exec>("cargoClippy") {
workingDir("build/smithyprojections/sdk-codegen-test/")
// disallow warnings
commandLine("cargo", "clippy", "--", "-D", "warnings", "-Aclippy::upper_case_acronyms", "-Aclippy::large-enum-variant")
dependsOn("assemble")
}
tasks["test"].finalizedBy("cargoCheck", "cargoClippy", "cargoTest", "cargoDocs")
tasks["clean"].doFirst {
delete("smithy-build.json")
}

View File

@ -6,6 +6,7 @@
package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator
import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayCustomizationDecorator
val DECORATORS = listOf(
CredentialsProviderDecorator(),
@ -15,7 +16,8 @@ val DECORATORS = listOf(
SigV4SigningDecorator(),
RetryPolicyDecorator(),
IntegrationTestDecorator(),
FluentClientDecorator()
FluentClientDecorator(),
ApiGatewayCustomizationDecorator()
)
class AwsCodegenDecorator : CombinedCodegenDecorator(DECORATORS) {

View File

@ -9,7 +9,6 @@ import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.rust
@ -91,7 +90,7 @@ class EndpointConfigCustomization(private val runtimeConfig: RuntimeConfig, serv
}
// This is an experiment in a slightly different way to create runtime types. All code MAY be refactored to use this pattern
fun RuntimeConfig.awsEndpointDependency() = CargoDependency("aws-endpoint", Local(this.relativePath))
fun RuntimeConfig.awsEndpointDependency() = awsRuntimeDependency("aws-endpoint")
class EndpointResolverFeature(private val runtimeConfig: RuntimeConfig, private val operationShape: OperationShape) :
OperationCustomization() {

View File

@ -8,7 +8,6 @@ package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.CratesIo
import software.amazon.smithy.rust.codegen.rustlang.DependencyScope
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator
@ -42,4 +41,4 @@ class AwsHyperDevDep(private val runtimeConfig: RuntimeConfig) : LibRsCustomizat
}
val Tokio = CargoDependency("tokio", CratesIo("1"), features = listOf("macros", "test-util"), scope = DependencyScope.Dev)
fun RuntimeConfig.awsHyper() = CargoDependency("aws-hyper", Local(relativePath), features = listOf("test-util"))
fun RuntimeConfig.awsHyper() = awsRuntimeDependency("aws-hyper", features = listOf("test-util"))

View File

@ -0,0 +1,26 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import java.io.File
import java.nio.file.Path
fun RuntimeConfig.awsRoot(): String {
val asPath = Path.of(relativePath)
val path = if (asPath.isAbsolute) {
asPath.parent.resolve("aws/rust-runtime").toAbsolutePath().toString()
} else {
relativePath
}
check(File(path).exists()) { "$path must exist to generate a working SDK" }
return path
}
fun RuntimeConfig.awsRuntimeDependency(name: String, features: List<String> = listOf()): CargoDependency =
CargoDependency(name, Local(awsRoot()), features = features)

View File

@ -6,8 +6,6 @@
package software.amazon.smithy.rustsdk
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.docs
@ -75,8 +73,7 @@ class CredentialProviderConfig(runtimeConfig: RuntimeConfig) : ConfigCustomizati
self
}
""",
credentialsProvider
credentialsProvider,
)
}
ServiceConfig.BuilderBuild -> rust(
@ -111,7 +108,9 @@ class PubUseCredentials(private val runtimeConfig: RuntimeConfig) : LibRsCustomi
}
}
fun awsAuth(runtimeConfig: RuntimeConfig) = CargoDependency("aws-auth", Local(runtimeConfig.relativePath))
fun credentialsProvider(runtimeConfig: RuntimeConfig) = RuntimeType("ProvideCredentials", awsAuth(runtimeConfig), "aws_auth")
fun awsAuth(runtimeConfig: RuntimeConfig) = runtimeConfig.awsRuntimeDependency("aws-auth")
fun credentialsProvider(runtimeConfig: RuntimeConfig) =
RuntimeType("ProvideCredentials", awsAuth(runtimeConfig), "aws_auth")
fun defaultProvider(runtimeConfig: RuntimeConfig) = RuntimeType("default_provider", awsAuth(runtimeConfig), "aws_auth")
fun setProvider(runtimeConfig: RuntimeConfig) = RuntimeType("set_provider", awsAuth(runtimeConfig), "aws_auth")

View File

@ -6,8 +6,6 @@
package software.amazon.smithy.rustsdk
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.writable
@ -133,4 +131,4 @@ class PubUseRegion(private val runtimeConfig: RuntimeConfig) : LibRsCustomizatio
fun region(runtimeConfig: RuntimeConfig) =
RuntimeType("region", awsTypes(runtimeConfig), "aws_types")
fun awsTypes(runtimeConfig: RuntimeConfig) = CargoDependency("aws-types", Local(runtimeConfig.relativePath))
fun awsTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.awsRuntimeDependency("aws-types")

View File

@ -7,8 +7,6 @@ package software.amazon.smithy.rustsdk
import software.amazon.smithy.aws.traits.auth.SigV4Trait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.rust
@ -100,4 +98,4 @@ class SigV4SigningFeature(private val runtimeConfig: RuntimeConfig) :
}
}
fun RuntimeConfig.sigAuth() = CargoDependency("aws-sig-auth", Local(this.relativePath))
fun RuntimeConfig.sigAuth() = awsRuntimeDependency("aws-sig-auth")

View File

@ -8,7 +8,6 @@ package software.amazon.smithy.rustsdk
import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.rust
@ -58,7 +57,7 @@ class ApiVersion(private val runtimeConfig: RuntimeConfig, serviceTrait: Service
}
}
fun RuntimeConfig.awsHttp(): CargoDependency = CargoDependency("aws-http", Local(this.relativePath))
fun RuntimeConfig.awsHttp(): CargoDependency = awsRuntimeDependency("aws-http")
fun RuntimeConfig.userAgentModule() = awsHttp().asType().copy(name = "user_agent")
class UserAgentFeature(private val runtimeConfig: RuntimeConfig) : OperationCustomization() {

View File

@ -0,0 +1,50 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
package software.amazon.smithy.rustsdk.customize.apigateway
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator
import software.amazon.smithy.rust.codegen.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.smithy.generators.OperationSection
import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig
import software.amazon.smithy.rust.codegen.smithy.letIf
class ApiGatewayCustomizationDecorator : RustCodegenDecorator {
override val name: String = "ApiGateway"
override val order: Byte = 0
private fun applies(protocolConfig: ProtocolConfig) = protocolConfig.serviceShape.id == ShapeId.from("com.amazonaws.apigateway#BackplaneControlService")
override fun operationCustomizations(
protocolConfig: ProtocolConfig,
operation: OperationShape,
baseCustomizations: List<OperationCustomization>
): List<OperationCustomization> {
return baseCustomizations.letIf(applies(protocolConfig)) {
it + ApiGatewayAddAcceptHeader()
}
}
}
class ApiGatewayAddAcceptHeader : OperationCustomization() {
override fun section(section: OperationSection): Writable = when (section) {
is OperationSection.FinalizeOperation -> emptySection
OperationSection.ImplBlock -> emptySection
is OperationSection.MutateRequest -> writable {
rust(
"""${section.request}
.request_mut()
.headers_mut()
.insert("Accept", #T::HeaderValue::from_static("application/json"));""",
RuntimeType.http
)
}
}
}

View File

@ -6,12 +6,11 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.testutil.validateConfigCustomizations
internal class CredentialProviderConfigTest {
@Test
fun `generates a valid config`() {
validateConfigCustomizations(CredentialProviderConfig(TestRuntimeConfig))
validateConfigCustomizations(CredentialProviderConfig(AwsTestRuntimeConfig))
}
}

View File

@ -10,7 +10,6 @@ import org.junit.jupiter.api.Test
import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.testutil.stubConfigProject
@ -35,21 +34,21 @@ internal class EndpointConfigCustomizationTest {
@Test
fun `generates valid code`() {
validateConfigCustomizations(EndpointConfigCustomization(TestRuntimeConfig, model.lookup("test#TestService")))
validateConfigCustomizations(EndpointConfigCustomization(AwsTestRuntimeConfig, model.lookup("test#TestService")))
}
@Test
fun `generates valid code when no endpoint prefix is provided`() {
val serviceShape = model.lookup<ServiceShape>("test#NoEndpointPrefix")
validateConfigCustomizations(EndpointConfigCustomization(TestRuntimeConfig, serviceShape))
validateConfigCustomizations(EndpointConfigCustomization(AwsTestRuntimeConfig, serviceShape))
serviceShape.expectTrait(ServiceTrait::class.java).endpointPrefix shouldBe "noendpointprefix"
}
@Test
fun `write an endpoint into the config`() {
val project = stubConfigProject(EndpointConfigCustomization(TestRuntimeConfig, model.lookup("test#TestService")))
val project = stubConfigProject(EndpointConfigCustomization(AwsTestRuntimeConfig, model.lookup("test#TestService")))
project.lib {
it.addDependency(awsTypes(TestRuntimeConfig))
it.addDependency(awsTypes(AwsTestRuntimeConfig))
it.addDependency(CargoDependency.Http)
it.unitTest(
"""

View File

@ -6,12 +6,11 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.testutil.validateConfigCustomizations
internal class RegionProviderConfigTest {
@Test
fun `generates a valid config`() {
validateConfigCustomizations(RegionProviderConfig(TestRuntimeConfig))
validateConfigCustomizations(RegionProviderConfig(AwsTestRuntimeConfig))
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.testutil.TestRuntimeConfig
import java.io.File
// In aws-sdk-codegen, the working dir when gradle runs tests is actually `./aws`. So, to find the smithy runtime, we need
// to go up one more level
val AwsTestRuntimeConfig = TestRuntimeConfig.copy(
relativePath = run {
val path = File("../../rust-runtime")
check(path.exists()) { "$path must exist to generate a working SDK" }
path.absolutePath
}
)

View File

@ -169,6 +169,10 @@ impl Request {
self.configuration.as_ref().borrow()
}
pub fn request_mut(&mut self) -> &mut http::Request<SdkBody> {
&mut self.inner
}
pub fn try_clone(&self) -> Option<Request> {
let cloned_body = self.inner.body().try_clone()?;
let mut cloned_request = http::Request::builder()

View File

@ -21,4 +21,5 @@ include(":codegen")
include(":codegen-test")
include(":rust-runtime")
include(":aws:sdk-codegen")
include(":aws:sdk-codegen-test")
include(":aws:sdk")