Skip to content

Commit f0aa96b

Browse files
committed
Move converters from constraint violations into ValidationException to decorators
This allows "easily" converting to other custom validation exception shapes by implementing a decorator. As a simple example, this PR adds a `CustomValidationExceptionWithReasonDecorator` decorator that is able to convert into a shape that is very similar to `smithy.framework#ValidationException`, but that has an additional `reason` field. The decorator can be enabled via the newly added `experimentalCustomValidationExceptionWithReasonPleaseDoNotUse` codegen config flag. This effectively provides a way for users to use custom validation exceptions without having to wait for the full implementation of #2053, provided they're interested enough to write a decorator in a JVM language. This mechanism is _experimental_ and will be removed once full support for custom validation exceptions as described in #2053 lands, hence why the configuration key is strongly worded in this respect. This commit also ports the mechanism to run codegen integration tests within Kotlin unit tests for client SDKs to the server. See #1956 for details. The custom validation exception decorator is tested this way.
1 parent 7bf9251 commit f0aa96b

File tree

50 files changed

+1199
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1199
-362
lines changed

aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
1313
import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings
1414
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation
1515
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
16+
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
1617
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
1718
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
1819
import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings
@@ -39,9 +40,10 @@ fun awsSdkIntegrationTest(
3940
test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
4041
) =
4142
clientIntegrationTest(
42-
model, runtimeConfig = AwsTestRuntimeConfig,
43-
additionalSettings = ObjectNode.builder()
44-
.withMember(
43+
model,
44+
IntegrationTestParams(
45+
runtimeConfig = AwsTestRuntimeConfig,
46+
additionalSettings = ObjectNode.builder().withMember(
4547
"customizationConfig",
4648
ObjectNode.builder()
4749
.withMember(
@@ -51,6 +53,7 @@ fun awsSdkIntegrationTest(
5153
.build(),
5254
).build(),
5355
)
54-
.withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
56+
.withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
57+
),
5558
test = test,
5659
)

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.customize.NoOpEventStre
1616
import software.amazon.smithy.rust.codegen.client.smithy.customize.RequiredCustomizations
1717
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator
1818
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientDecorator
19-
import software.amazon.smithy.rust.codegen.client.testutil.DecoratableBuildPlugin
19+
import software.amazon.smithy.rust.codegen.client.testutil.ClientDecoratableBuildPlugin
2020
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.NonExhaustive
2121
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider
2222
import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider
@@ -36,7 +36,7 @@ import java.util.logging.Logger
3636
* `resources/META-INF.services/software.amazon.smithy.build.SmithyBuildPlugin` refers to this class by name which
3737
* enables the smithy-build plugin to invoke `execute` with all Smithy plugin context + models.
3838
*/
39-
class RustClientCodegenPlugin : DecoratableBuildPlugin() {
39+
class RustClientCodegenPlugin : ClientDecoratableBuildPlugin() {
4040
override fun getName(): String = "rust-client-codegen"
4141

4242
override fun executeWithDecorator(
@@ -66,10 +66,10 @@ class RustClientCodegenPlugin : DecoratableBuildPlugin() {
6666
}
6767

6868
companion object {
69-
/** SymbolProvider
69+
/**
7070
* When generating code, smithy types need to be converted into Rust types—that is the core role of the symbol provider
7171
*
72-
* The Symbol provider is composed of a base `SymbolVisitor` which handles the core functionality, then is layered
72+
* The Symbol provider is composed of a base [SymbolVisitor] which handles the core functionality, then is layered
7373
* with other symbol providers, documented inline, to handle the full scope of Smithy types.
7474
*/
7575
fun baseSymbolProvider(model: Model, serviceShape: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig) =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.client.testutil
7+
8+
import software.amazon.smithy.build.PluginContext
9+
import software.amazon.smithy.build.SmithyBuildPlugin
10+
import software.amazon.smithy.model.Model
11+
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
12+
import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
13+
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
14+
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
15+
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
16+
import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest
17+
import java.nio.file.Path
18+
19+
fun clientIntegrationTest(
20+
model: Model,
21+
params: IntegrationTestParams = IntegrationTestParams(),
22+
additionalDecorators: List<ClientCodegenDecorator> = listOf(),
23+
test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
24+
): Path {
25+
fun invokeRustCodegenPlugin(ctx: PluginContext) {
26+
val codegenDecorator = object : ClientCodegenDecorator {
27+
override val name: String = "Add tests"
28+
override val order: Byte = 0
29+
30+
override fun classpathDiscoverable(): Boolean = false
31+
32+
override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
33+
test(codegenContext, rustCrate)
34+
}
35+
}
36+
RustClientCodegenPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray())
37+
}
38+
return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin)
39+
}
40+
41+
/**
42+
* A `SmithyBuildPlugin` that accepts an additional decorator.
43+
*
44+
* This exists to allow tests to easily customize the _real_ build without needing to list out customizations
45+
* or attempt to manually discover them from the path.
46+
*/
47+
abstract class ClientDecoratableBuildPlugin : SmithyBuildPlugin {
48+
abstract fun executeWithDecorator(
49+
context: PluginContext,
50+
vararg decorator: ClientCodegenDecorator,
51+
)
52+
53+
override fun execute(context: PluginContext) {
54+
executeWithDecorator(context)
55+
}
56+
}

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt

Lines changed: 0 additions & 103 deletions
This file was deleted.

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
1919
import software.amazon.smithy.rust.codegen.core.rustlang.writable
2020
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
2121
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
22+
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
2223
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
2324
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
2425

@@ -170,8 +171,8 @@ internal class HttpVersionListGeneratorTest {
170171

171172
clientIntegrationTest(
172173
model,
173-
listOf(FakeSigningDecorator()),
174-
addModuleToEventStreamAllowList = true,
174+
IntegrationTestParams(addModuleToEventStreamAllowList = true),
175+
additionalDecorators = listOf(FakeSigningDecorator()),
175176
) { clientCodegenContext, rustCrate ->
176177
val moduleName = clientCodegenContext.moduleUseName()
177178
rustCrate.integrationTest("validate_eventstream_http") {

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test
1111
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
1212
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
1313
import software.amazon.smithy.rust.codegen.core.rustlang.rust
14+
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
1415
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
1516
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
1617
import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
@@ -123,8 +124,8 @@ class EndpointsDecoratorTest {
123124
fun `set an endpoint in the property bag`() {
124125
val testDir = clientIntegrationTest(
125126
model,
126-
// just run integration tests
127-
command = { "cargo test --test *".runWithWarnings(it) },
127+
// Just run integration tests.
128+
IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }),
128129
) { clientCodegenContext, rustCrate ->
129130
rustCrate.integrationTest("endpoint_params_test") {
130131
val moduleName = clientCodegenContext.moduleUseName()

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ class RustWriter private constructor(
481481
* Callers must take care to use [this] when writing to ensure code is written to the right place:
482482
* ```kotlin
483483
* val writer = RustWriter.forModule("model")
484-
* writer.withModule(RustModule.public("nested")) {
484+
* writer.withInlineModule(RustModule.public("nested")) {
485485
* Generator(...).render(this) // GOOD
486486
* Generator(...).render(writer) // WRONG!
487487
* }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.core.testutil
7+
8+
import software.amazon.smithy.build.PluginContext
9+
import software.amazon.smithy.model.Model
10+
import software.amazon.smithy.model.node.ObjectNode
11+
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
12+
import software.amazon.smithy.rust.codegen.core.util.runCommand
13+
import java.io.File
14+
import java.nio.file.Path
15+
16+
/**
17+
* A helper class holding common data with defaults that is threaded through several functions, to make their
18+
* signatures shorter.
19+
*/
20+
data class IntegrationTestParams(
21+
val addModuleToEventStreamAllowList: Boolean = false,
22+
val service: String? = null,
23+
val runtimeConfig: RuntimeConfig? = null,
24+
val additionalSettings: ObjectNode = ObjectNode.builder().build(),
25+
val overrideTestDir: File? = null,
26+
val command: ((Path) -> Unit)? = null,
27+
)
28+
29+
/**
30+
* Run cargo test on a true, end-to-end, codegen product of a given model.
31+
*/
32+
fun codegenIntegrationTest(model: Model, params: IntegrationTestParams, invokePlugin: (PluginContext) -> Unit): Path {
33+
val (ctx, testDir) = generatePluginContext(
34+
model,
35+
params.additionalSettings,
36+
params.addModuleToEventStreamAllowList,
37+
params.service,
38+
params.runtimeConfig,
39+
params.overrideTestDir,
40+
)
41+
invokePlugin(ctx)
42+
ctx.fileManifest.printGeneratedFiles()
43+
params.command?.invoke(testDir) ?: "cargo test".runCommand(testDir, environment = mapOf("RUSTFLAGS" to "-D warnings"))
44+
return testDir
45+
}

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ fun generatePluginContext(
190190
)
191191
}
192192

193-
val settings = settingsBuilder.merge(additionalSettings)
194-
.build()
193+
val settings = settingsBuilder.merge(additionalSettings).build()
195194
val pluginContext = PluginContext.builder().model(model).fileManifest(manifest).settings(settings).build()
196195
return pluginContext to testPath
197196
}

codegen-server-test/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels ->
5050
imports = listOf("$commonModels/constraints.smithy"),
5151
extraConfig = """, "codegen": { "publicConstrainedTypes": false } """,
5252
),
53+
CodegenTest(
54+
"com.amazonaws.constraints#CustomValidationExceptionsExperimental",
55+
"custom_validation_exceptions_experimental",
56+
imports = listOf("$commonModels/custom-validation-exceptions-experimental.smithy"),
57+
extraConfig = """, "codegen": { "experimentalCustomValidationExceptionWithReasonPleaseDoNotUse": "com.amazonaws.constraints#ValidationException" } """.trimMargin(),
58+
),
5359
CodegenTest(
5460
"com.amazonaws.constraints#UniqueItemsService",
5561
"unique_items",

codegen-server/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ dependencies {
2626
implementation(project(":codegen-core"))
2727
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
2828
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
29+
30+
// `smithy.framework#ValidationException` is defined here, which is used in `constraints.smithy`, which is used
31+
// in `CustomValidationExceptionWithReasonDecoratorTest`.
32+
testImplementation("software.amazon.smithy:smithy-validation-model:$smithyVersion")
2933
}
3034

3135
tasks.compileKotlin { kotlinOptions.jvmTarget = "1.8" }

codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonCodegenServerPlugin.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.customizations.D
2020
import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolMetadataProvider
2121
import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolProvider
2222
import software.amazon.smithy.rust.codegen.server.smithy.DeriveEqAndHashSymbolMetadataProvider
23+
import software.amazon.smithy.rust.codegen.server.smithy.customizations.CustomValidationExceptionWithReasonDecorator
2324
import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations
25+
import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator
2426
import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator
2527
import java.util.logging.Level
2628
import java.util.logging.Logger
@@ -47,7 +49,10 @@ class PythonCodegenServerPlugin : SmithyBuildPlugin {
4749
val codegenDecorator: CombinedServerCodegenDecorator =
4850
CombinedServerCodegenDecorator.fromClasspath(
4951
context,
50-
CombinedServerCodegenDecorator(DECORATORS + ServerRequiredCustomizations()),
52+
ServerRequiredCustomizations(),
53+
SmithyValidationExceptionDecorator(),
54+
CustomValidationExceptionWithReasonDecorator(),
55+
*DECORATORS,
5156
)
5257

5358
// PythonServerCodegenVisitor is the main driver of code generation that traverses the model and generates code

codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class PythonServerCodegenVisitor(
132132
*/
133133
override fun stringShape(shape: StringShape) {
134134
fun pythonServerEnumGeneratorFactory(codegenContext: ServerCodegenContext, writer: RustWriter, shape: StringShape) =
135-
PythonServerEnumGenerator(codegenContext, writer, shape)
135+
PythonServerEnumGenerator(codegenContext, writer, shape, validationExceptionConversionGenerator)
136136
stringShape(shape, ::pythonServerEnumGeneratorFactory)
137137
}
138138

codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/customizations/PythonServerCodegenDecorator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class PyO3ExtensionModuleDecorator : ServerCodegenDecorator {
134134
}
135135
}
136136

137-
val DECORATORS = listOf(
137+
val DECORATORS = arrayOf(
138138
/**
139139
* Add the [InternalServerError] error to all operations.
140140
* This is done because the Python interpreter can raise exceptions during execution.

0 commit comments

Comments
 (0)