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 26e30a2ead7..59c65cc8b85 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 @@ -10,6 +10,7 @@ import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.rust.codegen.client.smithy.customizations.ClientCustomizations +import software.amazon.smithy.rust.codegen.client.smithy.customizations.ClientSerdeDecorator import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpAuthDecorator import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpConnectorConfigDecorator import software.amazon.smithy.rust.codegen.client.smithy.customizations.IdempotencyTokenDecorator @@ -72,6 +73,7 @@ class RustClientCodegenPlugin : ClientDecoratableBuildPlugin() { IdempotencyTokenDecorator(), StalledStreamProtectionDecorator(), StaticSdkFeatureTrackerDecorator(), + ClientSerdeDecorator(), *decorator, ) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientSerdeDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientSerdeDecorator.kt new file mode 100644 index 00000000000..384a354606f --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientSerdeDecorator.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.customizations + +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.customizations.serde.extrasCommon + +class ClientSerdeDecorator : ClientCodegenDecorator { + override val name: String = "ClientSerdeDecorator" + override val order: Byte = 0 + + override fun extras( + codegenContext: ClientCodegenContext, + rustCrate: RustCrate, + ) = extrasCommon( + codegenContext, + rustCrate, + // Constraints don't affect client codegen. + unwrapConstraints = { writable { } }, + ) +} diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/serde/SerdeDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/serde/SerdeDecoratorTest.kt new file mode 100644 index 00000000000..36852e44c4c --- /dev/null +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/serde/SerdeDecoratorTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.customizations.serde + +import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.CratesIo +import software.amazon.smithy.rust.codegen.core.rustlang.RustType +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +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.unitTest + +/** + * The serde decorators for the client and the server are _identical_, but they live in separate Gradle projects. + * There's no point in testing everything twice, since the code is the same. Hence this file just contains a simple + * smoke test to ensure we don't disable the client decorator _somehow_; all tests testing the decorator's logic should + * live in `SerdeDecoratorTest` of the `codegen-server` project instead. + */ +class SerdeDecoratorTest { + @Test + fun `smoke test`() { + val model = + """ + namespace com.example + use smithy.rust#serde + use aws.protocols#awsJson1_0 + + @awsJson1_0 + @serde + service MyResourceService { + resources: [MyResource] + } + + resource MyResource { + read: ReadMyResource + } + + @readonly + operation ReadMyResource { + input := { } + } + """.asSmithyModel(smithyVersion = "2") + + val params = + IntegrationTestParams(cargoCommand = "cargo test --all-features", service = "com.example#MyResourceService") + clientIntegrationTest(model, params = params) { ctx, crate -> + val codegenScope = + arrayOf( + "crate" to RustType.Opaque(ctx.moduleUseName()), + "serde_json" to CargoDependency("serde_json", CratesIo("1")).toDevDependency().toType(), + // we need the derive feature + "serde" to CargoDependency.Serde.toDevDependency().toType(), + ) + + crate.integrationTest("test_serde") { + unitTest("input_serialized") { + rustTemplate( + """ + use #{crate}::operation::read_my_resource::ReadMyResourceInput; + use #{crate}::serde::*; + let input = ReadMyResourceInput::builder().build().unwrap(); + let settings = SerializationSettings::default(); + let _serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); + """, + *codegenScope, + ) + } + } + } + } +} diff --git a/codegen-core/build.gradle.kts b/codegen-core/build.gradle.kts index e300dac4a79..b6a5cb20c51 100644 --- a/codegen-core/build.gradle.kts +++ b/codegen-core/build.gradle.kts @@ -23,6 +23,7 @@ val smithyVersion: String by project dependencies { implementation(kotlin("stdlib-jdk8")) + implementation(project(":smithy-shapes")) implementation("org.jsoup:jsoup:1.16.2") api("software.amazon.smithy:smithy-codegen-core:$smithyVersion") api("com.moandjiezana.toml:toml4j:0.7.2") diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecorator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerdeDecorator.kt similarity index 56% rename from codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecorator.kt rename to codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerdeDecorator.kt index 5e5cff78e7f..8752c681033 100644 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecorator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerdeDecorator.kt @@ -1,22 +1,15 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.serde +package software.amazon.smithy.rust.codegen.core.smithy.customizations.serde -import software.amazon.smithy.model.neighbor.Walker import software.amazon.smithy.model.shapes.Shape -import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext -import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Feature import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.util.hasTrait -import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext -import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +import software.amazon.smithy.rust.smithy.shapes.SerdeTrait val SerdeFeature = Feature("serde", false, listOf("dep:serde")) val SerdeModule = @@ -26,35 +19,22 @@ val SerdeModule = documentationOverride = "Implementations of `serde` for model types. NOTE: These implementations are NOT used for wire serialization as part of a Smithy protocol and WILL NOT match the wire format. They are provided for convenience only.", ) -class ClientSerdeDecorator : ClientCodegenDecorator { - override val name: String = "ClientSerdeDecorator" - override val order: Byte = 0 - - override fun extras( - codegenContext: ClientCodegenContext, - rustCrate: RustCrate, - ) = extrasCommon(codegenContext, rustCrate) -} - -class ServerSerdeDecorator : ServerCodegenDecorator { - override val name: String = "ServerSerdeDecorator" - override val order: Byte = 0 - - override fun extras( - codegenContext: ServerCodegenContext, - rustCrate: RustCrate, - ) = extrasCommon(codegenContext, rustCrate) -} - -// Just a common function to keep things DRY. -private fun extrasCommon( +/** + * The entrypoint to both the client and server decorators. + */ +fun extrasCommon( codegenContext: CodegenContext, rustCrate: RustCrate, + unwrapConstraints: (Shape) -> Writable, ) { val roots = serializationRoots(codegenContext) if (roots.isNotEmpty()) { rustCrate.mergeFeature(SerdeFeature) - val generator = SerializeImplGenerator(codegenContext) + val generator = + SerializeImplGenerator( + codegenContext, + unwrapConstraints, + ) rustCrate.withModule(SerdeModule) { roots.forEach { generator.generateRootSerializerForShape(it)(this) @@ -70,6 +50,6 @@ private fun extrasCommon( */ fun serializationRoots(ctx: CodegenContext): List { val serviceShape = ctx.serviceShape - val walker = Walker(ctx.model) + val walker = DirectedWalker(ctx.model) return walker.walkShapes(serviceShape).filter { it.hasTrait() } } diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerializeImplGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerializeImplGenerator.kt similarity index 93% rename from codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerializeImplGenerator.kt rename to codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerializeImplGenerator.kt index 3db8d0086c7..c3b99e066e5 100644 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SerializeImplGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SerializeImplGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.serde +package software.amazon.smithy.rust.codegen.core.smithy.customizations.serde import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.knowledge.TopDownIndex @@ -39,7 +39,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.writable 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.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.SimpleShapes import software.amazon.smithy.rust.codegen.core.smithy.contextName @@ -57,10 +56,11 @@ import software.amazon.smithy.rust.codegen.core.util.isEventStream import software.amazon.smithy.rust.codegen.core.util.isTargetUnit import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rust.codegen.core.util.toPascalCase -import software.amazon.smithy.rust.codegen.server.smithy.ServerRustSettings -import software.amazon.smithy.rust.codegen.server.smithy.hasConstraintTrait -class SerializeImplGenerator(private val codegenContext: CodegenContext) { +class SerializeImplGenerator( + private val codegenContext: CodegenContext, + private val unwrapConstraints: (Shape) -> Writable, +) { private val model = codegenContext.model private val topIndex = TopDownIndex.of(model) @@ -123,14 +123,14 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { } if (wrapper != null && applyTo != null) { rustTemplate( - "&#{wrapper}(#{applyTo}#{unwrapConstraints})", "wrapper" to wrapper, + "&#{wrapper}(#{applyTo})", + "wrapper" to wrapper, "applyTo" to applyTo, - "unwrapConstraints" to shape.unwrapConstraints(), ) } else { deps?.toSymbol().also { addDependency(it) } applyTo?.invoke(this) - shape.unwrapConstraints()(this) + unwrapConstraints(shape)(this) } } } @@ -270,7 +270,7 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { val baseValue = writable { rust("self.value") - shape.unwrapConstraints()(this) + unwrapConstraints(shape)(this) if (shape.isStringShape) { rust(".as_str()") } @@ -315,9 +315,7 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { val module = serdeSubmodule(shape) val type = module.toType().resolve(name).toSymbol() val base = - writable { rust("self.value.0") }.letIf(shape.hasConstraintTrait() && constraintTraitsEnabled()) { - it.plus { rust(".0") } - } + writable { rust("self.value.0") }.plus(unwrapConstraints(shape)) val serialization = implSerializeConfiguredWrapper(type) { body(base)(this) @@ -336,21 +334,6 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { return wrapperStruct } - private fun constraintTraitsEnabled(): Boolean = - codegenContext.target == CodegenTarget.SERVER && - (codegenContext.settings as ServerRustSettings).codegenConfig.publicConstrainedTypes - - private fun Shape.unwrapConstraints(): Writable { - val shape = this - return writable { - if (constraintTraitsEnabled() && hasConstraintTrait()) { - if (isBlobShape || isTimestampShape || isDocumentShape || shape is NumberShape) { - rust(".0") - } - } - } - } - /** * Serialize the field of a structure, union, list or map. * @@ -455,14 +438,14 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { } private fun serializeDateTime(shape: TimestampShape): RuntimeType = - RuntimeType.forInlineFun("SerializeDateTime", Companion.PrimitiveShapesModule) { + RuntimeType.forInlineFun("SerializeDateTime", PrimitiveShapesModule) { implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { rust("serializer.serialize_str(&self.value.to_string())") } } private fun serializeBlob(shape: BlobShape): RuntimeType = - RuntimeType.forInlineFun("SerializeBlob", Companion.PrimitiveShapesModule) { + RuntimeType.forInlineFun("SerializeBlob", PrimitiveShapesModule) { implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { rustTemplate( """ @@ -478,7 +461,7 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { } private fun serializeByteStream(shape: BlobShape): RuntimeType = - RuntimeType.forInlineFun("SerializeByteStream", Companion.PrimitiveShapesModule) { + RuntimeType.forInlineFun("SerializeByteStream", PrimitiveShapesModule) { implSerializeConfigured(RuntimeType.byteStream(codegenContext.runtimeConfig).toSymbol()) { // This doesn't work yet—there is no way to get data out of a ByteStream from a sync context rustTemplate( @@ -498,7 +481,7 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) { } private fun serializeDocument(shape: DocumentShape): RuntimeType = - RuntimeType.forInlineFun("SerializeDocument", Companion.PrimitiveShapesModule) { + RuntimeType.forInlineFun("SerializeDocument", PrimitiveShapesModule) { implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { rustTemplate( """ diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SupportStructures.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SupportStructures.kt similarity index 99% rename from codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SupportStructures.kt rename to codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SupportStructures.kt index e034cf41681..2fc9bfcb888 100644 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/SupportStructures.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/serde/SupportStructures.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.serde +package software.amazon.smithy.rust.codegen.core.smithy.customizations.serde import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/Traits.kt b/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/Traits.kt deleted file mode 100644 index 92e933d5829..00000000000 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/Traits.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.serde - -import software.amazon.smithy.build.SmithyBuildException -import software.amazon.smithy.model.SourceLocation -import software.amazon.smithy.model.node.Node -import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.model.traits.AbstractTrait -import software.amazon.smithy.model.traits.Trait -import software.amazon.smithy.rust.codegen.core.util.orNull - -class SerdeTrait constructor( - private val serialize: Boolean, - private val deserialize: Boolean, - private val tag: String?, - private val content: String?, - sourceLocation: SourceLocation, -) : - AbstractTrait(ID, sourceLocation) { - override fun createNode(): Node { - val builder = - Node.objectNodeBuilder() - .sourceLocation(sourceLocation) - .withMember("serialize", serialize) - .withMember("deserialize", deserialize) - .withMember("tag", tag) - .withMember("content", content) - return builder.build() - } - - class Provider : AbstractTrait.Provider(ID) { - override fun createTrait( - target: ShapeId, - value: Node, - ): Trait = - with(value.expectObjectNode()) { - val serialize = getBooleanMemberOrDefault("serialize", true) - val deserialize = getBooleanMemberOrDefault("deserialize", false) - val tag = getStringMember("tag").orNull()?.value - val content = getStringMember("content").orNull()?.value - if (deserialize) { - throw SmithyBuildException("deserialize is not currently supported.") - } - val result = - SerdeTrait( - serialize, - deserialize, - tag, - content, - value.sourceLocation, - ) - result.setNodeCache(value) - result - } - } - - companion object { - val ID: ShapeId = ShapeId.from("smithy.rust#serde") - } - } diff --git a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService deleted file mode 100644 index 10037cd4151..00000000000 --- a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService +++ /dev/null @@ -1 +0,0 @@ -software.amazon.smithy.rust.codegen.serde.SerdeTrait$Provider diff --git a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator b/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator deleted file mode 100644 index 76ac956e577..00000000000 --- a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +++ /dev/null @@ -1,4 +0,0 @@ -# This file lists the names of classes which implement decorators to the `rust-client-codegen` plugin. -# The source code we are extending is found in https://github.com/smithy-lang/smithy-rs/blob/d5ea2cdd91e9b74232e08bd3137e3cd6aa2c3d01/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt - -software.amazon.smithy.rust.codegen.serde.ClientSerdeDecorator diff --git a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator b/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator deleted file mode 100644 index 8df7cf0e49d..00000000000 --- a/codegen-serde/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +++ /dev/null @@ -1,4 +0,0 @@ -# This file lists the names of classes which implement decorators to the `rust-server-codegen` plugin. -# The source code we are extending is found in https://github.com/awslabs/smithy-rs/blob/release-2023-04-26/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt - -software.amazon.smithy.rust.codegen.serde.ServerSerdeDecorator diff --git a/codegen-server/build.gradle.kts b/codegen-server/build.gradle.kts index 49e0462888c..af842ab62c8 100644 --- a/codegen-server/build.gradle.kts +++ b/codegen-server/build.gradle.kts @@ -34,6 +34,8 @@ dependencies { // It's handy to re-use protocol test suite models from Smithy in our Kotlin tests. testImplementation("software.amazon.smithy:smithy-protocol-tests:$smithyVersion") + + testImplementation(project(":smithy-shapes")) } java { diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt index f6dc9eac664..3e9fe30f2ad 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt @@ -29,7 +29,6 @@ import software.amazon.smithy.model.traits.UniqueItemsTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords import software.amazon.smithy.rust.codegen.core.rustlang.Visibility -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.isOptional @@ -151,12 +150,7 @@ fun Shape.hasPublicConstrainedWrapperTupleType( fun Shape.wouldHaveConstrainedWrapperTupleTypeWerePublicConstrainedTypesEnabled(model: Model): Boolean = hasPublicConstrainedWrapperTupleType(model, true) -/** - * Helper function to determine whether a shape will map to a _public_ constrained wrapper tuple type. - * - * This function is used in core code generators, so it takes in a [CodegenContext] that is downcast - * to [ServerCodegenContext] when generating servers. - */ +/** Helper function to determine whether a shape will map to a _public_ constrained wrapper tuple type. */ fun workingWithPublicConstrainedWrapperTupleType( shape: Shape, model: Model, diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt index e68fa26a33c..d3bd16f95a9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustServerCodegenPlugin.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.StreamingShapeSymbolProvi import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor 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.ServerSerdeDecorator 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 @@ -52,6 +53,7 @@ class RustServerCodegenPlugin : ServerDecoratableBuildPlugin() { ServerRequiredCustomizations(), SmithyValidationExceptionDecorator(), CustomValidationExceptionWithReasonDecorator(), + ServerSerdeDecorator(), *decorator, ) logger.info("Loaded plugin to generate pure Rust bindings for the server SDK") diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerSerdeDecorator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerSerdeDecorator.kt new file mode 100644 index 00000000000..02eed41e7dd --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerSerdeDecorator.kt @@ -0,0 +1,38 @@ +/* + * 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.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.customizations.serde.extrasCommon +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator +import software.amazon.smithy.rust.codegen.server.smithy.hasPublicConstrainedWrapperTupleType + +class ServerSerdeDecorator : ServerCodegenDecorator { + override val name: String = "ServerSerdeDecorator" + override val order: Byte = 0 + + override fun extras( + codegenContext: ServerCodegenContext, + rustCrate: RustCrate, + ) = extrasCommon( + codegenContext, + rustCrate, + unwrapConstraints = { shape -> + writable { + if (shape.hasPublicConstrainedWrapperTupleType( + codegenContext.model, + codegenContext.settings.codegenConfig.publicConstrainedTypes, + ) + ) { + rust(".0") + } + } + }, + ) +} diff --git a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecoratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeDecoratorTest.kt similarity index 64% rename from codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecoratorTest.kt rename to codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeDecoratorTest.kt index 47907d4323b..3f01b9a63f8 100644 --- a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeDecoratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeDecoratorTest.kt @@ -3,10 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.serde +package software.amazon.smithy.rust.codegen.server.smithy.customizations.serde 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.Attribute.Companion.cfg import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.feature @@ -20,7 +19,6 @@ 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.unitTest import software.amazon.smithy.rust.codegen.core.util.dq -import software.amazon.smithy.rust.codegen.core.util.runCommand import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest class SerdeDecoratorTest { @@ -138,15 +136,15 @@ class SerdeDecoratorTest { } structure Nested { - @required - int: Integer, - float: Float, - double: Double, - sensitive: Timestamps, - notSensitive: AlsoTimestamps, - manyEnums: TestEnumList, - sparse: SparseList - map: SparseMap + @required + int: Integer, + float: Float, + double: Double, + sensitive: Timestamps, + notSensitive: AlsoTimestamps, + manyEnums: TestEnumList, + sparse: SparseList + map: SparseMap } list TestEnumList { @@ -268,6 +266,7 @@ class SerdeDecoratorTest { arrayOf( "crate" to RustType.Opaque(ctx.moduleUseName()), "serde_json" to CargoDependency("serde_json", CratesIo("1")).toDevDependency().toType(), + "ciborium" to CargoDependency.Ciborium.toType(), // we need the derive feature "serde" to CargoDependency.Serde.toDevDependency().toType(), ) @@ -324,7 +323,6 @@ class SerdeDecoratorTest { let settings = SerializationSettings::default(); let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); assert_eq!(serialized, ${expectedStreaming.dq()}); - """, *codegenScope, ) @@ -354,6 +352,23 @@ class SerdeDecoratorTest { *codegenScope, ) } + + unitTest("cbor") { + rustTemplate( + """ + use #{crate}::input::StreamingInput; + use #{crate}::types::ByteStream; + use #{crate}::serde::*; + let input = StreamingInput::builder().data(ByteStream::from_static(b"123")).build().unwrap(); + let settings = SerializationSettings::default(); + let mut serialized = Vec::new(); + #{ciborium}::ser::into_writer(&input.serialize_ref(&settings), &mut serialized) + .expect("failed to serialize input into CBOR format using `ciborium`"); + assert_eq!(serialized, b"\xa1ddataC123"); + """, + *codegenScope, + ) + } } } } @@ -364,8 +379,8 @@ class SerdeDecoratorTest { "e": "A", "nested": { "int": 5, - "float": "-Infinity", - "double": "-Infinity", + "float": null, + "double": null, "sensitive": { "a": "1970-01-01T00:00:00Z" }, @@ -382,8 +397,8 @@ class SerdeDecoratorTest { }, "document": "hello!", "blob": "aGVsbG8=", - "float": "Infinity", - "double": "NaN" + "float": null, + "double": null }""".replace("\\s".toRegex(), "") private val expectedRedacted = @@ -392,8 +407,8 @@ class SerdeDecoratorTest { "e": "", "nested": { "int": 5, - "float": "-Infinity", - "double": "-Infinity", + "float": null, + "double": null, "sensitive": { "a": "" }, @@ -408,127 +423,10 @@ class SerdeDecoratorTest { "union": "", "document": "hello!", "blob": "", - "float": "Infinity", - "double": "NaN" + "float": null, + "double": null } """.replace("\\s".toRegex(), "") private val expectedStreaming = """{"data":"MTIz"}""" - - @Test - fun generateSerializersThatWorkClient() { - val path = - clientIntegrationTest(simpleModel, params = params) { ctx, crate -> - val codegenScope = - arrayOf( - "crate" to RustType.Opaque(ctx.moduleUseName()), - "serde_json" to CargoDependency("serde_json", CratesIo("1")).toDevDependency().toType(), - "ciborium" to CargoDependency.Ciborium.toType(), - // we need the derive feature - "serde" to CargoDependency.Serde.toDevDependency().toType(), - ) - - crate.integrationTest("test_serde") { - unitTest("input_serialized") { - rustTemplate( - """ - use #{crate}::types::{Nested, U, TestEnum}; - use #{crate}::serde::*; - use std::time::UNIX_EPOCH; - use aws_smithy_types::{DateTime, Document, Blob}; - let input = #{crate}::operation::say_hello::SayHelloInput::builder() - .foo("foo-value") - .e("A".into()) - .document(Document::String("hello!".into())) - .blob(Blob::new("hello")) - .float(f32::INFINITY) - .double(f64::NAN) - .nested(Nested::builder() - .int(5) - .float(f32::NEG_INFINITY) - .double(f64::NEG_INFINITY) - .sensitive("a", DateTime::from(UNIX_EPOCH)) - .not_sensitive("a", DateTime::from(UNIX_EPOCH)) - .many_enums("A".into()) - .sparse(None).sparse(Some(TestEnum::A)).sparse(Some(TestEnum::B)) - .build().unwrap() - ) - .union(U::Enum("B".into())) - .build() - .unwrap(); - let mut settings = #{crate}::serde::SerializationSettings::default(); - settings.out_of_range_floats_as_strings = true; - let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_eq!(serialized, ${expectedNoRedactions.dq()}); - settings.redact_sensitive_fields = true; - let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_eq!(serialized, ${expectedRedacted.dq()}); - settings.out_of_range_floats_as_strings = false; - let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_ne!(serialized, ${expectedRedacted.dq()}); - """, - *codegenScope, - ) - } - - unitTest("serde_of_bytestream") { - rustTemplate( - """ - use #{crate}::operation::streaming::StreamingInput; - use #{crate}::primitives::ByteStream; - use #{crate}::serde::*; - let input = StreamingInput::builder().data(ByteStream::from_static(b"123")).build().unwrap(); - let settings = SerializationSettings::default(); - let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_eq!(serialized, ${expectedStreaming.dq()}); - """, - *codegenScope, - ) - } - - unitTest("delegated_serde") { - rustTemplate( - """ - use #{crate}::operation::say_hello::SayHelloInput; - use #{crate}::serde::*; - ##[derive(#{serde}::Serialize)] - struct MyRecord { - ##[serde(serialize_with = "serialize_redacted")] - redact_field: SayHelloInput, - ##[serde(serialize_with = "serialize_unredacted")] - unredacted_field: SayHelloInput - } - let input = SayHelloInput::builder().foo("foo-value").build().unwrap(); - - let field = MyRecord { - redact_field: input.clone(), - unredacted_field: input - }; - let serialized = #{serde_json}::to_string(&field).expect("failed to serialize"); - assert_eq!(serialized, r##"{"redact_field":{"foo":""},"unredacted_field":{"foo":"foo-value"}}"##); - """, - *codegenScope, - ) - } - - unitTest("cbor") { - rustTemplate( - """ - use #{crate}::operation::streaming::StreamingInput; - use #{crate}::primitives::ByteStream; - use #{crate}::serde::*; - let input = StreamingInput::builder().data(ByteStream::from_static(b"123")).build().unwrap(); - let settings = SerializationSettings::default(); - let mut serialized = Vec::new(); - #{ciborium}::ser::into_writer(&input.serialize_ref(&settings), &mut serialized) - .expect("failed to serialize input into CBOR format using `ciborium`"); - assert_eq!(serialized, b"\xa1ddataC123"); - """, - *codegenScope, - ) - } - } - } - "cargo clippy --all-features".runCommand(path) - } } diff --git a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeProtocolTestTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeProtocolTestTest.kt similarity index 74% rename from codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeProtocolTestTest.kt rename to codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeProtocolTestTest.kt index 2b70531cd85..ffd7527b876 100644 --- a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/SerdeProtocolTestTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/serde/SerdeProtocolTestTest.kt @@ -3,11 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.serde +package software.amazon.smithy.rust.codegen.server.smithy.customizations.serde import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.ValueSource import software.amazon.smithy.model.Model import software.amazon.smithy.model.SourceLocation @@ -15,40 +14,24 @@ import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.transform.ModelTransformer -import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest -import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ServiceShapeId 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.util.letIf import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest +import software.amazon.smithy.rust.smithy.shapes.SerdeTrait import java.io.File -import java.util.stream.Stream class SerdeProtocolTestTest { - companion object { - @JvmStatic - fun testedModels(): Stream = - Stream.of( - Arguments.of(ShapeId.from(ServiceShapeId.REST_JSON)), - Arguments.of(ShapeId.from("com.amazonaws.constraints#ConstraintService")), - ) - } - - @Test - fun testRestJson() { - val serviceShapeId = ShapeId.from("com.amazonaws.constraints#ConstraintService") - val model = Model.assembler().discoverModels().assemble().result.get().attachSerdeToService(serviceShapeId) - clientIntegrationTest( - model, - IntegrationTestParams(service = serviceShapeId.toString(), cargoCommand = "cargo test --all-features"), - ) { clientCodegenContext, rustCrate -> - } - } - private fun Model.attachSerdeToService(serviceShapeId: ShapeId): Model { val service = this.expectShape(serviceShapeId, ServiceShape::class.java).toBuilder().addTrait( - SerdeTrait(true, false, null, null, SourceLocation.NONE), + SerdeTrait( + serialize = true, + deserialize = false, + tag = null, + content = null, + sourceLocation = SourceLocation.NONE, + ), ).build() return ModelTransformer.create().mapShapes(this) { serviceShape -> serviceShape.letIf(serviceShape.id == serviceShapeId) { @@ -57,6 +40,17 @@ class SerdeProtocolTestTest { } } + @Test + fun testRpcV2Cbor() { + val serviceShapeId = ShapeId.from("smithy.protocoltests.rpcv2Cbor#RpcV2Protocol") + val model = Model.assembler().discoverModels().assemble().result.get().attachSerdeToService(serviceShapeId) + serverIntegrationTest( + model, + IntegrationTestParams(service = serviceShapeId.toString(), cargoCommand = "cargo test --all-features"), + ) { _, _ -> + } + } + @ParameterizedTest @ValueSource(booleans = [true, false]) fun testConstraintsModel(usePublicConstrainedTypes: Boolean) { @@ -77,7 +71,7 @@ class SerdeProtocolTestTest { cargoCommand = "cargo test --all-features", additionalSettings = constrainedShapesSettings, ), - ) { clientCodegenContext, rustCrate -> + ) { _, _ -> } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index ef481244ba2..37bd499ee8e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,12 +9,12 @@ include(":codegen-core") include(":codegen-client") include(":codegen-client-test") include(":codegen-server") -include(":codegen-serde") include(":codegen-server:python") include(":codegen-server:typescript") include(":codegen-server-test") include(":codegen-server-test:python") include(":codegen-server-test:typescript") +include(":smithy-shapes") include(":rust-runtime") include(":aws:rust-runtime") include(":aws:sdk") diff --git a/codegen-serde/build.gradle.kts b/smithy-shapes/build.gradle.kts similarity index 81% rename from codegen-serde/build.gradle.kts rename to smithy-shapes/build.gradle.kts index e011de24814..f7f03a22e32 100644 --- a/codegen-serde/build.gradle.kts +++ b/smithy-shapes/build.gradle.kts @@ -10,19 +10,16 @@ plugins { `maven-publish` } -description = "Plugin to generate `serde` implementations. NOTE: This is separate from generalized serialization." -extra["displayName"] = "Smithy :: Rust :: Codegen Serde" -extra["moduleName"] = "software.amazon.smithy.rust.codegen.client" +description = "Smithy shapes vended by smithy-rs" +extra["displayName"] = "Smithy :: Rust :: Shapes" -group = "software.amazon.smithy.rust.codegen.serde" +group = "software.amazon.smithy.rust.smithy.shapes" version = "0.1.0" val smithyVersion: String by project dependencies { - implementation(project(":codegen-core")) - implementation(project(":codegen-client")) - implementation(project(":codegen-server")) + api("software.amazon.smithy:smithy-codegen-core:$smithyVersion") } java { @@ -61,7 +58,6 @@ if (isTestingEnabled.toBoolean()) { val kotestVersion: String by project dependencies { - runtimeOnly(project(":rust-runtime")) testImplementation("org.junit.jupiter:junit-jupiter:5.6.1") testImplementation("software.amazon.smithy:smithy-validation-model:$smithyVersion") testImplementation("software.amazon.smithy:smithy-aws-protocol-tests:$smithyVersion") diff --git a/smithy-shapes/src/main/kotlin/software/amazon/smithy/rust/smithy/shapes/SerdeTrait.kt b/smithy-shapes/src/main/kotlin/software/amazon/smithy/rust/smithy/shapes/SerdeTrait.kt new file mode 100644 index 00000000000..a97ffcb7e5f --- /dev/null +++ b/smithy-shapes/src/main/kotlin/software/amazon/smithy/rust/smithy/shapes/SerdeTrait.kt @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.smithy.shapes + +import software.amazon.smithy.build.SmithyBuildException +import software.amazon.smithy.model.SourceLocation +import software.amazon.smithy.model.node.Node +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.AbstractTrait +import software.amazon.smithy.model.traits.Trait + +class SerdeTrait( + private val serialize: Boolean, + private val deserialize: Boolean, + private val tag: String?, + private val content: String?, + sourceLocation: SourceLocation, +) : + AbstractTrait(ID, sourceLocation) { + override fun createNode(): Node { + val builder = + Node.objectNodeBuilder() + .sourceLocation(sourceLocation) + .withMember("serialize", serialize) + .withMember("deserialize", deserialize) + .withMember("tag", tag) + .withMember("content", content) + return builder.build() + } + + class Provider : AbstractTrait.Provider(ID) { + override fun createTrait( + target: ShapeId, + value: Node, + ): Trait = + with(value.expectObjectNode()) { + val serialize = getBooleanMemberOrDefault("serialize", true) + val deserialize = getBooleanMemberOrDefault("deserialize", false) + val tag = getStringMember("tag").orElse(null)?.value + val content = getStringMember("content").orElse(null)?.value + if (deserialize) { + throw SmithyBuildException("deserialize is not currently supported.") + } + val result = + SerdeTrait( + serialize, + deserialize, + tag, + content, + value.sourceLocation, + ) + result.setNodeCache(value) + result + } + } + + companion object { + val ID: ShapeId = ShapeId.from("smithy.rust#serde") + } +} diff --git a/smithy-shapes/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-shapes/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService new file mode 100644 index 00000000000..dd1c6dfc2d4 --- /dev/null +++ b/smithy-shapes/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -0,0 +1 @@ +software.amazon.smithy.rust.smithy.shapes.SerdeTrait$Provider diff --git a/codegen-serde/src/main/resources/META-INF/smithy/manifest b/smithy-shapes/src/main/resources/META-INF/smithy/manifest similarity index 100% rename from codegen-serde/src/main/resources/META-INF/smithy/manifest rename to smithy-shapes/src/main/resources/META-INF/smithy/manifest diff --git a/codegen-serde/src/main/resources/META-INF/smithy/serde.smithy b/smithy-shapes/src/main/resources/META-INF/smithy/serde.smithy similarity index 100% rename from codegen-serde/src/main/resources/META-INF/smithy/serde.smithy rename to smithy-shapes/src/main/resources/META-INF/smithy/serde.smithy