From f0aa96bb1addb5b8c5d6aae73ac03c7c58cebfb8 Mon Sep 17 00:00:00 2001 From: david-perez Date: Fri, 3 Feb 2023 21:39:30 +0100 Subject: [PATCH 1/5] 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. --- .../amazon/smithy/rustsdk/TestUtil.kt | 11 +- .../client/smithy/RustClientCodegenPlugin.kt | 8 +- .../testutil/ClientCodegenIntegrationTest.kt | 56 ++++ .../client/testutil/CodegenIntegrationTest.kt | 103 ------ .../HttpVersionListGeneratorTest.kt | 5 +- .../client/endpoint/EndpointsDecoratorTest.kt | 5 +- .../rust/codegen/core/rustlang/RustWriter.kt | 2 +- .../core/testutil/CodegenIntegrationTest.kt | 45 +++ .../smithy/rust/codegen/core/testutil/Rust.kt | 3 +- codegen-server-test/build.gradle.kts | 6 + codegen-server/build.gradle.kts | 4 + .../smithy/PythonCodegenServerPlugin.kt | 7 +- .../smithy/PythonServerCodegenVisitor.kt | 2 +- .../PythonServerCodegenDecorator.kt | 2 +- .../generators/PythonServerEnumGenerator.kt | 4 +- .../testutil/PythonServerTestHelpers.kt | 9 +- .../server/smithy/RustCodegenServerPlugin.kt | 38 ++- .../server/smithy/ServerCodegenVisitor.kt | 70 ++-- .../server/smithy/ServerRustSettings.kt | 9 + .../smithy/ValidateUnsupportedConstraints.kt | 10 +- ...mValidationExceptionWithReasonDecorator.kt | 313 ++++++++++++++++++ .../SmithyValidationExceptionDecorator.kt | 239 +++++++++++++ .../customize/ServerCodegenDecorator.kt | 9 +- .../CollectionConstraintViolationGenerator.kt | 26 +- .../generators/ConstrainedBlobGenerator.kt | 17 +- .../ConstrainedCollectionGenerator.kt | 8 +- .../generators/ConstrainedNumberGenerator.kt | 38 +-- .../generators/ConstrainedStringGenerator.kt | 20 +- .../MapConstraintViolationGenerator.kt | 48 +-- .../ServerBuilderConstraintViolations.kt | 23 +- .../generators/ServerBuilderGenerator.kt | 19 +- ...rGeneratorWithoutPublicConstrainedTypes.kt | 3 +- .../smithy/generators/ServerEnumGenerator.kt | 13 +- .../ValidationExceptionConversionGenerator.kt | 50 +++ .../testutil/ServerCodegenIntegrationTest.kt | 55 +++ .../smithy/testutil/ServerTestHelpers.kt | 3 +- .../server/smithy/ServerCodegenVisitorTest.kt | 9 +- ...ateUnsupportedConstraintsAreNotUsedTest.kt | 7 +- ...idationExceptionWithReasonDecoratorTest.kt | 109 ++++++ .../ConstrainedBlobGeneratorTest.kt | 15 +- .../ConstrainedCollectionGeneratorTest.kt | 9 +- .../generators/ConstrainedMapGeneratorTest.kt | 8 +- .../ConstrainedNumberGeneratorTest.kt | 15 +- .../ConstrainedStringGeneratorTest.kt | 37 ++- .../ServerBuilderDefaultValuesTest.kt | 19 +- .../generators/ServerBuilderGeneratorTest.kt | 3 +- .../generators/ServerEnumGeneratorTest.kt | 24 +- .../UnconstrainedCollectionGeneratorTest.kt | 9 +- .../UnconstrainedMapGeneratorTest.kt | 8 +- .../ServerEventStreamBaseRequirements.kt | 6 +- 50 files changed, 1199 insertions(+), 362 deletions(-) create mode 100644 codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt delete mode 100644 codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt create mode 100644 codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/CodegenIntegrationTest.kt create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecorator.kt create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/SmithyValidationExceptionDecorator.kt create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ValidationExceptionConversionGenerator.kt create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt create mode 100644 codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecoratorTest.kt diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt index 8db0d6a1dff..9329153ffe2 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt @@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings @@ -39,9 +40,10 @@ fun awsSdkIntegrationTest( test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> }, ) = clientIntegrationTest( - model, runtimeConfig = AwsTestRuntimeConfig, - additionalSettings = ObjectNode.builder() - .withMember( + model, + IntegrationTestParams( + runtimeConfig = AwsTestRuntimeConfig, + additionalSettings = ObjectNode.builder().withMember( "customizationConfig", ObjectNode.builder() .withMember( @@ -51,6 +53,7 @@ fun awsSdkIntegrationTest( .build(), ).build(), ) - .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(), + .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(), + ), test = test, ) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt index 0189a4cb65d..aa94e25b77f 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt @@ -16,7 +16,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.customize.NoOpEventStre import software.amazon.smithy.rust.codegen.client.smithy.customize.RequiredCustomizations import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientDecorator -import software.amazon.smithy.rust.codegen.client.testutil.DecoratableBuildPlugin +import software.amazon.smithy.rust.codegen.client.testutil.ClientDecoratableBuildPlugin import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.NonExhaustive import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider @@ -36,7 +36,7 @@ import java.util.logging.Logger * `resources/META-INF.services/software.amazon.smithy.build.SmithyBuildPlugin` refers to this class by name which * enables the smithy-build plugin to invoke `execute` with all Smithy plugin context + models. */ -class RustClientCodegenPlugin : DecoratableBuildPlugin() { +class RustClientCodegenPlugin : ClientDecoratableBuildPlugin() { override fun getName(): String = "rust-client-codegen" override fun executeWithDecorator( @@ -66,10 +66,10 @@ class RustClientCodegenPlugin : DecoratableBuildPlugin() { } companion object { - /** SymbolProvider + /** * When generating code, smithy types need to be converted into Rust types—that is the core role of the symbol provider * - * The Symbol provider is composed of a base `SymbolVisitor` which handles the core functionality, then is layered + * The Symbol provider is composed of a base [SymbolVisitor] which handles the core functionality, then is layered * with other symbol providers, documented inline, to handle the full scope of Smithy types. */ fun baseSymbolProvider(model: Model, serviceShape: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig) = diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt new file mode 100644 index 00000000000..138f43cd264 --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.testutil + +import software.amazon.smithy.build.PluginContext +import software.amazon.smithy.build.SmithyBuildPlugin +import software.amazon.smithy.model.Model +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams +import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest +import java.nio.file.Path + +fun clientIntegrationTest( + model: Model, + params: IntegrationTestParams = IntegrationTestParams(), + additionalDecorators: List = listOf(), + test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> }, +): Path { + fun invokeRustCodegenPlugin(ctx: PluginContext) { + val codegenDecorator = object : ClientCodegenDecorator { + override val name: String = "Add tests" + override val order: Byte = 0 + + override fun classpathDiscoverable(): Boolean = false + + override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { + test(codegenContext, rustCrate) + } + } + RustClientCodegenPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray()) + } + return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin) +} + +/** + * A `SmithyBuildPlugin` that accepts an additional decorator. + * + * This exists to allow tests to easily customize the _real_ build without needing to list out customizations + * or attempt to manually discover them from the path. + */ +abstract class ClientDecoratableBuildPlugin : SmithyBuildPlugin { + abstract fun executeWithDecorator( + context: PluginContext, + vararg decorator: ClientCodegenDecorator, + ) + + override fun execute(context: PluginContext) { + executeWithDecorator(context) + } +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt deleted file mode 100644 index c764e290595..00000000000 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.client.testutil - -import software.amazon.smithy.build.PluginContext -import software.amazon.smithy.build.SmithyBuildPlugin -import software.amazon.smithy.model.Model -import software.amazon.smithy.model.node.ObjectNode -import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext -import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin -import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig -import software.amazon.smithy.rust.codegen.core.smithy.RustCrate -import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext -import software.amazon.smithy.rust.codegen.core.testutil.printGeneratedFiles -import software.amazon.smithy.rust.codegen.core.util.runCommand -import java.io.File -import java.nio.file.Path - -/** - * Run cargo test on a true, end-to-end, codegen product of a given model. - * - * For test purposes, additional codegen decorators can also be composed. - */ -fun clientIntegrationTest( - model: Model, - additionalDecorators: List = listOf(), - addModuleToEventStreamAllowList: Boolean = false, - service: String? = null, - runtimeConfig: RuntimeConfig? = null, - additionalSettings: ObjectNode = ObjectNode.builder().build(), - command: ((Path) -> Unit)? = null, - test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> }, -): Path { - return codegenIntegrationTest( - model, - RustClientCodegenPlugin(), - additionalDecorators, - addModuleToEventStreamAllowList = addModuleToEventStreamAllowList, - service = service, - runtimeConfig = runtimeConfig, - additionalSettings = additionalSettings, - test = test, - command = command, - ) -} - -/** - * A Smithy BuildPlugin that accepts an additional decorator - * - * This exists to allow tests to easily customize the _real_ build without needing to list out customizations - * or attempt to manually discover them from the path - */ -abstract class DecoratableBuildPlugin : SmithyBuildPlugin { - abstract fun executeWithDecorator( - context: PluginContext, - vararg decorator: ClientCodegenDecorator, - ) - - override fun execute(context: PluginContext) { - executeWithDecorator(context) - } -} - -// TODO(https://github.com/awslabs/smithy-rs/issues/1864): move to core once CodegenDecorator is in core -private fun codegenIntegrationTest( - model: Model, - buildPlugin: DecoratableBuildPlugin, - additionalDecorators: List, - additionalSettings: ObjectNode = ObjectNode.builder().build(), - addModuleToEventStreamAllowList: Boolean = false, - service: String? = null, - runtimeConfig: RuntimeConfig? = null, - overrideTestDir: File? = null, test: (ClientCodegenContext, RustCrate) -> Unit, - command: ((Path) -> Unit)? = null, -): Path { - val (ctx, testDir) = generatePluginContext( - model, - additionalSettings, - addModuleToEventStreamAllowList, - service, - runtimeConfig, - overrideTestDir, - ) - - val codegenDecorator = object : ClientCodegenDecorator { - override val name: String = "Add tests" - override val order: Byte = 0 - - override fun classpathDiscoverable(): Boolean = false - - override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { - test(codegenContext, rustCrate) - } - } - buildPlugin.executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray()) - ctx.fileManifest.printGeneratedFiles() - command?.invoke(testDir) ?: "cargo test".runCommand(testDir, environment = mapOf("RUSTFLAGS" to "-D warnings")) - return testDir -} diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt index 0c57ba622a7..0b860545aa1 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -170,8 +171,8 @@ internal class HttpVersionListGeneratorTest { clientIntegrationTest( model, - listOf(FakeSigningDecorator()), - addModuleToEventStreamAllowList = true, + IntegrationTestParams(addModuleToEventStreamAllowList = true), + additionalDecorators = listOf(FakeSigningDecorator()), ) { clientCodegenContext, rustCrate -> val moduleName = clientCodegenContext.moduleUseName() rustCrate.integrationTest("validate_eventstream_http") { diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt index d05f3b21de9..d9b398f0ca3 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings @@ -123,8 +124,8 @@ class EndpointsDecoratorTest { fun `set an endpoint in the property bag`() { val testDir = clientIntegrationTest( model, - // just run integration tests - command = { "cargo test --test *".runWithWarnings(it) }, + // Just run integration tests. + IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }), ) { clientCodegenContext, rustCrate -> rustCrate.integrationTest("endpoint_params_test") { val moduleName = clientCodegenContext.moduleUseName() diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index 2b4a17c193b..d4a8ca52974 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -481,7 +481,7 @@ class RustWriter private constructor( * Callers must take care to use [this] when writing to ensure code is written to the right place: * ```kotlin * val writer = RustWriter.forModule("model") - * writer.withModule(RustModule.public("nested")) { + * writer.withInlineModule(RustModule.public("nested")) { * Generator(...).render(this) // GOOD * Generator(...).render(writer) // WRONG! * } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/CodegenIntegrationTest.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/CodegenIntegrationTest.kt new file mode 100644 index 00000000000..d1f208c3931 --- /dev/null +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/CodegenIntegrationTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.core.testutil + +import software.amazon.smithy.build.PluginContext +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.node.ObjectNode +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.core.util.runCommand +import java.io.File +import java.nio.file.Path + +/** + * A helper class holding common data with defaults that is threaded through several functions, to make their + * signatures shorter. + */ +data class IntegrationTestParams( + val addModuleToEventStreamAllowList: Boolean = false, + val service: String? = null, + val runtimeConfig: RuntimeConfig? = null, + val additionalSettings: ObjectNode = ObjectNode.builder().build(), + val overrideTestDir: File? = null, + val command: ((Path) -> Unit)? = null, +) + +/** + * Run cargo test on a true, end-to-end, codegen product of a given model. + */ +fun codegenIntegrationTest(model: Model, params: IntegrationTestParams, invokePlugin: (PluginContext) -> Unit): Path { + val (ctx, testDir) = generatePluginContext( + model, + params.additionalSettings, + params.addModuleToEventStreamAllowList, + params.service, + params.runtimeConfig, + params.overrideTestDir, + ) + invokePlugin(ctx) + ctx.fileManifest.printGeneratedFiles() + params.command?.invoke(testDir) ?: "cargo test".runCommand(testDir, environment = mapOf("RUSTFLAGS" to "-D warnings")) + return testDir +} diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt index a8567c4db47..65e48883a4b 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt @@ -190,8 +190,7 @@ fun generatePluginContext( ) } - val settings = settingsBuilder.merge(additionalSettings) - .build() + val settings = settingsBuilder.merge(additionalSettings).build() val pluginContext = PluginContext.builder().model(model).fileManifest(manifest).settings(settings).build() return pluginContext to testPath } diff --git a/codegen-server-test/build.gradle.kts b/codegen-server-test/build.gradle.kts index b2841452c14..50bc399574e 100644 --- a/codegen-server-test/build.gradle.kts +++ b/codegen-server-test/build.gradle.kts @@ -50,6 +50,12 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels -> imports = listOf("$commonModels/constraints.smithy"), extraConfig = """, "codegen": { "publicConstrainedTypes": false } """, ), + CodegenTest( + "com.amazonaws.constraints#CustomValidationExceptionsExperimental", + "custom_validation_exceptions_experimental", + imports = listOf("$commonModels/custom-validation-exceptions-experimental.smithy"), + extraConfig = """, "codegen": { "experimentalCustomValidationExceptionWithReasonPleaseDoNotUse": "com.amazonaws.constraints#ValidationException" } """.trimMargin(), + ), CodegenTest( "com.amazonaws.constraints#UniqueItemsService", "unique_items", diff --git a/codegen-server/build.gradle.kts b/codegen-server/build.gradle.kts index 8dd6dc5d186..f5b2f5bc29a 100644 --- a/codegen-server/build.gradle.kts +++ b/codegen-server/build.gradle.kts @@ -26,6 +26,10 @@ dependencies { implementation(project(":codegen-core")) implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion") + + // `smithy.framework#ValidationException` is defined here, which is used in `constraints.smithy`, which is used + // in `CustomValidationExceptionWithReasonDecoratorTest`. + testImplementation("software.amazon.smithy:smithy-validation-model:$smithyVersion") } tasks.compileKotlin { kotlinOptions.jvmTarget = "1.8" } diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonCodegenServerPlugin.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonCodegenServerPlugin.kt index 7e277185f7b..ee3e522777d 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonCodegenServerPlugin.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonCodegenServerPlugin.kt @@ -20,7 +20,9 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.customizations.D import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolMetadataProvider import software.amazon.smithy.rust.codegen.server.smithy.ConstrainedShapeSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.DeriveEqAndHashSymbolMetadataProvider +import software.amazon.smithy.rust.codegen.server.smithy.customizations.CustomValidationExceptionWithReasonDecorator import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator import java.util.logging.Level import java.util.logging.Logger @@ -47,7 +49,10 @@ class PythonCodegenServerPlugin : SmithyBuildPlugin { val codegenDecorator: CombinedServerCodegenDecorator = CombinedServerCodegenDecorator.fromClasspath( context, - CombinedServerCodegenDecorator(DECORATORS + ServerRequiredCustomizations()), + ServerRequiredCustomizations(), + SmithyValidationExceptionDecorator(), + CustomValidationExceptionWithReasonDecorator(), + *DECORATORS, ) // PythonServerCodegenVisitor is the main driver of code generation that traverses the model and generates code diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt index 8fd2a07f583..80055c9a68a 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt @@ -132,7 +132,7 @@ class PythonServerCodegenVisitor( */ override fun stringShape(shape: StringShape) { fun pythonServerEnumGeneratorFactory(codegenContext: ServerCodegenContext, writer: RustWriter, shape: StringShape) = - PythonServerEnumGenerator(codegenContext, writer, shape) + PythonServerEnumGenerator(codegenContext, writer, shape, validationExceptionConversionGenerator) stringShape(shape, ::pythonServerEnumGeneratorFactory) } diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/customizations/PythonServerCodegenDecorator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/customizations/PythonServerCodegenDecorator.kt index ff07ba1207e..8de0a3f056e 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/customizations/PythonServerCodegenDecorator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/customizations/PythonServerCodegenDecorator.kt @@ -134,7 +134,7 @@ class PyO3ExtensionModuleDecorator : ServerCodegenDecorator { } } -val DECORATORS = listOf( +val DECORATORS = arrayOf( /** * Add the [InternalServerError] error to all operations. * This is done because the Python interpreter can raise exceptions during execution. diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt index 90bdcc6e444..55a4a083a1b 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt @@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerEnumGenerator +import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator /** * To share enums defined in Rust with Python, `pyo3` provides the `PyClass` trait. @@ -27,7 +28,8 @@ class PythonServerEnumGenerator( codegenContext: ServerCodegenContext, private val writer: RustWriter, shape: StringShape, -) : ServerEnumGenerator(codegenContext, writer, shape) { + validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, +) : ServerEnumGenerator(codegenContext, writer, shape, validationExceptionConversionGenerator) { private val pyO3 = PythonServerCargoDependency.PyO3.toType() diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/testutil/PythonServerTestHelpers.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/testutil/PythonServerTestHelpers.kt index e38e1ae67c3..669279c8d53 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/testutil/PythonServerTestHelpers.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/testutil/PythonServerTestHelpers.kt @@ -13,7 +13,9 @@ import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext import software.amazon.smithy.rust.codegen.core.util.runCommand import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCodegenVisitor import software.amazon.smithy.rust.codegen.server.python.smithy.customizations.DECORATORS +import software.amazon.smithy.rust.codegen.server.smithy.customizations.CustomValidationExceptionWithReasonDecorator import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator import java.io.File import java.nio.file.Path @@ -25,10 +27,13 @@ fun generatePythonServerPluginContext(model: Model) = generatePluginContext(model, runtimeConfig = TestRuntimeConfig) fun executePythonServerCodegenVisitor(pluginCtx: PluginContext) { - val codegenDecorator: CombinedServerCodegenDecorator = + val codegenDecorator = CombinedServerCodegenDecorator.fromClasspath( pluginCtx, - CombinedServerCodegenDecorator(DECORATORS + ServerRequiredCustomizations()), + *DECORATORS, + ServerRequiredCustomizations(), + SmithyValidationExceptionDecorator(), + CustomValidationExceptionWithReasonDecorator(), ) PythonServerCodegenVisitor(pluginCtx, codegenDecorator).execute() } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt index 8a1dc17e546..e2ada81f17a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.rust.codegen.server.smithy import software.amazon.smithy.build.PluginContext -import software.amazon.smithy.build.SmithyBuildPlugin import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape @@ -18,8 +17,12 @@ import software.amazon.smithy.rust.codegen.core.smithy.StreamingShapeMetadataPro import software.amazon.smithy.rust.codegen.core.smithy.StreamingShapeSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig +import software.amazon.smithy.rust.codegen.server.smithy.customizations.CustomValidationExceptionWithReasonDecorator import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator +import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +import software.amazon.smithy.rust.codegen.server.smithy.testutil.ServerDecoratableBuildPlugin import java.util.logging.Level import java.util.logging.Logger @@ -30,33 +33,34 @@ import java.util.logging.Logger * `resources/META-INF.services/software.amazon.smithy.build.SmithyBuildPlugin` refers to this class by name which * enables the smithy-build plugin to invoke `execute` with all Smithy plugin context + models. */ -class RustCodegenServerPlugin : SmithyBuildPlugin { +class RustCodegenServerPlugin : ServerDecoratableBuildPlugin() { private val logger = Logger.getLogger(javaClass.name) override fun getName(): String = "rust-server-codegen" - override fun execute(context: PluginContext) { - // Suppress extremely noisy logs about reserved words + /** + * See [software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin]. + */ + override fun executeWithDecorator( + context: PluginContext, + vararg decorator: ServerCodegenDecorator, + ) { Logger.getLogger(ReservedWordSymbolProvider::class.java.name).level = Level.OFF - // Discover [RustCodegenDecorators] on the classpath. [RustCodegenDecorator] returns different types of - // customizations. A customization is a function of: - // - location (e.g. the mutate section of an operation) - // - context (e.g. the of the operation) - // - writer: The active RustWriter at the given location - val codegenDecorator: CombinedServerCodegenDecorator = - CombinedServerCodegenDecorator.fromClasspath(context, ServerRequiredCustomizations()) - - // ServerCodegenVisitor is the main driver of code generation that traverses the model and generates code + val codegenDecorator = + CombinedServerCodegenDecorator.fromClasspath( + context, + ServerRequiredCustomizations(), + SmithyValidationExceptionDecorator(), + CustomValidationExceptionWithReasonDecorator(), + *decorator, + ) logger.info("Loaded plugin to generate pure Rust bindings for the server SDK") ServerCodegenVisitor(context, codegenDecorator).execute() } companion object { /** - * When generating code, smithy types need to be converted into Rust types—that is the core role of the symbol provider. - * - * The Symbol provider is composed of a base [SymbolVisitor] which handles the core functionality, then is layered - * with other symbol providers, documented inline, to handle the full scope of Smithy types. + * See [software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin]. */ fun baseSymbolProvider( model: Model, diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index 607aad2158e..213f69b47e6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.model.shapes.IntegerShape import software.amazon.smithy.model.shapes.ListShape import software.amazon.smithy.model.shapes.LongShape import software.amazon.smithy.model.shapes.MapShape +import software.amazon.smithy.model.shapes.NumberShape import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.SetShape @@ -74,6 +75,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerStruct import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedCollectionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedMapGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedUnionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerProtocolLoader @@ -101,6 +103,7 @@ open class ServerCodegenVisitor( protected var codegenContext: ServerCodegenContext protected var protocolGeneratorFactory: ProtocolGeneratorFactory protected var protocolGenerator: ServerProtocolGenerator + protected var validationExceptionConversionGenerator: ValidationExceptionConversionGenerator init { val symbolVisitorConfig = @@ -144,6 +147,9 @@ open class ServerCodegenVisitor( serverSymbolProviders.pubCrateConstrainedShapeSymbolProvider, ) + // We can use a not-null assertion because [CombinedServerCodegenDecorator] returns a not null value. + validationExceptionConversionGenerator = codegenDecorator.validationExceptionConversion(codegenContext)!! + rustCrate = RustCrate(context.fileManifest, codegenContext.symbolProvider, settings.codegenConfig) protocolGenerator = protocolGeneratorFactory.buildProtocolGenerator(codegenContext) } @@ -195,10 +201,12 @@ open class ServerCodegenVisitor( "[rust-server-codegen] Generating Rust server for service $service, protocol ${codegenContext.protocol}", ) + val validationExceptionShapeId = validationExceptionConversionGenerator.shapeId for (validationResult in listOf( validateOperationsWithConstrainedInputHaveValidationExceptionAttached( model, service, + validationExceptionShapeId, ), validateUnsupportedConstraints(model, service, codegenContext.settings.codegenConfig), )) { @@ -260,7 +268,7 @@ open class ServerCodegenVisitor( writer: RustWriter, ) { if (codegenContext.settings.codegenConfig.publicConstrainedTypes || shape.isReachableFromOperationInput()) { - val serverBuilderGenerator = ServerBuilderGenerator(codegenContext, shape) + val serverBuilderGenerator = ServerBuilderGenerator(codegenContext, shape, validationExceptionConversionGenerator) serverBuilderGenerator.render(writer) if (codegenContext.settings.codegenConfig.publicConstrainedTypes) { @@ -281,7 +289,7 @@ open class ServerCodegenVisitor( if (!codegenContext.settings.codegenConfig.publicConstrainedTypes) { val serverBuilderGeneratorWithoutPublicConstrainedTypes = - ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, shape) + ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, shape, validationExceptionConversionGenerator) serverBuilderGeneratorWithoutPublicConstrainedTypes.render(writer) writer.implBlock(shape, codegenContext.symbolProvider) { @@ -334,7 +342,13 @@ open class ServerCodegenVisitor( if (isDirectlyConstrained || renderUnconstrainedList) { rustCrate.withModule(ModelsModule) { - CollectionConstraintViolationGenerator(codegenContext, this, shape, constraintsInfo).render() + CollectionConstraintViolationGenerator( + codegenContext, + this, + shape, + constraintsInfo, + validationExceptionConversionGenerator, + ).render() } } } @@ -374,7 +388,12 @@ open class ServerCodegenVisitor( if (isDirectlyConstrained || renderUnconstrainedMap) { rustCrate.withModule(ModelsModule) { - MapConstraintViolationGenerator(codegenContext, this, shape).render() + MapConstraintViolationGenerator( + codegenContext, + this, + shape, + validationExceptionConversionGenerator, + ).render() } } } @@ -386,42 +405,19 @@ open class ServerCodegenVisitor( */ override fun stringShape(shape: StringShape) { fun serverEnumGeneratorFactory(codegenContext: ServerCodegenContext, writer: RustWriter, shape: StringShape) = - ServerEnumGenerator(codegenContext, writer, shape) + ServerEnumGenerator(codegenContext, writer, shape, validationExceptionConversionGenerator) stringShape(shape, ::serverEnumGeneratorFactory) } - override fun integerShape(shape: IntegerShape) { - if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - logger.info("[rust-server-codegen] Generating a constrained integer $shape") - rustCrate.withModule(ModelsModule) { - ConstrainedNumberGenerator(codegenContext, this, shape).render() - } - } - } - - override fun shortShape(shape: ShortShape) { - if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - logger.info("[rust-server-codegen] Generating a constrained short $shape") - rustCrate.withModule(ModelsModule) { - ConstrainedNumberGenerator(codegenContext, this, shape).render() - } - } - } - - override fun longShape(shape: LongShape) { - if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - logger.info("[rust-server-codegen] Generating a constrained long $shape") - rustCrate.withModule(ModelsModule) { - ConstrainedNumberGenerator(codegenContext, this, shape).render() - } - } - } - - override fun byteShape(shape: ByteShape) { + override fun integerShape(shape: IntegerShape) = integralShape(shape) + override fun shortShape(shape: ShortShape) = integralShape(shape) + override fun longShape(shape: LongShape) = integralShape(shape) + override fun byteShape(shape: ByteShape) = integralShape(shape) + private fun integralShape(shape: NumberShape) { if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - logger.info("[rust-server-codegen] Generating a constrained byte $shape") + logger.info("[rust-server-codegen] Generating a constrained integral $shape") rustCrate.withModule(ModelsModule) { - ConstrainedNumberGenerator(codegenContext, this, shape).render() + ConstrainedNumberGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() } } } @@ -450,7 +446,7 @@ open class ServerCodegenVisitor( } else if (!shape.hasTrait() && shape.isDirectlyConstrained(codegenContext.symbolProvider)) { logger.info("[rust-server-codegen] Generating a constrained string $shape") rustCrate.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, shape).render() + ConstrainedStringGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() } } } @@ -544,7 +540,7 @@ open class ServerCodegenVisitor( if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { rustCrate.withModule(ModelsModule) { - ConstrainedBlobGenerator(codegenContext, this, shape).render() + ConstrainedBlobGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt index dbfc8356a29..e3c47ad3156 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt @@ -83,12 +83,20 @@ data class ServerCodegenConfig( override val debugMode: Boolean = defaultDebugMode, val publicConstrainedTypes: Boolean = defaultPublicConstrainedTypes, val ignoreUnsupportedConstraints: Boolean = defaultIgnoreUnsupportedConstraints, + /** + * A flag to enable _experimental_ support for custom validation exceptions via the + * [CustomValidationExceptionWithReasonDecorator] decorator. + * TODO(https://github.com/awslabs/smithy-rs/pull/2053): this will go away once we implement the RFC, when users will be + * able to define the converters in their Rust application code. + */ + val experimentalCustomValidationExceptionWithReasonPleaseDoNotUse: String? = defaultExperimentalCustomValidationExceptionWithReasonPleaseDoNotUse, ) : CoreCodegenConfig( formatTimeoutSeconds, debugMode, ) { companion object { private const val defaultPublicConstrainedTypes = true private const val defaultIgnoreUnsupportedConstraints = false + private val defaultExperimentalCustomValidationExceptionWithReasonPleaseDoNotUse = null fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional) = if (node.isPresent) { @@ -97,6 +105,7 @@ data class ServerCodegenConfig( debugMode = coreCodegenConfig.debugMode, publicConstrainedTypes = node.get().getBooleanMemberOrDefault("publicConstrainedTypes", defaultPublicConstrainedTypes), ignoreUnsupportedConstraints = node.get().getBooleanMemberOrDefault("ignoreUnsupportedConstraints", defaultIgnoreUnsupportedConstraints), + experimentalCustomValidationExceptionWithReasonPleaseDoNotUse = node.get().getStringMemberOrDefault("experimentalCustomValidationExceptionWithReasonPleaseDoNotUse", defaultExperimentalCustomValidationExceptionWithReasonPleaseDoNotUse), ) } else { ServerCodegenConfig( diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt index 8bf6f928d9c..fd3f259bc7b 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt @@ -134,9 +134,13 @@ data class ValidationResult(val shouldAbort: Boolean, val messages: List for #{RequestRejection} { + fn from(constraint_violation: ConstraintViolation) -> Self { + let first_validation_exception_field = constraint_violation.as_validation_exception_field("".to_owned()); + let validation_exception = crate::error::ValidationException { + message: format!("1 validation error detected. {}", &first_validation_exception_field.message), + reason: crate::model::ValidationExceptionReason::FieldValidationFailed, + fields: Some(vec![first_validation_exception_field]), + }; + Self::ConstraintViolation( + crate::operation_ser::serialize_structure_crate_error_validation_exception(&validation_exception) + .expect("validation exceptions should never fail to serialize; please file a bug report under https://github.com/awslabs/smithy-rs/issues") + ) + } + } + """, + *codegenScope, + ) + } + + override fun stringShapeConstraintViolationImplBlock(stringConstraintsInfo: Collection): Writable = writable { + val validationExceptionFields = + stringConstraintsInfo.map { + writable { + when (it) { + is Pattern -> { + rust( + """ + Self::Pattern(string) => crate::model::ValidationExceptionField { + message: format!("${it.patternTrait.validationErrorMessage()}", &string, &path, r##"${it.patternTrait.pattern}"##), + name: path, + reason: crate::model::ValidationExceptionFieldReason::PatternNotValid, + }, + """, + ) + } + is Length -> { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${it.lengthTrait.validationErrorMessage()}", length, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::LengthNotValid, + }, + """, + ) + } + } + } + }.join("\n") + + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to validationExceptionFields, + ) + } + + override fun enumShapeConstraintViolationImplBlock(enumTrait: EnumTrait) = writable { + val enumValueSet = enumTrait.enumDefinitionValues.joinToString(", ") + val message = "Value {} at '{}' failed to satisfy constraint: Member must satisfy enum value set: [$enumValueSet]" + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + crate::model::ValidationExceptionField { + message: format!(r##"$message"##, &self.0, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::ValueNotValid, + } + } + """, + "String" to RuntimeType.String, + ) + } + + override fun numberShapeConstraintViolationImplBlock(rangeInfo: Range) = writable { + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + Self::Range(value) => crate::model::ValidationExceptionField { + message: format!("${rangeInfo.rangeTrait.validationErrorMessage()}", value, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::ValueNotValid, + } + } + } + """, + "String" to RuntimeType.String, + ) + } + + override fun blobShapeConstraintViolationImplBlock(blobConstraintsInfo: Collection) = writable { + val validationExceptionFields = + blobConstraintsInfo.map { + writable { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${it.lengthTrait.validationErrorMessage()}", length, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::LengthNotValid, + }, + """, + ) + } + }.join("\n") + + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to validationExceptionFields, + ) + } + + override fun mapShapeConstraintViolationImplBlock( + shape: MapShape, + keyShape: StringShape, + valueShape: Shape, + symbolProvider: RustSymbolProvider, + model: Model, + ) = writable { + rustBlockTemplate( + "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", + "String" to RuntimeType.String, + ) { + rustBlock("match self") { + shape.getTrait()?.also { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${it.validationErrorMessage()}", length, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::LengthNotValid, + }, + """, + ) + } + if (isKeyConstrained(keyShape, symbolProvider)) { + rust("""Self::Key(key_constraint_violation) => key_constraint_violation.as_validation_exception_field(path),""") + } + if (isValueConstrained(valueShape, model, symbolProvider)) { + rust("""Self::Value(key, value_constraint_violation) => value_constraint_violation.as_validation_exception_field(path + "/" + key.as_str()),""") + } + } + } + } + + override fun builderConstraintViolationImplBlock(constraintViolations: Collection) = writable { + rustBlock("match self") { + constraintViolations.forEach { + if (it.hasInner()) { + rust("""ConstraintViolation::${it.name()}(inner) => inner.as_validation_exception_field(path + "/${it.forMember.memberName}"),""") + } else { + rust( + """ + ConstraintViolation::${it.name()} => crate::model::ValidationExceptionField { + message: format!("Value null at '{}/${it.forMember.memberName}' failed to satisfy constraint: Member must not be null", path), + name: path + "/${it.forMember.memberName}", + reason: crate::model::ValidationExceptionFieldReason::Other, + }, + """, + ) + } + } + } + } + + override fun collectionShapeConstraintViolationImplBlock( + collectionConstraintsInfo: + Collection, + isMemberConstrained: Boolean, + ) = writable { + val validationExceptionFields = collectionConstraintsInfo.map { + writable { + when (it) { + is CollectionTraitInfo.Length -> { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${it.lengthTrait.validationErrorMessage()}", length, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::LengthNotValid, + }, + """, + ) + } + is CollectionTraitInfo.UniqueItems -> { + rust( + """ + Self::UniqueItems { duplicate_indices, .. } => + crate::model::ValidationExceptionField { + message: format!("${it.uniqueItemsTrait.validationErrorMessage()}", &duplicate_indices, &path), + name: path, + reason: crate::model::ValidationExceptionFieldReason::ValueNotValid, + }, + """, + ) + } + } + } + }.toMutableList() + + if (isMemberConstrained) { + validationExceptionFields += { + rust( + """ + Self::Member(index, member_constraint_violation) => + member_constraint_violation.as_validation_exception_field(path + "/" + &index.to_string()) + """, + ) + } + } + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{AsValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "AsValidationExceptionFields" to validationExceptionFields.join("\n"), + ) + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/SmithyValidationExceptionDecorator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/SmithyValidationExceptionDecorator.kt new file mode 100644 index 00000000000..eb91206b5eb --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/SmithyValidationExceptionDecorator.kt @@ -0,0 +1,239 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.customizations + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.MapShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StringShape +import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.LengthTrait +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.join +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock +import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.util.getTrait +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.ServerRuntimeType +import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +import software.amazon.smithy.rust.codegen.server.smithy.generators.BlobLength +import software.amazon.smithy.rust.codegen.server.smithy.generators.CollectionTraitInfo +import software.amazon.smithy.rust.codegen.server.smithy.generators.ConstraintViolation +import software.amazon.smithy.rust.codegen.server.smithy.generators.Range +import software.amazon.smithy.rust.codegen.server.smithy.generators.StringTraitInfo +import software.amazon.smithy.rust.codegen.server.smithy.generators.TraitInfo +import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.generators.isKeyConstrained +import software.amazon.smithy.rust.codegen.server.smithy.generators.isValueConstrained +import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage + +/** + * A decorator that adds code to convert from constraint violations to Smithy's `smithy.framework#ValidationException`, + * defined in [0]. This is Smithy's recommended shape to return when validation fails. + * + * This decorator is always enabled when using the `rust-server-codegen` plugin. + * + * [0]: https://github.com/awslabs/smithy/tree/main/smithy-validation-model + * + * TODO(https://github.com/awslabs/smithy-rs/pull/2053): once the RFC is implemented, consider moving this back into the + * generators. + */ +class SmithyValidationExceptionDecorator : ServerCodegenDecorator { + override val name: String + get() = "SmithyValidationExceptionDecorator" + override val order: Byte + get() = 69 + + override fun validationExceptionConversion(codegenContext: ServerCodegenContext): ValidationExceptionConversionGenerator = + SmithyValidationExceptionConversionGenerator(codegenContext) +} + +class SmithyValidationExceptionConversionGenerator(private val codegenContext: ServerCodegenContext) : + ValidationExceptionConversionGenerator { + + // Define a companion object so that we can refer to this shape id globally. + companion object { + val SHAPE_ID: ShapeId = ShapeId.from("smithy.framework#ValidationException") + } + override val shapeId: ShapeId = SHAPE_ID + + override fun renderImplFromConstraintViolationForRequestRejection(): Writable = writable { + val codegenScope = arrayOf( + "RequestRejection" to ServerRuntimeType.requestRejection(codegenContext.runtimeConfig), + "From" to RuntimeType.From, + ) + rustTemplate( + """ + impl #{From} for #{RequestRejection} { + fn from(constraint_violation: ConstraintViolation) -> Self { + let first_validation_exception_field = constraint_violation.as_validation_exception_field("".to_owned()); + let validation_exception = crate::error::ValidationException { + message: format!("1 validation error detected. {}", &first_validation_exception_field.message), + field_list: Some(vec![first_validation_exception_field]), + }; + Self::ConstraintViolation( + crate::operation_ser::serialize_structure_crate_error_validation_exception(&validation_exception) + .expect("validation exceptions should never fail to serialize; please file a bug report under https://github.com/awslabs/smithy-rs/issues") + ) + } + } + """, + *codegenScope, + ) + } + + override fun stringShapeConstraintViolationImplBlock(stringConstraintsInfo: Collection): Writable = writable { + val constraintsInfo: List = stringConstraintsInfo.map(StringTraitInfo::toTraitInfo) + + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to constraintsInfo.map { it.asValidationExceptionField }.join("\n"), + ) + } + + override fun blobShapeConstraintViolationImplBlock(blobConstraintsInfo: Collection): Writable = writable { + val constraintsInfo: List = blobConstraintsInfo.map(BlobLength::toTraitInfo) + + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to constraintsInfo.map { it.asValidationExceptionField }.join("\n"), + ) + } + + override fun mapShapeConstraintViolationImplBlock( + shape: MapShape, + keyShape: StringShape, + valueShape: Shape, + symbolProvider: RustSymbolProvider, + model: Model, + ) = writable { + rustBlockTemplate( + "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", + "String" to RuntimeType.String, + ) { + rustBlock("match self") { + shape.getTrait()?.also { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${it.validationErrorMessage()}", length, &path), + path, + }, + """, + ) + } + if (isKeyConstrained(keyShape, symbolProvider)) { + // Note how we _do not_ append the key's member name to the path. This is intentional, as + // per the `RestJsonMalformedLengthMapKey` test. Note keys are always strings. + // https://github.com/awslabs/smithy/blob/ee0b4ff90daaaa5101f32da936c25af8c91cc6e9/smithy-aws-protocol-tests/model/restJson1/validation/malformed-length.smithy#L296-L295 + rust("""Self::Key(key_constraint_violation) => key_constraint_violation.as_validation_exception_field(path),""") + } + if (isValueConstrained(valueShape, model, symbolProvider)) { + // `as_str()` works with regular `String`s and constrained string shapes. + rust("""Self::Value(key, value_constraint_violation) => value_constraint_violation.as_validation_exception_field(path + "/" + key.as_str()),""") + } + } + } + } + + override fun enumShapeConstraintViolationImplBlock(enumTrait: EnumTrait) = writable { + val enumValueSet = enumTrait.enumDefinitionValues.joinToString(", ") + val message = "Value {} at '{}' failed to satisfy constraint: Member must satisfy enum value set: [$enumValueSet]" + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + crate::model::ValidationExceptionField { + message: format!(r##"$message"##, &self.0, &path), + path, + } + } + """, + "String" to RuntimeType.String, + ) + } + + override fun numberShapeConstraintViolationImplBlock(rangeInfo: Range) = writable { + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to rangeInfo.toTraitInfo().asValidationExceptionField, + ) + } + + override fun builderConstraintViolationImplBlock(constraintViolations: Collection) = writable { + rustBlock("match self") { + constraintViolations.forEach { + if (it.hasInner()) { + rust("""ConstraintViolation::${it.name()}(inner) => inner.as_validation_exception_field(path + "/${it.forMember.memberName}"),""") + } else { + rust( + """ + ConstraintViolation::${it.name()} => crate::model::ValidationExceptionField { + message: format!("Value null at '{}/${it.forMember.memberName}' failed to satisfy constraint: Member must not be null", path), + path: path + "/${it.forMember.memberName}", + }, + """, + ) + } + } + } + } + + override fun collectionShapeConstraintViolationImplBlock( + collectionConstraintsInfo: + Collection, + isMemberConstrained: Boolean, + ) = writable { + val validationExceptionFields = collectionConstraintsInfo.map { it.toTraitInfo().asValidationExceptionField }.toMutableList() + if (isMemberConstrained) { + validationExceptionFields += { + rust( + """ + Self::Member(index, member_constraint_violation) => + member_constraint_violation.as_validation_exception_field(path + "/" + &index.to_string()) + """, + ) + } + } + rustTemplate( + """ + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{AsValidationExceptionFields:W} + } + } + """, + "String" to RuntimeType.String, + "AsValidationExceptionFields" to validationExceptionFields.join("\n"), + ) + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt index b5b0f192955..156c00421e8 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.CombinedCoreCod import software.amazon.smithy.rust.codegen.core.smithy.customize.CoreCodegenDecorator import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator import java.util.logging.Logger @@ -21,6 +22,7 @@ typealias ServerProtocolMap = ProtocolMap { fun protocols(serviceId: ShapeId, currentProtocols: ServerProtocolMap): ServerProtocolMap = currentProtocols + fun validationExceptionConversion(codegenContext: ServerCodegenContext): ValidationExceptionConversionGenerator? = null } /** @@ -28,7 +30,7 @@ interface ServerCodegenDecorator : CoreCodegenDecorator { * * This makes the actual concrete codegen simpler by not needing to deal with multiple separate decorators. */ -class CombinedServerCodegenDecorator(decorators: List) : +class CombinedServerCodegenDecorator(private val decorators: List) : CombinedCoreCodegenDecorator(decorators), ServerCodegenDecorator { override val name: String @@ -41,6 +43,11 @@ class CombinedServerCodegenDecorator(decorators: List) : decorator.protocols(serviceId, protocolMap) } + override fun validationExceptionConversion(codegenContext: ServerCodegenContext): ValidationExceptionConversionGenerator = + // We use `firstNotNullOf` instead of `firstNotNullOfOrNull` because the [SmithyValidationExceptionDecorator] + // is registered. + decorators.sortedBy { it.order }.firstNotNullOf { it.validationExceptionConversion(codegenContext) } + companion object { fun fromClasspath( context: PluginContext, diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/CollectionConstraintViolationGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/CollectionConstraintViolationGenerator.kt index 7867b045c42..7d503e72de1 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/CollectionConstraintViolationGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/CollectionConstraintViolationGenerator.kt @@ -11,7 +11,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.rustlang.join import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext @@ -22,7 +21,8 @@ class CollectionConstraintViolationGenerator( codegenContext: ServerCodegenContext, private val modelsModuleWriter: RustWriter, private val shape: CollectionShape, - private val constraintsInfo: List, + private val collectionConstraintsInfo: List, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { private val model = codegenContext.model private val symbolProvider = codegenContext.symbolProvider @@ -35,6 +35,7 @@ class CollectionConstraintViolationGenerator( PubCrateConstraintViolationSymbolProvider(this) } } + private val constraintsInfo: List = collectionConstraintsInfo.map { it.toTraitInfo() } fun render() { val memberShape = model.expectShape(shape.member.target) @@ -75,30 +76,13 @@ class CollectionConstraintViolationGenerator( ) if (shape.isReachableFromOperationInput()) { - val validationExceptionFields = constraintsInfo.map { it.asValidationExceptionField }.toMutableList() - if (isMemberConstrained) { - validationExceptionFields += { - rust( - """ - Self::Member(index, member_constraint_violation) => - member_constraint_violation.as_validation_exception_field(path + "/" + &index.to_string()) - """, - ) - } - } - rustTemplate( """ impl $constraintViolationName { - pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { - match self { - #{AsValidationExceptionFields:W} - } - } + #{CollectionShapeConstraintViolationImplBlock} } """, - "String" to RuntimeType.String, - "AsValidationExceptionFields" to validationExceptionFields.join("\n"), + "CollectionShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.collectionShapeConstraintViolationImplBlock(collectionConstraintsInfo, isMemberConstrained), ) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt index 41fec1cec75..12156917875 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt @@ -33,6 +33,7 @@ class ConstrainedBlobGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: BlobShape, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { val model = codegenContext.model val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -45,9 +46,10 @@ class ConstrainedBlobGenerator( PubCrateConstraintViolationSymbolProvider(this) } } - private val constraintsInfo: List = listOf(LengthTrait::class.java) + private val blobConstraintsInfo: List = listOf(LengthTrait::class.java) .mapNotNull { shape.getTrait(it).orNull() } - .map { BlobLength(it).toTraitInfo() } + .map { BlobLength(it) } + private val constraintsInfo: List = blobConstraintsInfo.map { it.toTraitInfo() } fun render() { val symbol = constrainedShapeSymbolProvider.toSymbol(shape) @@ -128,21 +130,16 @@ class ConstrainedBlobGenerator( writer.rustTemplate( """ impl ${constraintViolation.name} { - pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { - match self { - #{ValidationExceptionFields:W} - } - } + #{BlobShapeConstraintViolationImplBlock} } """, - "String" to RuntimeType.String, - "ValidationExceptionFields" to constraintsInfo.map { it.asValidationExceptionField }.join("\n"), + "BlobShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.blobShapeConstraintViolationImplBlock(blobConstraintsInfo), ) } } } -private data class BlobLength(val lengthTrait: LengthTrait) { +data class BlobLength(val lengthTrait: LengthTrait) { fun toTraitInfo(): TraitInfo = TraitInfo( { rust("Self::check_length(&value)?;") }, { diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt index 4463220a964..3ae5a78a081 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt @@ -48,7 +48,7 @@ class ConstrainedCollectionGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: CollectionShape, - private val constraintsInfo: List, + collectionConstraintsInfo: List, private val unconstrainedSymbol: Symbol? = null, ) { private val model = codegenContext.model @@ -63,6 +63,7 @@ class ConstrainedCollectionGenerator( } } private val symbolProvider = codegenContext.symbolProvider + private val constraintsInfo = collectionConstraintsInfo.map { it.toTraitInfo() } fun render() { check(constraintsInfo.isNotEmpty()) { @@ -178,7 +179,7 @@ class ConstrainedCollectionGenerator( } } -internal sealed class CollectionTraitInfo { +sealed class CollectionTraitInfo { data class UniqueItems(val uniqueItemsTrait: UniqueItemsTrait, val memberSymbol: Symbol) : CollectionTraitInfo() { override fun toTraitInfo(): TraitInfo = TraitInfo( @@ -365,11 +366,10 @@ internal sealed class CollectionTraitInfo { } } - fun fromShape(shape: CollectionShape, symbolProvider: SymbolProvider): List = + fun fromShape(shape: CollectionShape, symbolProvider: SymbolProvider): List = supportedCollectionConstraintTraits .mapNotNull { shape.getTrait(it).orNull() } .map { trait -> fromTrait(trait, shape, symbolProvider) } - .map(CollectionTraitInfo::toTraitInfo) } abstract fun toTraitInfo(): TraitInfo diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt index 281f0005c11..6680d154e9f 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt @@ -21,8 +21,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata @@ -45,6 +43,7 @@ class ConstrainedNumberGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: NumberShape, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { val model = codegenContext.model val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -74,7 +73,8 @@ class ConstrainedNumberGenerator( val name = symbol.name val unconstrainedTypeName = unconstrainedType.render() val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape) - val constraintsInfo = listOf(Range(rangeTrait).toTraitInfo(unconstrainedTypeName)) + val rangeInfo = Range(rangeTrait) + val constraintsInfo = listOf(rangeInfo.toTraitInfo()) writer.documentShape(shape, model) writer.docs(rustDocsConstrainedTypeEpilogue(name)) @@ -143,35 +143,23 @@ class ConstrainedNumberGenerator( ) if (shape.isReachableFromOperationInput()) { - rustBlock("impl ${constraintViolation.name}") { - rustBlockTemplate( - "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", - "String" to RuntimeType.String, - ) { - rustBlock("match self") { - rust( - """ - Self::Range(value) => crate::model::ValidationExceptionField { - message: format!("${rangeTrait.validationErrorMessage()}", value, &path), - path, - }, - """, - ) - } + rustTemplate( + """ + impl ${constraintViolation.name} { + #{NumberShapeConstraintViolationImplBlock} } - } + """, + "NumberShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.numberShapeConstraintViolationImplBlock(rangeInfo), + ) } } } } -private data class Range(val rangeTrait: RangeTrait) { - fun toTraitInfo(unconstrainedTypeName: String): TraitInfo = TraitInfo( +data class Range(val rangeTrait: RangeTrait) { + fun toTraitInfo(): TraitInfo = TraitInfo( { rust("Self::check_range(value)?;") }, - { - docs("Error when a number doesn't satisfy its `@range` requirements.") - rust("Range($unconstrainedTypeName)") - }, + { docs("Error when a number doesn't satisfy its `@range` requirements.") }, { rust( """ diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 20a6746aa81..d267412edfd 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -47,6 +47,7 @@ class ConstrainedStringGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: StringShape, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { val model = codegenContext.model val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -60,10 +61,12 @@ class ConstrainedStringGenerator( } } private val symbol = constrainedShapeSymbolProvider.toSymbol(shape) - private val constraintsInfo: List = + private val stringConstraintsInfo: List = supportedStringConstraintTraits .mapNotNull { shape.getTrait(it).orNull() } .map { StringTraitInfo.fromTrait(symbol, it) } + private val constraintsInfo: List = + stringConstraintsInfo .map(StringTraitInfo::toTraitInfo) fun render() { @@ -155,15 +158,10 @@ class ConstrainedStringGenerator( writer.rustTemplate( """ impl ${constraintViolation.name} { - pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { - match self { - #{ValidationExceptionFields:W} - } - } + #{StringShapeConstraintViolationImplBlock:W} } """, - "String" to RuntimeType.String, - "ValidationExceptionFields" to constraintsInfo.map { it.asValidationExceptionField }.join("\n"), + "StringShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.stringShapeConstraintViolationImplBlock(stringConstraintsInfo), ) } } @@ -184,7 +182,7 @@ class ConstrainedStringGenerator( } } } -private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() { +data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() { override fun toTraitInfo(): TraitInfo = TraitInfo( tryFromCheck = { rust("Self::check_length(&value)?;") }, constraintViolationVariant = { @@ -229,7 +227,7 @@ private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() { } } -private data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait) : StringTraitInfo() { +data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait) : StringTraitInfo() { override fun toTraitInfo(): TraitInfo { val pattern = patternTrait.pattern @@ -301,7 +299,7 @@ private data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait) : } } -private sealed class StringTraitInfo { +sealed class StringTraitInfo { companion object { fun fromTrait(symbol: Symbol, trait: Trait) = when (trait) { diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/MapConstraintViolationGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/MapConstraintViolationGenerator.kt index cfcf5a53e1e..4833fa3058c 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/MapConstraintViolationGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/MapConstraintViolationGenerator.kt @@ -10,23 +10,18 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility -import software.amazon.smithy.rust.codegen.core.rustlang.rust -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.module -import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput -import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage class MapConstraintViolationGenerator( codegenContext: ServerCodegenContext, private val modelsModuleWriter: RustWriter, val shape: MapShape, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { private val model = codegenContext.model private val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -80,35 +75,20 @@ class MapConstraintViolationGenerator( ) if (shape.isReachableFromOperationInput()) { - rustBlock("impl $constraintViolationName") { - rustBlockTemplate( - "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", - "String" to RuntimeType.String, - ) { - rustBlock("match self") { - shape.getTrait()?.also { - rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${it.validationErrorMessage()}", length, &path), - path, - }, - """, - ) - } - if (isKeyConstrained(keyShape, symbolProvider)) { - // Note how we _do not_ append the key's member name to the path. This is intentional, as - // per the `RestJsonMalformedLengthMapKey` test. Note keys are always strings. - // https://github.com/awslabs/smithy/blob/ee0b4ff90daaaa5101f32da936c25af8c91cc6e9/smithy-aws-protocol-tests/model/restJson1/validation/malformed-length.smithy#L296-L295 - rust("""Self::Key(key_constraint_violation) => key_constraint_violation.as_validation_exception_field(path),""") - } - if (isValueConstrained(valueShape, model, symbolProvider)) { - // `as_str()` works with regular `String`s and constrained string shapes. - rust("""Self::Value(key, value_constraint_violation) => value_constraint_violation.as_validation_exception_field(path + "/" + key.as_str()),""") - } - } + rustTemplate( + """ + impl $constraintViolationName { + #{MapShapeConstraintViolationImplBlock} } - } + """, + "MapShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.mapShapeConstraintViolationImplBlock( + shape, + keyShape, + valueShape, + symbolProvider, + model, + ), + ) } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt index 55eae76c510..b2c89f8d733 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt @@ -17,7 +17,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.smithy.makeRustBoxed @@ -38,6 +37,7 @@ class ServerBuilderConstraintViolations( codegenContext: ServerCodegenContext, private val shape: StructureShape, private val builderTakesInUnconstrainedTypes: Boolean, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { private val model = codegenContext.model private val symbolProvider = codegenContext.symbolProvider @@ -154,25 +154,6 @@ class ServerBuilderConstraintViolations( } private fun renderAsValidationExceptionFieldList(writer: RustWriter) { - val validationExceptionFieldWritable = writable { - rustBlock("match self") { - all.forEach { - if (it.hasInner()) { - rust("""ConstraintViolation::${it.name()}(inner) => inner.as_validation_exception_field(path + "/${it.forMember.memberName}"),""") - } else { - rust( - """ - ConstraintViolation::${it.name()} => crate::model::ValidationExceptionField { - message: format!("Value null at '{}/${it.forMember.memberName}' failed to satisfy constraint: Member must not be null", path), - path: path + "/${it.forMember.memberName}", - }, - """, - ) - } - } - } - } - writer.rustTemplate( """ impl ConstraintViolation { @@ -181,7 +162,7 @@ class ServerBuilderConstraintViolations( } } """, - "ValidationExceptionFieldWritable" to validationExceptionFieldWritable, + "ValidationExceptionFieldWritable" to validationExceptionConversionGenerator.builderConstraintViolationImplBlock((all)), "String" to RuntimeType.String, ) } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt index 27bfa69d32c..178aa725b7b 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt @@ -88,6 +88,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.wouldHaveConstrainedWra class ServerBuilderGenerator( codegenContext: ServerCodegenContext, private val shape: StructureShape, + private val customValidationExceptionWithReasonConversionGenerator: ValidationExceptionConversionGenerator, ) { companion object { /** @@ -141,7 +142,7 @@ class ServerBuilderGenerator( private val builderSymbol = shape.serverBuilderSymbol(codegenContext) private val isBuilderFallible = hasFallibleBuilder(shape, model, symbolProvider, takeInUnconstrainedTypes) private val serverBuilderConstraintViolations = - ServerBuilderConstraintViolations(codegenContext, shape, takeInUnconstrainedTypes) + ServerBuilderConstraintViolations(codegenContext, shape, takeInUnconstrainedTypes, customValidationExceptionWithReasonConversionGenerator) private val codegenScope = arrayOf( "RequestRejection" to ServerRuntimeType.requestRejection(runtimeConfig), @@ -214,21 +215,9 @@ class ServerBuilderGenerator( private fun renderImplFromConstraintViolationForRequestRejection(writer: RustWriter) { writer.rustTemplate( """ - impl #{From} for #{RequestRejection} { - fn from(constraint_violation: ConstraintViolation) -> Self { - let first_validation_exception_field = constraint_violation.as_validation_exception_field("".to_owned()); - let validation_exception = crate::error::ValidationException { - message: format!("1 validation error detected. {}", &first_validation_exception_field.message), - field_list: Some(vec![first_validation_exception_field]), - }; - Self::ConstraintViolation( - crate::operation_ser::serialize_structure_crate_error_validation_exception(&validation_exception) - .expect("impossible") - ) - } - } + #{Converter:W} """, - *codegenScope, + "Converter" to customValidationExceptionWithReasonConversionGenerator.renderImplFromConstraintViolationForRequestRejection(), ) } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt index b30252a8a74..32e58fd44ec 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt @@ -46,6 +46,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerRuntimeType class ServerBuilderGeneratorWithoutPublicConstrainedTypes( private val codegenContext: ServerCodegenContext, shape: StructureShape, + validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { companion object { /** @@ -79,7 +80,7 @@ class ServerBuilderGeneratorWithoutPublicConstrainedTypes( private val builderSymbol = shape.serverBuilderSymbol(symbolProvider, false) private val isBuilderFallible = hasFallibleBuilder(shape, symbolProvider) private val serverBuilderConstraintViolations = - ServerBuilderConstraintViolations(codegenContext, shape, builderTakesInUnconstrainedTypes = false) + ServerBuilderConstraintViolations(codegenContext, shape, builderTakesInUnconstrainedTypes = false, validationExceptionConversionGenerator) private val codegenScope = arrayOf( "RequestRejection" to ServerRuntimeType.requestRejection(codegenContext.runtimeConfig), diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt index 1a55cb88796..82c040f66b9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt @@ -23,6 +23,7 @@ open class ServerEnumGenerator( val codegenContext: ServerCodegenContext, private val writer: RustWriter, shape: StringShape, + private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) : EnumGenerator(codegenContext.model, codegenContext.symbolProvider, writer, shape, shape.expectTrait()) { override var target: CodegenTarget = CodegenTarget.SERVER @@ -52,21 +53,13 @@ open class ServerEnumGenerator( ) if (shape.isReachableFromOperationInput()) { - val enumValueSet = enumTrait.enumDefinitionValues.joinToString(", ") - val message = "Value {} at '{}' failed to satisfy constraint: Member must satisfy enum value set: [$enumValueSet]" - rustTemplate( """ impl $constraintViolationName { - pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { - crate::model::ValidationExceptionField { - message: format!(r##"$message"##, &self.0, &path), - path, - } - } + #{EnumShapeConstraintViolationImplBlock:W} } """, - *codegenScope, + "EnumShapeConstraintViolationImplBlock" to validationExceptionConversionGenerator.enumShapeConstraintViolationImplBlock(enumTrait), ) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ValidationExceptionConversionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ValidationExceptionConversionGenerator.kt new file mode 100644 index 00000000000..7db44265863 --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ValidationExceptionConversionGenerator.kt @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.generators + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.MapShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StringShape +import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider + +/** + * Collection of methods that will be invoked by the respective generators to generate code to convert constraint + * violations to validation exceptions. + * This is only rendered for shapes that lie in a constrained operation's closure. + */ +interface ValidationExceptionConversionGenerator { + val shapeId: ShapeId + + /** + * Convert from a top-level operation input's constraint violation into + * `aws_smithy_http_server::rejection::RequestRejection`. + */ + fun renderImplFromConstraintViolationForRequestRejection(): Writable + + // Simple shapes. + fun stringShapeConstraintViolationImplBlock(stringConstraintsInfo: Collection): Writable + fun enumShapeConstraintViolationImplBlock(enumTrait: EnumTrait): Writable + fun numberShapeConstraintViolationImplBlock(rangeInfo: Range): Writable + fun blobShapeConstraintViolationImplBlock(blobConstraintsInfo: Collection): Writable + + // Aggregate shapes. + fun mapShapeConstraintViolationImplBlock( + shape: MapShape, + keyShape: StringShape, + valueShape: Shape, + symbolProvider: RustSymbolProvider, + model: Model, + ): Writable + fun builderConstraintViolationImplBlock(constraintViolations: Collection): Writable + fun collectionShapeConstraintViolationImplBlock( + collectionConstraintsInfo: Collection, + isMemberConstrained: Boolean, + ): Writable +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt new file mode 100644 index 00000000000..f75ea9b877d --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.testutil + +import software.amazon.smithy.build.PluginContext +import software.amazon.smithy.build.SmithyBuildPlugin +import software.amazon.smithy.model.Model +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams +import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest +import software.amazon.smithy.rust.codegen.server.smithy.RustCodegenServerPlugin +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +import java.nio.file.Path + +/** + * This file is entirely analogous to [software.amazon.smithy.rust.codegen.client.testutil.ClientCodegenIntegrationTest.kt]. + */ + +fun serverIntegrationTest( + model: Model, + params: IntegrationTestParams = IntegrationTestParams(), + additionalDecorators: List = listOf(), + test: (ServerCodegenContext, RustCrate) -> Unit = { _, _ -> }, +): Path { + fun invokeRustCodegenPlugin(ctx: PluginContext) { + val codegenDecorator = object : ServerCodegenDecorator { + override val name: String = "Add tests" + override val order: Byte = 0 + + override fun classpathDiscoverable(): Boolean = false + + override fun extras(codegenContext: ServerCodegenContext, rustCrate: RustCrate) { + test(codegenContext, rustCrate) + } + } + // TODO Rename + RustCodegenServerPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray()) + } + return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin) +} + +abstract class ServerDecoratableBuildPlugin : SmithyBuildPlugin { + abstract fun executeWithDecorator( + context: PluginContext, + vararg decorator: ServerCodegenDecorator, + ) + + override fun execute(context: PluginContext) { + executeWithDecorator(context) + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt index 9d49dfb9fea..59d6a580c89 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt @@ -24,6 +24,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.ServerRustSettings import software.amazon.smithy.rust.codegen.server.smithy.ServerSymbolProviders +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerBuilderGenerator // These are the settings we default to if the user does not override them in their `smithy-build.json`. @@ -122,7 +123,7 @@ fun StructureShape.serverRenderWithModelBuilder(model: Model, symbolProvider: Ru val serverCodegenContext = serverTestCodegenContext(model) // Note that this always uses `ServerBuilderGenerator` and _not_ `ServerBuilderGeneratorWithoutPublicConstrainedTypes`, // regardless of the `publicConstrainedTypes` setting. - val modelBuilder = ServerBuilderGenerator(serverCodegenContext, this) + val modelBuilder = ServerBuilderGenerator(serverCodegenContext, this, SmithyValidationExceptionConversionGenerator(serverCodegenContext)) modelBuilder.render(writer) writer.implBlock(this, symbolProvider) { modelBuilder.renderConvenienceMethod(this) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitorTest.kt index 2f23c143f43..942e5f76170 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitorTest.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionDecorator import software.amazon.smithy.rust.codegen.server.smithy.customize.CombinedServerCodegenDecorator import kotlin.io.path.writeText @@ -45,8 +46,12 @@ class ServerCodegenVisitorTest { """.asSmithyModel(smithyVersion = "2.0") val (ctx, testDir) = generatePluginContext(model) testDir.resolve("src/main.rs").writeText("fn main() {}") - val codegenDecorator: CombinedServerCodegenDecorator = - CombinedServerCodegenDecorator.fromClasspath(ctx, ServerRequiredCustomizations()) + val codegenDecorator = + CombinedServerCodegenDecorator.fromClasspath( + ctx, + ServerRequiredCustomizations(), + SmithyValidationExceptionDecorator(), + ) val visitor = ServerCodegenVisitor(ctx, codegenDecorator) val baselineModel = visitor.baselineTransformInternalTest(model) baselineModel.getShapesWithTrait(ShapeId.from("smithy.api#mixin")).isEmpty() shouldBe true diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt index 83ea701a975..7e111f758cf 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import java.util.logging.Level internal class ValidateUnsupportedConstraintsAreNotUsedTest { @@ -53,7 +54,11 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { } """.asSmithyModel() val service = model.lookup("test#TestService") - val validationResult = validateOperationsWithConstrainedInputHaveValidationExceptionAttached(model, service) + val validationResult = validateOperationsWithConstrainedInputHaveValidationExceptionAttached( + model, + service, + SmithyValidationExceptionConversionGenerator.SHAPE_ID, + ) validationResult.messages shouldHaveSize 1 diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecoratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecoratorTest.kt new file mode 100644 index 00000000000..31b46023819 --- /dev/null +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecoratorTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.customizations + +import org.junit.jupiter.api.Test +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.node.Node +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.transform.ModelTransformer +import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest +import java.io.File +import kotlin.streams.toList + +fun swapOutSmithyValidationExceptionForCustomOne(model: Model): Model { + val customValidationExceptionModel = + """ + namespace com.amazonaws.constraints + + enum ValidationExceptionFieldReason { + LENGTH_NOT_VALID = "LengthNotValid" + PATTERN_NOT_VALID = "PatternNotValid" + SYNTAX_NOT_VALID = "SyntaxNotValid" + VALUE_NOT_VALID = "ValueNotValid" + OTHER = "Other" + } + + /// Stores information about a field passed inside a request that resulted in an exception. + structure ValidationExceptionField { + /// The field name. + @required + Name: String + + @required + Reason: ValidationExceptionFieldReason + + /// Message describing why the field failed validation. + @required + Message: String + } + + /// A list of fields. + list ValidationExceptionFieldList { + member: ValidationExceptionField + } + + enum ValidationExceptionReason { + FIELD_VALIDATION_FAILED = "FieldValidationFailed" + UNKNOWN_OPERATION = "UnknownOperation" + CANNOT_PARSE = "CannotParse" + OTHER = "Other" + } + + /// The input fails to satisfy the constraints specified by an AWS service. + @error("client") + @httpError(400) + structure ValidationException { + /// Description of the error. + @required + Message: String + + /// Reason the request failed validation. + @required + Reason: ValidationExceptionReason + + /// The field that caused the error, if applicable. If more than one field + /// caused the error, pick one and elaborate in the message. + Fields: ValidationExceptionFieldList + } + """.asSmithyModel(smithyVersion = "2.0") + + // Remove Smithy's `ValidationException`. + var model = ModelTransformer.create().removeShapes( + model, + listOf(model.expectShape(SmithyValidationExceptionConversionGenerator.SHAPE_ID)), + ) + // Add our custom one. + model = ModelTransformer.create().replaceShapes(model, customValidationExceptionModel.shapes().toList()) + // Make all operations use our custom one. + val newOperationShapes = model.operationShapes.map { operationShape -> + operationShape.toBuilder().addError(ShapeId.from("com.amazonaws.constraints#ValidationException")).build() + } + return ModelTransformer.create().replaceShapes(model, newOperationShapes) +} + +internal class CustomValidationExceptionWithReasonDecoratorTest { + @Test + fun `constraints model with the CustomValidationExceptionWithReasonDecorator applied compiles`() { + var model = File("../codegen-core/common-test-models/constraints.smithy").readText().asSmithyModel() + model = swapOutSmithyValidationExceptionForCustomOne(model) + + serverIntegrationTest( + model, + IntegrationTestParams( + additionalSettings = Node.objectNodeBuilder().withMember( + "codegen", + Node.objectNodeBuilder() + .withMember("experimentalCustomValidationExceptionWithReasonPleaseDoNotUse", "com.amazonaws.constraints#ValidationException") + .build(), + ).build(), + ), + ) + } +} diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGeneratorTest.kt index cfb8bbd38bb..52d9008ebfc 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGeneratorTest.kt @@ -23,6 +23,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import java.util.stream.Stream @@ -68,7 +69,12 @@ class ConstrainedBlobGeneratorTest { project.withModule(ModelsModule) { addDependency(RuntimeType.blob(codegenContext.runtimeConfig).toSymbol()) - ConstrainedBlobGenerator(codegenContext, this, constrainedBlobShape).render() + ConstrainedBlobGenerator( + codegenContext, + this, + constrainedBlobShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() unitTest( name = "try_from_success", @@ -121,7 +127,12 @@ class ConstrainedBlobGeneratorTest { val writer = RustWriter.forModule(ModelsModule.name) - ConstrainedBlobGenerator(codegenContext, writer, constrainedBlobShape).render() + ConstrainedBlobGenerator( + codegenContext, + writer, + constrainedBlobShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() // Check that the wrapped type is `pub(crate)`. writer.toString() shouldContain "pub struct ConstrainedBlob(pub(crate) aws_smithy_types::Blob);" diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGeneratorTest.kt index 8e043ec47c0..0916fceae2a 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGeneratorTest.kt @@ -33,6 +33,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.transformers.ShapesReachableFromOperationInputTagger import java.util.stream.Stream @@ -284,6 +285,12 @@ class ConstrainedCollectionGeneratorTest { ) { val constraintsInfo = CollectionTraitInfo.fromShape(constrainedCollectionShape, codegenContext.symbolProvider) ConstrainedCollectionGenerator(codegenContext, writer, constrainedCollectionShape, constraintsInfo).render() - CollectionConstraintViolationGenerator(codegenContext, writer, constrainedCollectionShape, constraintsInfo).render() + CollectionConstraintViolationGenerator( + codegenContext, + writer, + constrainedCollectionShape, + constraintsInfo, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGeneratorTest.kt index 2da058cde55..93f6676d47b 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGeneratorTest.kt @@ -24,6 +24,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.transformers.ShapesReachableFromOperationInputTagger import java.util.stream.Stream @@ -153,6 +154,11 @@ class ConstrainedMapGeneratorTest { constrainedMapShape: MapShape, ) { ConstrainedMapGenerator(codegenContext, writer, constrainedMapShape).render() - MapConstraintViolationGenerator(codegenContext, writer, constrainedMapShape).render() + MapConstraintViolationGenerator( + codegenContext, + writer, + constrainedMapShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGeneratorTest.kt index 681d0fffe35..44e08a8fc8e 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGeneratorTest.kt @@ -20,6 +20,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import java.util.stream.Stream @@ -71,7 +72,12 @@ class ConstrainedNumberGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ModelsModule) { - ConstrainedNumberGenerator(codegenContext, this, shape).render() + ConstrainedNumberGenerator( + codegenContext, + this, + shape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() unitTest( name = "try_from_success", @@ -132,7 +138,12 @@ class ConstrainedNumberGeneratorTest { val codegenContext = serverTestCodegenContext(model) val writer = RustWriter.forModule(ModelsModule.name) - ConstrainedNumberGenerator(codegenContext, writer, constrainedShape).render() + ConstrainedNumberGenerator( + codegenContext, + writer, + constrainedShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() // Check that the wrapped type is `pub(crate)`. writer.toString() shouldContain "pub struct $shapeName(pub(crate) $rustType);" diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index a8fa6e441d3..63fafa4a130 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -23,6 +23,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.CommandFailed import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import java.util.stream.Stream @@ -82,7 +83,12 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() + ConstrainedStringGenerator( + codegenContext, + this, + constrainedStringShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() unitTest( name = "try_from_success", @@ -136,7 +142,12 @@ class ConstrainedStringGeneratorTest { val writer = RustWriter.forModule(ModelsModule.name) - ConstrainedStringGenerator(codegenContext, writer, constrainedStringShape).render() + ConstrainedStringGenerator( + codegenContext, + writer, + constrainedStringShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() // Check that the wrapped type is `pub(crate)`. writer.toString() shouldContain "pub struct ConstrainedString(pub(crate) std::string::String);" @@ -162,8 +173,19 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(codegenContext.symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() - ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape).render() + val validationExceptionConversionGenerator = SmithyValidationExceptionConversionGenerator(codegenContext) + ConstrainedStringGenerator( + codegenContext, + this, + constrainedStringShape, + validationExceptionConversionGenerator, + ).render() + ConstrainedStringGenerator( + codegenContext, + this, + sensitiveConstrainedStringShape, + validationExceptionConversionGenerator, + ).render() unitTest( name = "non_sensitive_string_display_implementation", @@ -201,7 +223,12 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(codegenContext.symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() + ConstrainedStringGenerator( + codegenContext, + this, + constrainedStringShape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() } assertThrows { diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt index 2219c2e653b..dc18daf855f 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt @@ -31,6 +31,7 @@ import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.core.util.toPascalCase import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestRustSettings import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestSymbolProvider @@ -175,28 +176,38 @@ class ServerBuilderDefaultValuesTest { codegenConfig = ServerCodegenConfig(publicConstrainedTypes = false), ), ) - val builderGenerator = ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, struct) + val builderGenerator = ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, struct, SmithyValidationExceptionConversionGenerator(codegenContext)) writer.implBlock(struct, symbolProvider) { builderGenerator.renderConvenienceMethod(writer) } builderGenerator.render(writer) - ServerEnumGenerator(codegenContext, writer, model.lookup("com.test#Language")).render() + ServerEnumGenerator( + codegenContext, + writer, + model.lookup("com.test#Language"), + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() StructureGenerator(model, symbolProvider, writer, struct).render() } private fun writeServerBuilderGenerator(writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { val struct = model.lookup("com.test#MyStruct") val codegenContext = serverTestCodegenContext(model) - val builderGenerator = ServerBuilderGenerator(codegenContext, struct) + val builderGenerator = ServerBuilderGenerator(codegenContext, struct, SmithyValidationExceptionConversionGenerator(codegenContext)) writer.implBlock(struct, symbolProvider) { builderGenerator.renderConvenienceMethod(writer) } builderGenerator.render(writer) - ServerEnumGenerator(codegenContext, writer, model.lookup("com.test#Language")).render() + ServerEnumGenerator( + codegenContext, + writer, + model.lookup("com.test#Language"), + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() StructureGenerator(model, symbolProvider, writer, struct).render() } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt index 24688f36546..515bbb58f8b 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt @@ -14,6 +14,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext class ServerBuilderGeneratorTest { @@ -38,7 +39,7 @@ class ServerBuilderGeneratorTest { val writer = RustWriter.forModule("model") val shape = model.lookup("test#Credentials") StructureGenerator(model, codegenContext.symbolProvider, writer, shape).render(CodegenTarget.SERVER) - val builderGenerator = ServerBuilderGenerator(codegenContext, shape) + val builderGenerator = ServerBuilderGenerator(codegenContext, shape, SmithyValidationExceptionConversionGenerator(codegenContext)) builderGenerator.render(writer) writer.implBlock(shape, codegenContext.symbolProvider) { builderGenerator.renderConvenienceMethod(this) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGeneratorTest.kt index 0e813cebbd4..abef4485aa3 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGeneratorTest.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext class ServerEnumGeneratorTest { @@ -40,7 +41,12 @@ class ServerEnumGeneratorTest { @Test fun `it generates TryFrom, FromStr and errors for enums`() { - ServerEnumGenerator(codegenContext, writer, shape).render() + ServerEnumGenerator( + codegenContext, + writer, + shape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() writer.compileAndTest( """ use std::str::FromStr; @@ -53,10 +59,15 @@ class ServerEnumGeneratorTest { @Test fun `it generates enums without the unknown variant`() { - ServerEnumGenerator(codegenContext, writer, shape).render() + ServerEnumGenerator( + codegenContext, + writer, + shape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() writer.compileAndTest( """ - // check no unknown + // Check no `Unknown` variant. let instance = InstanceType::T2Micro; match instance { InstanceType::T2Micro => (), @@ -68,7 +79,12 @@ class ServerEnumGeneratorTest { @Test fun `it generates enums without non_exhaustive`() { - ServerEnumGenerator(codegenContext, writer, shape).render() + ServerEnumGenerator( + codegenContext, + writer, + shape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() writer.toString() shouldNotContain "#[non_exhaustive]" } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGeneratorTest.kt index 6a02765c9a1..a0d608d7c76 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGeneratorTest.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -68,7 +69,13 @@ class UnconstrainedCollectionGeneratorTest { it, ).render() - CollectionConstraintViolationGenerator(codegenContext, this@modelsModuleWriter, it, listOf()).render() + CollectionConstraintViolationGenerator( + codegenContext, + this@modelsModuleWriter, + it, + CollectionTraitInfo.fromShape(it, codegenContext.constrainedShapeSymbolProvider), + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() } this@unconstrainedModuleWriter.unitTest( diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGeneratorTest.kt index 75e176be39d..ea2a6907a36 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGeneratorTest.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -66,7 +67,12 @@ class UnconstrainedMapGeneratorTest { listOf(mapA, mapB).forEach { UnconstrainedMapGenerator(codegenContext, this@unconstrainedModuleWriter, it).render() - MapConstraintViolationGenerator(codegenContext, this@modelsModuleWriter, it).render() + MapConstraintViolationGenerator( + codegenContext, + this@modelsModuleWriter, + it, + SmithyValidationExceptionConversionGenerator(codegenContext), + ).render() } this@unconstrainedModuleWriter.unitTest( diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt index e3ff924db85..496f5f319a1 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt @@ -21,6 +21,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestRequirements import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerBuilderGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerBuilderGeneratorWithoutPublicConstrainedTypes import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerOperationErrorGenerator @@ -67,15 +68,16 @@ abstract class ServerEventStreamBaseRequirements : EventStreamTestRequirements Date: Fri, 3 Feb 2023 22:48:32 +0100 Subject: [PATCH 2/5] Fix tests --- .../smithy/rust/codegen/core/smithy/DirectedWalker.kt | 9 +++------ .../rust/codegen/core/testutil/EventStreamTestTools.kt | 7 ++++++- codegen-server-test/build.gradle.kts | 6 ------ ...onExceptionToConstrainedOperationInputsInAllowList.kt | 6 ++++-- .../eventstream/ServerEventStreamBaseRequirements.kt | 3 ++- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/DirectedWalker.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/DirectedWalker.kt index 51c0b4ecf80..f48b996045e 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/DirectedWalker.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/DirectedWalker.kt @@ -19,11 +19,8 @@ import java.util.function.Predicate class DirectedWalker(model: Model) { private val inner = Walker(model) - fun walkShapes(shape: Shape): Set { - return walkShapes(shape) { _ -> true } - } + fun walkShapes(shape: Shape): Set = walkShapes(shape) { true } - fun walkShapes(shape: Shape, predicate: Predicate): Set { - return inner.walkShapes(shape) { rel -> predicate.test(rel) && rel.direction == RelationshipDirection.DIRECTED } - } + fun walkShapes(shape: Shape, predicate: Predicate): Set = + inner.walkShapes(shape) { rel -> predicate.test(rel) && rel.direction == RelationshipDirection.DIRECTED } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt index 05151b7ce7c..34fac717aab 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.smithy.ErrorsModule import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -118,11 +119,15 @@ object EventStreamTestTools { val symbolProvider = codegenContext.symbolProvider val operationShape = model.expectShape(ShapeId.from("test#TestStreamOp")) as OperationShape val unionShape = model.expectShape(ShapeId.from("test#TestStream")) as UnionShape + val walker = DirectedWalker(model) val project = TestWorkspace.testProject(symbolProvider) val operationSymbol = symbolProvider.toSymbol(operationShape) project.withModule(ErrorsModule) { - val errors = model.structureShapes.filter { shape -> shape.hasTrait() } + val errors = model.serviceShapes + .flatMap { walker.walkShapes(it) } + .filterIsInstance() + .filter { shape -> shape.hasTrait() } requirements.renderOperationError(this, model, symbolProvider, operationSymbol, errors) requirements.renderOperationError(this, model, symbolProvider, symbolProvider.toSymbol(unionShape), errors) for (shape in errors) { diff --git a/codegen-server-test/build.gradle.kts b/codegen-server-test/build.gradle.kts index 50bc399574e..b2841452c14 100644 --- a/codegen-server-test/build.gradle.kts +++ b/codegen-server-test/build.gradle.kts @@ -50,12 +50,6 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels -> imports = listOf("$commonModels/constraints.smithy"), extraConfig = """, "codegen": { "publicConstrainedTypes": false } """, ), - CodegenTest( - "com.amazonaws.constraints#CustomValidationExceptionsExperimental", - "custom_validation_exceptions_experimental", - imports = listOf("$commonModels/custom-validation-exceptions-experimental.smithy"), - extraConfig = """, "codegen": { "experimentalCustomValidationExceptionWithReasonPleaseDoNotUse": "com.amazonaws.constraints#ValidationException" } """.trimMargin(), - ), CodegenTest( "com.amazonaws.constraints#UniqueItemsService", "unique_items", diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt index 02e1d4be643..a687e13fc6c 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt @@ -13,6 +13,7 @@ import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.util.inputShape +import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator import software.amazon.smithy.rust.codegen.server.smithy.hasConstraintTrait /** @@ -60,11 +61,12 @@ object AttachValidationExceptionToConstrainedOperationInputsInAllowList { walker.walkShapes(operationShape.inputShape(model)) .any { it is SetShape || it is EnumShape || it.hasConstraintTrait() } } - .filter { !it.errors.contains(ShapeId.from("smithy.framework#ValidationException")) } + // TODO Dry + .filter { !it.errors.contains(SmithyValidationExceptionConversionGenerator.SHAPE_ID) } return ModelTransformer.create().mapShapes(model) { shape -> if (shape is OperationShape && operationsWithConstrainedInputWithoutValidationException.contains(shape)) { - shape.toBuilder().addError("smithy.framework#ValidationException").build() + shape.toBuilder().addError(SmithyValidationExceptionConversionGenerator.SHAPE_ID).build() } else { shape } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt index 496f5f319a1..2eae338e6b1 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt @@ -56,7 +56,8 @@ abstract class ServerEventStreamBaseRequirements : EventStreamTestRequirements Date: Sat, 4 Feb 2023 00:22:36 +0100 Subject: [PATCH 3/5] Rename RustCodegenServerPlugin to RustServerCodegenPlugin --- ...ustCodegenServerPlugin.kt => RustServerCodegenPlugin.kt} | 2 +- .../rust/codegen/server/smithy/ServerCodegenContext.kt | 2 +- .../rust/codegen/server/smithy/ServerCodegenVisitor.kt | 2 +- .../smithy/rust/codegen/server/smithy/ServerRustSettings.kt | 2 +- .../server/smithy/testutil/ServerCodegenIntegrationTest.kt | 5 ++--- .../codegen/server/smithy/testutil/ServerTestHelpers.kt | 6 +++--- .../services/software.amazon.smithy.build.SmithyBuildPlugin | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) rename codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/{RustCodegenServerPlugin.kt => RustServerCodegenPlugin.kt} (98%) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt similarity index 98% rename from codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt rename to codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt index e2ada81f17a..812bc50916e 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCodegenServerPlugin.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt @@ -33,7 +33,7 @@ import java.util.logging.Logger * `resources/META-INF.services/software.amazon.smithy.build.SmithyBuildPlugin` refers to this class by name which * enables the smithy-build plugin to invoke `execute` with all Smithy plugin context + models. */ -class RustCodegenServerPlugin : ServerDecoratableBuildPlugin() { +class RustServerCodegenPlugin : ServerDecoratableBuildPlugin() { private val logger = Logger.getLogger(javaClass.name) override fun getName(): String = "rust-server-codegen" diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenContext.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenContext.kt index a0ad38f04f6..e71ae7ded1c 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenContext.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenContext.kt @@ -13,7 +13,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider /** - * [ServerCodegenContext] contains code-generation context that is _specific_ to the [RustCodegenServerPlugin] plugin + * [ServerCodegenContext] contains code-generation context that is _specific_ to the [RustServerCodegenPlugin] plugin * from the `rust-codegen-server` subproject. * * It inherits from [CodegenContext], which contains code-generation context that is common to _all_ smithy-rs plugins. diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index 213f69b47e6..def3e4a6110 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -132,7 +132,7 @@ open class ServerCodegenVisitor( service, symbolVisitorConfig, settings.codegenConfig.publicConstrainedTypes, - RustCodegenServerPlugin::baseSymbolProvider, + RustServerCodegenPlugin::baseSymbolProvider, ) codegenContext = ServerCodegenContext( diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt index e3c47ad3156..67fbea91cd6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerRustSettings.kt @@ -25,7 +25,7 @@ import java.util.Optional */ /** - * Settings used by [RustCodegenServerPlugin]. + * Settings used by [RustServerCodegenPlugin]. */ data class ServerRustSettings( override val service: ShapeId, diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt index f75ea9b877d..fc83f1392b0 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerCodegenIntegrationTest.kt @@ -11,7 +11,7 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest -import software.amazon.smithy.rust.codegen.server.smithy.RustCodegenServerPlugin +import software.amazon.smithy.rust.codegen.server.smithy.RustServerCodegenPlugin import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator import java.nio.file.Path @@ -37,8 +37,7 @@ fun serverIntegrationTest( test(codegenContext, rustCrate) } } - // TODO Rename - RustCodegenServerPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray()) + RustServerCodegenPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray()) } return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin) } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt index 59d6a580c89..e1e4f3bd528 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt @@ -19,7 +19,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig -import software.amazon.smithy.rust.codegen.server.smithy.RustCodegenServerPlugin +import software.amazon.smithy.rust.codegen.server.smithy.RustServerCodegenPlugin import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.ServerRustSettings @@ -54,7 +54,7 @@ fun serverTestSymbolProviders( (serviceShape ?: testServiceShapeFor(model)).id, ) ).codegenConfig.publicConstrainedTypes, - RustCodegenServerPlugin::baseSymbolProvider, + RustServerCodegenPlugin::baseSymbolProvider, ) fun serverTestRustSettings( @@ -99,7 +99,7 @@ fun serverTestCodegenContext( service, ServerTestSymbolVisitorConfig, settings.codegenConfig.publicConstrainedTypes, - RustCodegenServerPlugin::baseSymbolProvider, + RustServerCodegenPlugin::baseSymbolProvider, ) return ServerCodegenContext( diff --git a/codegen-server/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin b/codegen-server/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin index 00f891cb228..392b1d43920 100644 --- a/codegen-server/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin +++ b/codegen-server/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin @@ -2,4 +2,4 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 # -software.amazon.smithy.rust.codegen.server.smithy.RustCodegenServerPlugin +software.amazon.smithy.rust.codegen.server.smithy.RustServerCodegenPlugin From d1f929410193abdbe7b95a057e15d7fdd7e3c3fc Mon Sep 17 00:00:00 2001 From: david-perez Date: Sat, 4 Feb 2023 00:44:12 +0100 Subject: [PATCH 4/5] Remove stale TODO --- ...ValidationExceptionToConstrainedOperationInputsInAllowList.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt index a687e13fc6c..68840bde201 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/AttachValidationExceptionToConstrainedOperationInputsInAllowList.kt @@ -61,7 +61,6 @@ object AttachValidationExceptionToConstrainedOperationInputsInAllowList { walker.walkShapes(operationShape.inputShape(model)) .any { it is SetShape || it is EnumShape || it.hasConstraintTrait() } } - // TODO Dry .filter { !it.errors.contains(SmithyValidationExceptionConversionGenerator.SHAPE_ID) } return ModelTransformer.create().mapShapes(model) { shape -> From 40bd7b95adb03edca1451369d3a63ed6ed7020f3 Mon Sep 17 00:00:00 2001 From: david-perez Date: Wed, 8 Feb 2023 22:53:29 +0100 Subject: [PATCH 5/5] Fix --- .../CustomValidationExceptionWithReasonDecorator.kt | 5 +++-- .../server/smithy/generators/ConstrainedStringGenerator.kt | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecorator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecorator.kt index 32893b4f819..92de1fca48d 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecorator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/CustomValidationExceptionWithReasonDecorator.kt @@ -99,14 +99,15 @@ class ValidationExceptionWithReasonConversionGenerator(private val codegenContex writable { when (it) { is Pattern -> { - rust( + rustTemplate( """ Self::Pattern(string) => crate::model::ValidationExceptionField { - message: format!("${it.patternTrait.validationErrorMessage()}", &string, &path, r##"${it.patternTrait.pattern}"##), + message: #{MessageWritable:W}, name: path, reason: crate::model::ValidationExceptionFieldReason::PatternNotValid, }, """, + "MessageWritable" to it.errorMessage(), ) } is Length -> { diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 9771c721422..73a2ac7a0de 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -240,9 +240,10 @@ data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait, val isSen rust("Pattern(String)") }, asValidationExceptionField = { + Attribute.AllowUnusedVariables.render(this) rustTemplate( """ - Self::Pattern(_string) => crate::model::ValidationExceptionField { + Self::Pattern(string) => crate::model::ValidationExceptionField { message: #{ErrorMessage:W}, path }, @@ -264,7 +265,7 @@ data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait, val isSen ) } - private fun errorMessage(): Writable { + fun errorMessage(): Writable { val pattern = patternTrait.pattern return if (isSensitive) { @@ -279,7 +280,7 @@ data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait, val isSen writable { rust( """ - format!("Value {} at '{}' failed to satisfy constraint: Member must satisfy regular expression pattern: {}", &_string, &path, r##"$pattern"##) + format!("Value {} at '{}' failed to satisfy constraint: Member must satisfy regular expression pattern: {}", &string, &path, r##"$pattern"##) """, ) }