Unverified Commit a3075ea5 authored by david-perez's avatar david-perez Committed by GitHub
Browse files

Move converters from constraint violations into `ValidationException` to decorators (#2302)

This allows "easily" converting to other custom validation exception
shapes by implementing a decorator.

As a simple example, this PR adds a
`CustomValidationExceptionWithReasonDecorator` decorator that is able to
convert into a shape that is very similar to
`smithy.framework#ValidationException`, but that has an additional
`reason` field. The decorator can be enabled via the newly added
`experimentalCustomValidationExceptionWithReasonPleaseDoNotUse` codegen
config flag.

This effectively provides a way for users to use custom validation
exceptions without having to wait for the full implementation of #2053,
provided they're interested enough to write a decorator in a JVM
language. This mechanism is _experimental_ and will be removed once full
support for custom validation exceptions as described in #2053 lands,
hence why the configuration key is strongly worded in this respect.

This commit also ports the mechanism to run codegen integration tests
within Kotlin unit tests for client SDKs to the server. See #1956 for
details. The custom validation exception decorator is tested this way.
parent bb6155aa
Loading
Loading
Loading
Loading
+7 −4
Original line number Original line Diff line number Diff line
@@ -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.CoreRustSettings
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation
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.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.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings
import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings
@@ -39,9 +40,10 @@ fun awsSdkIntegrationTest(
    test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
    test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
) =
) =
    clientIntegrationTest(
    clientIntegrationTest(
        model, runtimeConfig = AwsTestRuntimeConfig,
        model,
        additionalSettings = ObjectNode.builder()
        IntegrationTestParams(
            .withMember(
            runtimeConfig = AwsTestRuntimeConfig,
            additionalSettings = ObjectNode.builder().withMember(
                "customizationConfig",
                "customizationConfig",
                ObjectNode.builder()
                ObjectNode.builder()
                    .withMember(
                    .withMember(
@@ -52,5 +54,6 @@ fun awsSdkIntegrationTest(
                    ).build(),
                    ).build(),
            )
            )
                .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
                .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
        ),
        test = test,
        test = test,
    )
    )
+2 −2
Original line number Original line Diff line number Diff line
@@ -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.customize.RequiredCustomizations
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator
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.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.Attribute.Companion.NonExhaustive
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider
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
 * `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.
 * 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 getName(): String = "rust-client-codegen"


    override fun executeWithDecorator(
    override fun executeWithDecorator(
+56 −0
Original line number Original line Diff line number Diff line
@@ -8,53 +8,43 @@ package software.amazon.smithy.rust.codegen.client.testutil
import software.amazon.smithy.build.PluginContext
import software.amazon.smithy.build.PluginContext
import software.amazon.smithy.build.SmithyBuildPlugin
import software.amazon.smithy.build.SmithyBuildPlugin
import software.amazon.smithy.model.Model
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.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
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.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.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
import software.amazon.smithy.rust.codegen.core.testutil.printGeneratedFiles
import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest
import software.amazon.smithy.rust.codegen.core.util.runCommand
import java.io.File
import java.nio.file.Path
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(
fun clientIntegrationTest(
    model: Model,
    model: Model,
    params: IntegrationTestParams = IntegrationTestParams(),
    additionalDecorators: List<ClientCodegenDecorator> = listOf(),
    additionalDecorators: List<ClientCodegenDecorator> = listOf(),
    addModuleToEventStreamAllowList: Boolean = false,
    service: String? = null,
    runtimeConfig: RuntimeConfig? = null,
    additionalSettings: ObjectNode = ObjectNode.builder().build(),
    command: ((Path) -> Unit)? = null,
    test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
    test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
): Path {
): Path {
    return codegenIntegrationTest(
    fun invokeRustCodegenPlugin(ctx: PluginContext) {
        model,
        val codegenDecorator = object : ClientCodegenDecorator {
        RustClientCodegenPlugin(),
            override val name: String = "Add tests"
        additionalDecorators,
            override val order: Byte = 0
        addModuleToEventStreamAllowList = addModuleToEventStreamAllowList,

        service = service,
            override fun classpathDiscoverable(): Boolean = false
        runtimeConfig = runtimeConfig,

        additionalSettings = additionalSettings,
            override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
        test = test,
                test(codegenContext, rustCrate)
        command = command,
            }
    )
        }
        RustClientCodegenPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray())
    }
    return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin)
}
}


/**
/**
 * A Smithy BuildPlugin that accepts an additional decorator
 * A `SmithyBuildPlugin` that accepts an additional decorator.
 *
 *
 * This exists to allow tests to easily customize the _real_ build without needing to list out customizations
 * 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
 * or attempt to manually discover them from the path.
 */
 */
abstract class DecoratableBuildPlugin : SmithyBuildPlugin {
abstract class ClientDecoratableBuildPlugin : SmithyBuildPlugin {
    abstract fun executeWithDecorator(
    abstract fun executeWithDecorator(
        context: PluginContext,
        context: PluginContext,
        vararg decorator: ClientCodegenDecorator,
        vararg decorator: ClientCodegenDecorator,
@@ -64,40 +54,3 @@ abstract class DecoratableBuildPlugin : SmithyBuildPlugin {
        executeWithDecorator(context)
        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<ClientCodegenDecorator>,
    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
}
+3 −2
Original line number Original line Diff line number Diff line
@@ -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.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
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.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.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest


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


        clientIntegrationTest(
        clientIntegrationTest(
            model,
            model,
            listOf(FakeSigningDecorator()),
            IntegrationTestParams(addModuleToEventStreamAllowList = true),
            addModuleToEventStreamAllowList = true,
            additionalDecorators = listOf(FakeSigningDecorator()),
        ) { clientCodegenContext, rustCrate ->
        ) { clientCodegenContext, rustCrate ->
            val moduleName = clientCodegenContext.moduleUseName()
            val moduleName = clientCodegenContext.moduleUseName()
            rustCrate.integrationTest("validate_eventstream_http") {
            rustCrate.integrationTest("validate_eventstream_http") {
+3 −2
Original line number Original line Diff line number Diff line
@@ -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.client.testutil.clientIntegrationTest
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
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.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.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
@@ -123,8 +124,8 @@ class EndpointsDecoratorTest {
    fun `set an endpoint in the property bag`() {
    fun `set an endpoint in the property bag`() {
        val testDir = clientIntegrationTest(
        val testDir = clientIntegrationTest(
            model,
            model,
            // just run integration tests
            // Just run integration tests.
            command = { "cargo test --test *".runWithWarnings(it) },
            IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }),
        ) { clientCodegenContext, rustCrate ->
        ) { clientCodegenContext, rustCrate ->
            rustCrate.integrationTest("endpoint_params_test") {
            rustCrate.integrationTest("endpoint_params_test") {
                val moduleName = clientCodegenContext.moduleUseName()
                val moduleName = clientCodegenContext.moduleUseName()
Loading