Skip to content

Commit 8212446

Browse files
david-perezLukeMathWalker
authored andcommitted
Move converters from constraint violations into ValidationException to decorators (#2302)
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 ddc5777 commit 8212446

File tree

49 files changed

+1189
-351
lines changed

Some content is hidden

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

49 files changed

+1189
-351
lines changed

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

+7-4
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

+2-2
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(
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

-103
This file was deleted.

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

+3-2
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

+3-2
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()
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/EventStreamTestTools.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
1818
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
1919
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
2020
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
21+
import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker
2122
import software.amazon.smithy.rust.codegen.core.smithy.ErrorsModule
2223
import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule
2324
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
@@ -118,11 +119,15 @@ object EventStreamTestTools {
118119
val symbolProvider = codegenContext.symbolProvider
119120
val operationShape = model.expectShape(ShapeId.from("test#TestStreamOp")) as OperationShape
120121
val unionShape = model.expectShape(ShapeId.from("test#TestStream")) as UnionShape
122+
val walker = DirectedWalker(model)
121123

122124
val project = TestWorkspace.testProject(symbolProvider)
123125
val operationSymbol = symbolProvider.toSymbol(operationShape)
124126
project.withModule(ErrorsModule) {
125-
val errors = model.structureShapes.filter { shape -> shape.hasTrait<ErrorTrait>() }
127+
val errors = model.serviceShapes
128+
.flatMap { walker.walkShapes(it) }
129+
.filterIsInstance<StructureShape>()
130+
.filter { shape -> shape.hasTrait<ErrorTrait>() }
126131
requirements.renderOperationError(this, model, symbolProvider, operationSymbol, errors)
127132
requirements.renderOperationError(this, model, symbolProvider, symbolProvider.toSymbol(unionShape), errors)
128133
for (shape in errors) {

codegen-server/build.gradle.kts

+4
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/PythonServerCodegenVisitor.kt

+1-1
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/RustServerCodegenPythonPlugin.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.customizations.D
1919
import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolMetadataProvider
2020
import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolProvider
2121
import software.amazon.smithy.rust.codegen.server.smithy.DeriveEqAndHashSymbolMetadataProvider
22+
import software.amazon.smithy.rust.codegen.server.smithy.customizations.CustomValidationExceptionWithReasonDecorator
2223
import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations
24+
import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator
2325
import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator
2426
import java.util.logging.Level
2527
import java.util.logging.Logger
@@ -50,7 +52,10 @@ class RustServerCodegenPythonPlugin : SmithyBuildPlugin {
5052
val codegenDecorator: CombinedServerCodegenDecorator =
5153
CombinedServerCodegenDecorator.fromClasspath(
5254
context,
53-
CombinedServerCodegenDecorator(DECORATORS + ServerRequiredCustomizations()),
55+
ServerRequiredCustomizations(),
56+
SmithyValidationExceptionDecorator(),
57+
CustomValidationExceptionWithReasonDecorator(),
58+
*DECORATORS,
5459
)
5560

5661
// 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/customizations/PythonServerCodegenDecorator.kt

+1-1
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.

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.core.util.dq
1717
import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCargoDependency
1818
import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext
1919
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerEnumGenerator
20+
import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator
2021

2122
/**
2223
* To share enums defined in Rust with Python, `pyo3` provides the `PyClass` trait.
@@ -27,7 +28,8 @@ class PythonServerEnumGenerator(
2728
codegenContext: ServerCodegenContext,
2829
private val writer: RustWriter,
2930
shape: StringShape,
30-
) : ServerEnumGenerator(codegenContext, writer, shape) {
31+
validationExceptionConversionGenerator: ValidationExceptionConversionGenerator,
32+
) : ServerEnumGenerator(codegenContext, writer, shape, validationExceptionConversionGenerator) {
3133

3234
private val pyO3 = PythonServerCargoDependency.PyO3.toType()
3335

0 commit comments

Comments
 (0)