diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 17f0731953..9786184a64 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -17,3 +17,9 @@ produced Rust code that did not compile""" references = ["smithy-rs#2352", "smithy-rs#2343"] meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "server"} author = "82marbag" + +[[smithy-rs]] +message = "Support for constraint traits on member shapes (constraint trait precedence) has been added." +references = ["smithy-rs#1969"] +meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "server" } +author = "drganjoo" diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt index 8d07242211..a8060070ea 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.client.testutil.clientTestRustSetting import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator @@ -48,6 +49,7 @@ abstract class ClientEventStreamBaseRequirements : EventStreamTestRequirements() + ErrorGenerator( + codegenContext.model, + codegenContext.symbolProvider, + writer, + shape, + errorTrait, + emptyList(), + ).render() } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt index 936d3b6324..b73d12a8d8 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt @@ -17,12 +17,13 @@ import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig +import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest class ClientEventStreamMarshallerGeneratorTest { @ParameterizedTest @ArgumentsSource(TestCasesProvider::class) fun test(testCase: EventStreamTestModels.TestCase) { - EventStreamTestTools.runTestCase( + EventStreamTestTools.setupTestCase( testCase, object : ClientEventStreamBaseRequirements() { override fun renderGenerator( @@ -41,6 +42,6 @@ class ClientEventStreamMarshallerGeneratorTest { }, CodegenTarget.CLIENT, EventStreamTestVariety.Marshall, - ) + ).compileAndTest() } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt index f9be7b3bf4..b20810ef97 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt @@ -19,12 +19,13 @@ import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject +import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest class ClientEventStreamUnmarshallerGeneratorTest { @ParameterizedTest @ArgumentsSource(TestCasesProvider::class) fun test(testCase: EventStreamTestModels.TestCase) { - EventStreamTestTools.runTestCase( + EventStreamTestTools.setupTestCase( testCase, object : ClientEventStreamBaseRequirements() { override fun renderGenerator( @@ -44,6 +45,6 @@ class ClientEventStreamUnmarshallerGeneratorTest { }, CodegenTarget.CLIENT, EventStreamTestVariety.Unmarshall, - ) + ).compileAndTest() } } 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 d11811ae36..a0e0e1caa6 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.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.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator @@ -63,6 +64,7 @@ interface EventStreamTestRequirements { /** Render a builder for the given shape */ fun renderBuilderForShape( + rustCrate: RustCrate, writer: RustWriter, codegenContext: C, shape: StructureShape, @@ -75,16 +77,23 @@ interface EventStreamTestRequirements { symbolProvider: RustSymbolProvider, operationOrEventStream: Shape, ) + + /** Render an error struct and builder */ + fun renderError(rustCrate: RustCrate, writer: RustWriter, codegenContext: C, shape: StructureShape) } object EventStreamTestTools { - fun runTestCase( + fun setupTestCase( testCase: EventStreamTestModels.TestCase, requirements: EventStreamTestRequirements, codegenTarget: CodegenTarget, variety: EventStreamTestVariety, - ) { - val model = EventStreamNormalizer.transform(OperationNormalizer.transform(testCase.model)) + transformers: List<(Model) -> Model> = listOf(), + ): TestWriterDelegator { + val model = (listOf(OperationNormalizer::transform, EventStreamNormalizer::transform) + transformers).fold(testCase.model) { model, transformer -> + transformer(model) + } + val serviceShape = model.expectShape(ShapeId.from("test#TestService")) as ServiceShape val codegenContext = requirements.createCodegenContext( model, @@ -102,7 +111,8 @@ object EventStreamTestTools { EventStreamTestVariety.Unmarshall -> writeUnmarshallTestCases(testCase, codegenTarget, generator) } } - test.project.compileAndTest() + + return test.project } private fun generateTestProject( @@ -115,7 +125,6 @@ object EventStreamTestTools { 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 errors = model.serviceShapes .flatMap { walker.walkShapes(it) } @@ -126,8 +135,7 @@ object EventStreamTestTools { requirements.renderOperationError(this, model, symbolProvider, operationShape) requirements.renderOperationError(this, model, symbolProvider, unionShape) for (shape in errors) { - StructureGenerator(model, symbolProvider, this, shape).render(codegenTarget) - requirements.renderBuilderForShape(this, codegenContext, shape) + requirements.renderError(project, this, codegenContext, shape) } } val inputOutput = model.lookup("test#TestStreamInputOutput") 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 48e7082cc7..5e0cd0a136 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 @@ -75,6 +75,7 @@ class PythonServerCodegenVisitor( serviceShape: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig, publicConstrainedTypes: Boolean, + includeConstraintShapeProvider: Boolean, ) = RustServerCodegenPythonPlugin.baseSymbolProvider(model, serviceShape, symbolVisitorConfig, publicConstrainedTypes) val serverSymbolProviders = ServerSymbolProviders.from( diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/RustServerCodegenPythonPlugin.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/RustServerCodegenPythonPlugin.kt index 19e3e0dba2..e5694a06cd 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/RustServerCodegenPythonPlugin.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/RustServerCodegenPythonPlugin.kt @@ -79,7 +79,7 @@ class RustServerCodegenPythonPlugin : SmithyBuildPlugin { // Generate public constrained types for directly constrained shapes. // In the Python server project, this is only done to generate constrained types for simple shapes (e.g. // a `string` shape with the `length` trait), but these always remain `pub(crate)`. - .let { if (constrainedTypes) ConstrainedShapeSymbolProvider(it, model, serviceShape) else it } + .let { if (constrainedTypes) ConstrainedShapeSymbolProvider(it, model, serviceShape, constrainedTypes) else it } // Generate different types for EventStream shapes (e.g. transcribe streaming) .let { EventStreamSymbolProvider(symbolVisitorConfig.runtimeConfig, it, model, CodegenTarget.SERVER) } // Add Rust attributes (like `#[derive(PartialEq)]`) to generated shapes diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProvider.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProvider.kt index a2e1fdb709..e061894f75 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProvider.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProvider.kt @@ -19,8 +19,12 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShortShape import software.amazon.smithy.model.shapes.StringShape +import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.traits.LengthTrait +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.RustType +import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.contextName @@ -29,9 +33,13 @@ import software.amazon.smithy.rust.codegen.core.smithy.handleRustBoxing import software.amazon.smithy.rust.codegen.core.smithy.locatedIn import software.amazon.smithy.rust.codegen.core.smithy.rustType import software.amazon.smithy.rust.codegen.core.smithy.symbolBuilder +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.orNull 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.generators.serverBuilderModule +import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait /** * The [ConstrainedShapeSymbolProvider] returns, for a given _directly_ @@ -56,14 +64,16 @@ class ConstrainedShapeSymbolProvider( private val base: RustSymbolProvider, private val model: Model, private val serviceShape: ServiceShape, + private val publicConstrainedTypes: Boolean, ) : WrappingSymbolProvider(base) { private val nullableIndex = NullableIndex.of(model) private fun publicConstrainedSymbolForMapOrCollectionShape(shape: Shape): Symbol { check(shape is MapShape || shape is CollectionShape) - val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase()) - return symbolBuilder(shape, rustType).locatedIn(ServerRustModule.Model).build() + val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes) + val rustType = RustType.Opaque(name) + return symbolBuilder(shape, rustType).locatedIn(module).build() } override fun toSymbol(shape: Shape): Symbol { @@ -74,8 +84,14 @@ class ConstrainedShapeSymbolProvider( val target = model.expectShape(shape.target) val targetSymbol = this.toSymbol(target) // Handle boxing first, so we end up with `Option>`, not `Box>`. - handleOptionality(handleRustBoxing(targetSymbol, shape), shape, nullableIndex, base.config().nullabilityCheckMode) + handleOptionality( + handleRustBoxing(targetSymbol, shape), + shape, + nullableIndex, + base.config().nullabilityCheckMode, + ) } + is MapShape -> { if (shape.isDirectlyConstrained(base)) { check(shape.hasTrait()) { @@ -91,6 +107,7 @@ class ConstrainedShapeSymbolProvider( .build() } } + is CollectionShape -> { if (shape.isDirectlyConstrained(base)) { check(constrainedCollectionCheck(shape)) { @@ -105,8 +122,11 @@ class ConstrainedShapeSymbolProvider( is StringShape, is IntegerShape, is ShortShape, is LongShape, is ByteShape, is BlobShape -> { if (shape.isDirectlyConstrained(base)) { - val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase()) - symbolBuilder(shape, rustType).locatedIn(ServerRustModule.Model).build() + // A standalone constrained shape goes into `ModelsModule`, but one + // arising from a constrained member shape goes into a module for the container. + val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes) + val rustType = RustType.Opaque(name) + symbolBuilder(shape, rustType).locatedIn(module).build() } else { base.toSymbol(shape) } @@ -122,9 +142,51 @@ class ConstrainedShapeSymbolProvider( * - That it has no unsupported constraints applied. */ private fun constrainedCollectionCheck(shape: CollectionShape): Boolean { - val supportedConstraintTraits = supportedCollectionConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet() + val supportedConstraintTraits = + supportedCollectionConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet() val allConstraintTraits = allConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet() - return supportedConstraintTraits.isNotEmpty() && allConstraintTraits.subtract(supportedConstraintTraits).isEmpty() + return supportedConstraintTraits.isNotEmpty() && allConstraintTraits.subtract(supportedConstraintTraits) + .isEmpty() + } + + /** + * Returns the pair (Rust Symbol Name, Inline Module) for the shape. At the time of model transformation all + * constrained member shapes are extracted and are given a model-wide unique name. However, the generated code + * for the new shapes is in a module that is named after the containing shape (structure, list, map or union). + * The new shape's Rust Symbol is renamed from `{structureName}{memberName}` to `{structure_name}::{member_name}` + */ + private fun getMemberNameAndModule( + shape: Shape, + serviceShape: ServiceShape, + defaultModule: RustModule.LeafModule, + pubCrateServerBuilder: Boolean, + ): Pair { + val syntheticMemberTrait = shape.getTrait() + ?: return Pair(shape.contextName(serviceShape), defaultModule) + + return if (syntheticMemberTrait.container is StructureShape) { + val builderModule = syntheticMemberTrait.container.serverBuilderModule(base, pubCrateServerBuilder) + val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name + Pair(renameTo.toPascalCase(), builderModule) + } else { + // For non-structure shapes, the new shape defined for a constrained member shape + // needs to be placed in an inline module named `pub {container_name_in_snake_case}`. + val moduleName = RustReservedWords.escapeIfNeeded(syntheticMemberTrait.container.id.name.toSnakeCase()) + val innerModuleName = moduleName + if (pubCrateServerBuilder) { + "_internal" + } else { + "" + } + + val innerModule = RustModule.new( + innerModuleName, + visibility = Visibility.publicIf(!pubCrateServerBuilder, Visibility.PUBCRATE), + parent = defaultModule, + inline = true, + ) + val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name + Pair(renameTo.toPascalCase(), innerModule) + } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintViolationSymbolProvider.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintViolationSymbolProvider.kt index 12d838db54..b47103aa75 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintViolationSymbolProvider.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintViolationSymbolProvider.kt @@ -29,8 +29,10 @@ import software.amazon.smithy.rust.codegen.core.smithy.contextName import software.amazon.smithy.rust.codegen.core.smithy.locatedIn import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.smithy.rustType +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderSymbol +import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait /** * The [ConstraintViolationSymbolProvider] returns, for a given constrained @@ -79,15 +81,29 @@ class ConstraintViolationSymbolProvider( private fun Shape.shapeModule(): RustModule.LeafModule { val documentation = if (publicConstrainedTypes && this.isDirectlyConstrained(base)) { - "See [`${this.contextName(serviceShape)}`]." + val symbol = base.toSymbol(this) + "See [`${this.contextName(serviceShape)}`]($symbol)." } else { null } - return RustModule.new( + + val syntheticTrait = getTrait() + + val (module, name) = if (syntheticTrait != null) { + // For constrained member shapes, the ConstraintViolation code needs to go in an inline rust module + // that is a descendant of the module that contains the extracted shape itself. + val overriddenMemberModule = this.getParentAndInlineModuleForConstrainedMember(base, publicConstrainedTypes)!! + val name = syntheticTrait.member.memberName + Pair(overriddenMemberModule.second, RustReservedWords.escapeIfNeeded(name).toSnakeCase()) + } else { // Need to use the context name so we get the correct name for maps. - name = RustReservedWords.escapeIfNeeded(this.contextName(serviceShape)).toSnakeCase(), + Pair(ServerRustModule.Model, RustReservedWords.escapeIfNeeded(this.contextName(serviceShape)).toSnakeCase()) + } + + return RustModule.new( + name = name, visibility = visibility, - parent = ServerRustModule.Model, + parent = module, inline = true, documentation = documentation, ) 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 14fd6a5d0b..4bac39c2df 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 @@ -26,11 +26,19 @@ import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.model.traits.RangeTrait import software.amazon.smithy.model.traits.RequiredTrait 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.isOptional +import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.toSnakeCase +import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderModule +import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait /** * This file contains utilities to work with constrained shapes. @@ -160,3 +168,47 @@ fun Shape.typeNameContainsNonPublicType( is StructureShape, is UnionShape -> false else -> UNREACHABLE("the above arms should be exhaustive, but we received shape: $this") } + +/** + * For synthetic shapes that are added to the model because of member constrained shapes, it returns + * the "container" and "the member shape" that originally had the constraint trait. For all other + * shapes, it returns null. + */ +fun Shape.overriddenConstrainedMemberInfo(): Pair? { + val trait = getTrait() ?: return null + return Pair(trait.container, trait.member) +} + +/** + * Returns the parent and the inline module that this particular shape should go in. + */ +fun Shape.getParentAndInlineModuleForConstrainedMember(symbolProvider: SymbolProvider, publicConstrainedTypes: Boolean): Pair? { + val overriddenTrait = getTrait() ?: return null + return if (overriddenTrait.container is StructureShape) { + val structureModule = symbolProvider.toSymbol(overriddenTrait.container).module() + val builderModule = overriddenTrait.container.serverBuilderModule(symbolProvider, !publicConstrainedTypes) + Pair(structureModule, builderModule) + } else { + // For constrained member shapes, the ConstraintViolation code needs to go in an inline rust module + // that is a descendant of the module that contains the extracted shape itself. + return if (publicConstrainedTypes) { + // Non-structured shape types need to go into their own module. + val shapeSymbol = symbolProvider.toSymbol(this) + val shapeModule = shapeSymbol.module() + check(!shapeModule.parent.isInline()) { + "Parent module of $id should not be an inline module." + } + Pair(shapeModule.parent as RustModule.LeafModule, shapeModule) + } else { + val name = RustReservedWords.escapeIfNeeded(overriddenTrait.container.id.name).toSnakeCase() + "_internal" + val innerModule = RustModule.new( + name = name, + visibility = Visibility.PUBCRATE, + parent = ServerRustModule.Model, + inline = true, + ) + + Pair(ServerRustModule.Model, innerModule) + } + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriter.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriter.kt new file mode 100644 index 0000000000..f4f352260e --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriter.kt @@ -0,0 +1,349 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.shapes.Shape +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.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.module +import java.util.concurrent.ConcurrentHashMap + +typealias DocWriter = () -> Any +typealias InlineModuleCreator = (Symbol, Writable) -> Unit + +/** + * Initializes RustCrate -> InnerModule data structure. + */ +fun RustCrate.initializeInlineModuleWriter(debugMode: Boolean): InnerModule = + crateToInlineModule + .getOrPut(this) { InnerModule(debugMode) } + +/** + * Returns the InnerModule for the given RustCrate + */ +fun RustCrate.getInlineModuleWriter(): InnerModule { + return crateToInlineModule.getOrPut(this) { InnerModule(false) } +} + +/** + * Returns a function that can be used to create an inline module writer. + */ +fun RustCrate.createInlineModuleCreator(): InlineModuleCreator { + return { symbol: Symbol, writable: Writable -> + this.getInlineModuleWriter().withInlineModuleHierarchyUsingCrate(this, symbol.module()) { + writable() + } + } +} + +/** + * If the passed in `shape` is a synthetic extracted shape resulting from a constrained struct member, + * the `Writable` is called using the structure's builder module. Otherwise, the `Writable` is called + * using the given `module`. + */ +fun RustCrate.withModuleOrWithStructureBuilderModule( + module: RustModule, + shape: Shape, + codegenContext: ServerCodegenContext, + codeWritable: Writable, +) { + // All structure constrained-member-shapes code is generated inside the structure builder's module. + val parentAndInlineModuleInfo = + shape.getParentAndInlineModuleForConstrainedMember(codegenContext.symbolProvider, codegenContext.settings.codegenConfig.publicConstrainedTypes) + if (parentAndInlineModuleInfo == null) { + this.withModule(module, codeWritable) + } else { + val (parent, inline) = parentAndInlineModuleInfo + val inlineWriter = this.getInlineModuleWriter() + + inlineWriter.withInlineModuleHierarchyUsingCrate(this, parent) { + inlineWriter.withInlineModuleHierarchy(this, inline) { + codeWritable(this) + } + } + } +} + +/** + * If the passed in `shape` is a synthetic extracted shape resulting from a constrained struct member, + * the `Writable` is called using the structure's builder module. Otherwise, the `Writable` is called + * using shape's `module`. + */ +fun RustCrate.useShapeWriterOrUseWithStructureBuilder( + shape: Shape, + codegenContext: ServerCodegenContext, + docWriter: DocWriter? = null, + writable: Writable, +) { + // All structure constrained-member-shapes code is generated inside the structure builder's module. + val parentAndInlineModuleInfo = + shape.getParentAndInlineModuleForConstrainedMember(codegenContext.symbolProvider, codegenContext.settings.codegenConfig.publicConstrainedTypes) + if (parentAndInlineModuleInfo == null) { + docWriter?.invoke() + this.useShapeWriter(shape, writable) + } else { + val (parent, inline) = parentAndInlineModuleInfo + val inlineWriter = this.getInlineModuleWriter() + + inlineWriter.withInlineModuleHierarchyUsingCrate(this, parent) { + inlineWriter.withInlineModuleHierarchy(this, inline) { + writable(this) + } + } + } +} + +fun RustCrate.renderInlineMemoryModules() { + val inlineModule = crateToInlineModule[this] + check(inlineModule != null) { + "InlineModule writer has not been registered for this crate" + } + inlineModule.render() +} + +/** + * Given a `RustWriter` calls the `Writable` using a `RustWriter` for the `inlineModule` + */ +fun RustCrate.withInMemoryInlineModule( + outerWriter: RustWriter, + inlineModule: RustModule.LeafModule, + docWriter: DocWriter?, + codeWritable: Writable, +) { + check(inlineModule.isInline()) { + "Module has to be an inline module for it to be used with the InlineModuleWriter" + } + this.getInlineModuleWriter().withInlineModuleHierarchy(outerWriter, inlineModule, docWriter) { + codeWritable(this) + } +} + +fun RustWriter.createTestInlineModuleCreator(): InlineModuleCreator { + return { symbol: Symbol, writable: Writable -> + this.withInlineModule(symbol.module()) { + writable() + } + } +} + +/** + * Maintains the `RustWriter` that has been created for a `RustModule.LeafModule`. + */ +private data class InlineModuleWithWriter(val inlineModule: RustModule.LeafModule, val writer: RustWriter) + +/** + * For each RustCrate a separate mapping of inline-module to `RustWriter` is maintained. + */ +private val crateToInlineModule: ConcurrentHashMap = + ConcurrentHashMap() + +class InnerModule(debugMode: Boolean) { + // Holds the root modules to start rendering the descendents from. + private val topLevelModuleWriters: ConcurrentHashMap = ConcurrentHashMap() + private val inlineModuleWriters: ConcurrentHashMap> = ConcurrentHashMap() + private val docWriters: ConcurrentHashMap> = ConcurrentHashMap() + private val writerCreator = RustWriter.factory(debugMode) + + // By default, when a RustWriter is rendered, it prints a comment on top + // indicating that it contains generated code and should not be manually edited. This comment + // appears on each descendent inline module. To remove those comments, each time an inline + // module is rendered, first `emptyLineCount` characters are removed from it. + private val emptyLineCount: Int = writerCreator + .apply("lines-it-always-writes.rs", "crate") + .toString() + .split("\n")[0] + .length + + fun withInlineModule(outerWriter: RustWriter, innerModule: RustModule.LeafModule, docWriter: DocWriter? = null, writable: Writable) { + if (docWriter != null) { + val moduleDocWriterList = docWriters.getOrPut(innerModule) { mutableListOf() } + moduleDocWriterList.add(docWriter) + } + writable(getWriter(outerWriter, innerModule)) + } + + /** + * Given a `RustCrate` and a `RustModule.LeafModule()`, it creates a writer to that module and calls the writable. + */ + fun withInlineModuleHierarchyUsingCrate(rustCrate: RustCrate, inlineModule: RustModule.LeafModule, docWriter: DocWriter? = null, writable: Writable) { + val hierarchy = getHierarchy(inlineModule).toMutableList() + check(!hierarchy.first().isInline()) { + "When adding a `RustModule.LeafModule` to the crate, the topmost module in the hierarchy cannot be an inline module." + } + // The last in the hierarchy is the one we will return the writer for. + val bottomMost = hierarchy.removeLast() + + // In case it is a top level module that has been passed (e.g. ModelsModule, OutputsModule) then + // register it with the topLevel writers and call the writable on it. Otherwise, go over the + // complete hierarchy, registering each of the inner modules and then call the `Writable` + // with the bottom most inline module that has been passed. + if (hierarchy.isNotEmpty()) { + val topMost = hierarchy.removeFirst() + + // Create an intermediate writer for all inner modules in the hierarchy. + rustCrate.withModule(topMost) { + var writer = this + hierarchy.forEach { + writer = getWriter(writer, it) + } + + withInlineModule(writer, bottomMost, docWriter, writable) + } + } else { + check(!bottomMost.isInline()) { + "There is only one module in the hierarchy, so it has to be non-inlined." + } + rustCrate.withModule(bottomMost) { + registerTopMostWriter(this) + writable(this) + } + } + } + + /** + * Given a `Writer` to a module and an inline `RustModule.LeafModule()`, it creates a writer to that module and calls the writable. + * It registers the complete hierarchy including the `outerWriter` if that is not already registrered. + */ + fun withInlineModuleHierarchy(outerWriter: RustWriter, inlineModule: RustModule.LeafModule, docWriter: DocWriter? = null, writable: Writable) { + val hierarchy = getHierarchy(inlineModule).toMutableList() + if (!hierarchy.first().isInline()) { + hierarchy.removeFirst() + } + check(hierarchy.isNotEmpty()) { + "An inline module should always have one parent besides itself." + } + + // The last in the hierarchy is the module under which the new inline module resides. + val bottomMost = hierarchy.removeLast() + + // Create an entry in the HashMap for all the descendent modules in the hierarchy. + var writer = outerWriter + hierarchy.forEach { + writer = getWriter(writer, it) + } + + withInlineModule(writer, bottomMost, docWriter, writable) + } + + /** + * Creates an in memory writer and registers it with a map of RustWriter -> listOf(Inline descendent modules) + */ + private fun createNewInlineModule(): RustWriter { + val writer = writerCreator.apply("unknown-module-would-never-be-written.rs", "crate") + // Register the new RustWriter in the map to allow further descendent inline modules to be created inside it. + inlineModuleWriters[writer] = mutableListOf() + return writer + } + + /** + * Returns the complete hierarchy of a `RustModule.LeafModule` from top to bottom + */ + private fun getHierarchy(module: RustModule.LeafModule): List { + var current: RustModule = module + var hierarchy = listOf() + + while (current is RustModule.LeafModule) { + hierarchy = listOf(current) + hierarchy + current = current.parent + } + + return hierarchy + } + + /** + * Writes out each inline module's code (`toString`) to the respective top level `RustWriter`. + */ + fun render() { + var writerToAddDependencies: RustWriter? = null + + fun writeInlineCode(rustWriter: RustWriter, code: String) { + val inlineCode = code.drop(emptyLineCount) + rustWriter.writeWithNoFormatting(inlineCode) + } + + fun renderDescendents(topLevelWriter: RustWriter, inMemoryWriter: RustWriter) { + // Traverse all descendent inline modules and render them. + inlineModuleWriters[inMemoryWriter]!!.forEach { + writeDocs(it.inlineModule) + + topLevelWriter.withInlineModule(it.inlineModule) { + writeInlineCode(this, it.writer.toString()) + renderDescendents(this, it.writer) + } + + // Add dependencies introduced by the inline module to the top most RustWriter. + it.writer.dependencies.forEach { dep -> writerToAddDependencies!!.addDependency(dep) } + } + } + + // Go over all the top level modules, create an `inlineModule` on the `RustWriter` + // and call the descendent hierarchy renderer using the `inlineModule::RustWriter`. + topLevelModuleWriters.keys.forEach { + writerToAddDependencies = it + + check(inlineModuleWriters[it] != null) { + "There must be a registered RustWriter for this module." + } + + renderDescendents(it, it) + } + } + + /** + * Given the inline-module returns an existing `RustWriter`, or if that inline module + * has never been registered before then a new `RustWriter` is created and returned. + */ + private fun getWriter(outerWriter: RustWriter, inlineModule: RustModule.LeafModule): RustWriter { + val nestedModuleWriter = inlineModuleWriters[outerWriter] + if (nestedModuleWriter != null) { + return findOrAddToList(nestedModuleWriter, inlineModule) + } + + val inlineWriters = registerTopMostWriter(outerWriter) + return findOrAddToList(inlineWriters, inlineModule) + } + + /** + * Records the root of a dependency graph of inline modules. + */ + private fun registerTopMostWriter(outerWriter: RustWriter): MutableList { + topLevelModuleWriters[outerWriter] = Unit + return inlineModuleWriters.getOrPut(outerWriter) { mutableListOf() } + } + + /** + * Either gets a new `RustWriter` for the inline module or creates a new one and adds it to + * the list of inline modules. + */ + private fun findOrAddToList( + inlineModuleList: MutableList, + lookForModule: RustModule.LeafModule, + ): RustWriter { + val inlineModuleAndWriter = inlineModuleList.firstOrNull() { + it.inlineModule.name == lookForModule.name + } + return if (inlineModuleAndWriter == null) { + val inlineWriter = createNewInlineModule() + inlineModuleList.add(InlineModuleWithWriter(lookForModule, inlineWriter)) + inlineWriter + } else { + check(inlineModuleAndWriter.inlineModule == lookForModule) { + "The two inline modules have the same name but different attributes on them." + } + + inlineModuleAndWriter.writer + } + } + + private fun writeDocs(innerModule: RustModule.LeafModule) { + docWriters[innerModule]?.forEach { + it() + } + } +} 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 812bc50916..f63f6977c4 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 @@ -67,10 +67,11 @@ class RustServerCodegenPlugin : ServerDecoratableBuildPlugin() { serviceShape: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig, constrainedTypes: Boolean = true, + includeConstrainedShapeProvider: Boolean = true, ) = SymbolVisitor(model, serviceShape = serviceShape, config = symbolVisitorConfig) // Generate public constrained types for directly constrained shapes. - .let { if (constrainedTypes) ConstrainedShapeSymbolProvider(it, model, serviceShape) else it } + .let { if (includeConstrainedShapeProvider) ConstrainedShapeSymbolProvider(it, model, serviceShape, constrainedTypes) else it } // Generate different types for EventStream shapes (e.g. transcribe streaming) .let { EventStreamSymbolProvider(symbolVisitorConfig.runtimeConfig, it, model, CodegenTarget.SERVER) } // Generate [ByteStream] instead of `Blob` for streaming binary shapes (e.g. S3 GetObject) 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 da1467cedd..ad5bd41a97 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 @@ -75,6 +75,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.Ser import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerProtocolLoader import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.transformers.AttachValidationExceptionToConstrainedOperationInputsInAllowList +import software.amazon.smithy.rust.codegen.server.smithy.transformers.ConstrainedMemberTransform import software.amazon.smithy.rust.codegen.server.smithy.transformers.RecursiveConstraintViolationBoxer import software.amazon.smithy.rust.codegen.server.smithy.transformers.RemoveEbsModelValidationException import software.amazon.smithy.rust.codegen.server.smithy.transformers.ShapesReachableFromOperationInputTagger @@ -165,6 +166,9 @@ open class ServerCodegenVisitor( .let(RecursiveConstraintViolationBoxer::transform) // Normalize operations by adding synthetic input and output shapes to every operation .let(OperationNormalizer::transform) + // Transforms constrained member shapes into non-constrained member shapes targeting a new shape that + // has the member's constraints. + .let(ConstrainedMemberTransform::transform) // Remove the EBS model's own `ValidationException`, which collides with `smithy.framework#ValidationException` .let(RemoveEbsModelValidationException::transform) // Attach the `smithy.framework#ValidationException` error to operations whose inputs are constrained, @@ -218,9 +222,14 @@ open class ServerCodegenVisitor( } } + rustCrate.initializeInlineModuleWriter(codegenContext.settings.codegenConfig.debugMode) + val serviceShapes = DirectedWalker(model).walkShapes(service) serviceShapes.forEach { it.accept(this) } codegenDecorator.extras(codegenContext, rustCrate) + + rustCrate.getInlineModuleWriter().render() + rustCrate.finalize( settings, model, @@ -268,7 +277,7 @@ open class ServerCodegenVisitor( ) { if (codegenContext.settings.codegenConfig.publicConstrainedTypes || shape.isReachableFromOperationInput()) { val serverBuilderGenerator = ServerBuilderGenerator(codegenContext, shape, validationExceptionConversionGenerator) - serverBuilderGenerator.render(writer) + serverBuilderGenerator.render(rustCrate, writer) if (codegenContext.settings.codegenConfig.publicConstrainedTypes) { writer.implBlock(shape, codegenContext.symbolProvider) { @@ -289,7 +298,7 @@ open class ServerCodegenVisitor( if (!codegenContext.settings.codegenConfig.publicConstrainedTypes) { val serverBuilderGeneratorWithoutPublicConstrainedTypes = ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, shape, validationExceptionConversionGenerator) - serverBuilderGeneratorWithoutPublicConstrainedTypes.render(writer) + serverBuilderGeneratorWithoutPublicConstrainedTypes.render(rustCrate, writer) writer.implBlock(shape, codegenContext.symbolProvider) { serverBuilderGeneratorWithoutPublicConstrainedTypes.renderConvenienceMethod(this) @@ -310,25 +319,29 @@ open class ServerCodegenVisitor( if (renderUnconstrainedList) { logger.info("[rust-server-codegen] Generating an unconstrained type for collection shape $shape") - rustCrate.withModule(ServerRustModule.UnconstrainedModule) { + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.UnconstrainedModule, shape, codegenContext) { UnconstrainedCollectionGenerator( codegenContext, - this, + rustCrate.createInlineModuleCreator(), shape, ).render() } if (!isDirectlyConstrained) { logger.info("[rust-server-codegen] Generating a constrained type for collection shape $shape") - rustCrate.withModule(ServerRustModule.ConstrainedModule) { - PubCrateConstrainedCollectionGenerator(codegenContext, this, shape).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.ConstrainedModule, shape, codegenContext) { + PubCrateConstrainedCollectionGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + shape, + ).render() } } } val constraintsInfo = CollectionTraitInfo.fromShape(shape, codegenContext.constrainedShapeSymbolProvider) if (isDirectlyConstrained) { - rustCrate.withModule(ServerRustModule.Model) { + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { ConstrainedCollectionGenerator( codegenContext, this, @@ -340,12 +353,11 @@ open class ServerCodegenVisitor( } if (isDirectlyConstrained || renderUnconstrainedList) { - rustCrate.withModule(ServerRustModule.Model) { + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { CollectionConstraintViolationGenerator( codegenContext, - this, - shape, - constraintsInfo, + rustCrate.createInlineModuleCreator(), + shape, constraintsInfo, validationExceptionConversionGenerator, ).render() } @@ -362,20 +374,28 @@ open class ServerCodegenVisitor( if (renderUnconstrainedMap) { logger.info("[rust-server-codegen] Generating an unconstrained type for map $shape") - rustCrate.withModule(ServerRustModule.UnconstrainedModule) { - UnconstrainedMapGenerator(codegenContext, this, shape).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.UnconstrainedModule, shape, codegenContext) { + UnconstrainedMapGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + shape, + ).render() } if (!isDirectlyConstrained) { logger.info("[rust-server-codegen] Generating a constrained type for map $shape") - rustCrate.withModule(ServerRustModule.ConstrainedModule) { - PubCrateConstrainedMapGenerator(codegenContext, this, shape).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.ConstrainedModule, shape, codegenContext) { + PubCrateConstrainedMapGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + shape, + ).render() } } } if (isDirectlyConstrained) { - rustCrate.withModule(ServerRustModule.Model) { + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { ConstrainedMapGenerator( codegenContext, this, @@ -386,10 +406,10 @@ open class ServerCodegenVisitor( } if (isDirectlyConstrained || renderUnconstrainedMap) { - rustCrate.withModule(ServerRustModule.Model) { + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { MapConstraintViolationGenerator( codegenContext, - this, + rustCrate.createInlineModuleCreator(), shape, validationExceptionConversionGenerator, ).render() @@ -415,8 +435,13 @@ open class ServerCodegenVisitor( private fun integralShape(shape: NumberShape) { if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { logger.info("[rust-server-codegen] Generating a constrained integral $shape") - rustCrate.withModule(ServerRustModule.Model) { - ConstrainedNumberGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { + ConstrainedNumberGenerator( + codegenContext, rustCrate.createInlineModuleCreator(), + this, + shape, + validationExceptionConversionGenerator, + ).render() } } } @@ -427,7 +452,7 @@ open class ServerCodegenVisitor( ) { if (shape.hasTrait()) { logger.info("[rust-server-codegen] Generating an enum $shape") - rustCrate.useShapeWriter(shape) { + rustCrate.useShapeWriterOrUseWithStructureBuilder(shape, codegenContext) { enumShapeGeneratorFactory(codegenContext, shape).render(this) ConstrainedTraitForEnumGenerator(model, codegenContext.symbolProvider, this, shape).render() } @@ -444,8 +469,14 @@ open class ServerCodegenVisitor( ) } else if (!shape.hasTrait() && shape.isDirectlyConstrained(codegenContext.symbolProvider)) { logger.info("[rust-server-codegen] Generating a constrained string $shape") - rustCrate.withModule(ServerRustModule.Model) { - ConstrainedStringGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { + ConstrainedStringGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + this, + shape, + validationExceptionConversionGenerator, + ).render() } } } @@ -469,15 +500,13 @@ open class ServerCodegenVisitor( ) ) { logger.info("[rust-server-codegen] Generating an unconstrained type for union shape $shape") - rustCrate.withModule(ServerRustModule.UnconstrainedModule) unconstrainedModuleWriter@{ - rustCrate.withModule(ServerRustModule.Model) modelsModuleWriter@{ - UnconstrainedUnionGenerator( - codegenContext, - this@unconstrainedModuleWriter, - this@modelsModuleWriter, - shape, - ).render() - } + rustCrate.withModule(ServerRustModule.UnconstrainedModule) modelsModuleWriter@{ + UnconstrainedUnionGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + this@modelsModuleWriter, + shape, + ).render() } } @@ -525,8 +554,14 @@ open class ServerCodegenVisitor( } if (shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - rustCrate.withModule(ServerRustModule.Model) { - ConstrainedBlobGenerator(codegenContext, this, shape, validationExceptionConversionGenerator).render() + rustCrate.withModuleOrWithStructureBuilderModule(ServerRustModule.Model, shape, codegenContext) { + ConstrainedBlobGenerator( + codegenContext, + rustCrate.createInlineModuleCreator(), + this, + shape, + validationExceptionConversionGenerator, + ).render() } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerSymbolProviders.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerSymbolProviders.kt index 0e368d8517..0c6bec0c6f 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerSymbolProviders.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerSymbolProviders.kt @@ -28,15 +28,16 @@ class ServerSymbolProviders private constructor( service: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig, publicConstrainedTypes: Boolean, - baseSymbolProviderFactory: (model: Model, service: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig, publicConstrainedTypes: Boolean) -> RustSymbolProvider, + baseSymbolProviderFactory: (model: Model, service: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig, publicConstrainedTypes: Boolean, includeConstraintShapeProvider: Boolean) -> RustSymbolProvider, ): ServerSymbolProviders { - val baseSymbolProvider = baseSymbolProviderFactory(model, service, symbolVisitorConfig, publicConstrainedTypes) + val baseSymbolProvider = baseSymbolProviderFactory(model, service, symbolVisitorConfig, publicConstrainedTypes, publicConstrainedTypes) return ServerSymbolProviders( symbolProvider = baseSymbolProvider, constrainedShapeSymbolProvider = baseSymbolProviderFactory( model, service, symbolVisitorConfig, + publicConstrainedTypes, true, ), unconstrainedShapeSymbolProvider = UnconstrainedShapeSymbolProvider( @@ -45,6 +46,7 @@ class ServerSymbolProviders private constructor( service, symbolVisitorConfig, false, + false, ), model, publicConstrainedTypes, service, ), diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/UnconstrainedShapeSymbolProvider.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/UnconstrainedShapeSymbolProvider.kt index a134bbb9e9..7b861c4659 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/UnconstrainedShapeSymbolProvider.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/UnconstrainedShapeSymbolProvider.kt @@ -100,10 +100,12 @@ class UnconstrainedShapeSymbolProvider( check(shape is CollectionShape || shape is MapShape || shape is UnionShape) val name = unconstrainedTypeNameForCollectionOrMapOrUnionShape(shape) + val parent = shape.getParentAndInlineModuleForConstrainedMember(this, publicConstrainedTypes)?.second ?: ServerRustModule.UnconstrainedModule + val module = RustModule.new( RustReservedWords.escapeIfNeeded(name.toSnakeCase()), visibility = Visibility.PUBCRATE, - parent = ServerRustModule.UnconstrainedModule, + parent = parent, inline = true, ) val rustType = RustType.Opaque(name, module.fullyQualifiedPath()) 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 78d6078b05..5116ec82d5 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 @@ -219,18 +219,7 @@ fun validateUnsupportedConstraints( // Traverse the model and error out if: val walker = DirectedWalker(model) - // 1. Constraint traits on member shapes are used. [Constraint trait precedence] has not been implemented yet. - // TODO(https://github.com/awslabs/smithy-rs/issues/1401) - // [Constraint trait precedence]: https://awslabs.github.io/smithy/2.0/spec/model.html#applying-traits - val unsupportedConstraintOnMemberShapeSet = walker - .walkShapes(service) - .asSequence() - .filterIsInstance() - .filterMapShapesToTraits(unsupportedConstraintsOnMemberShapes) - .map { (shape, trait) -> UnsupportedConstraintOnMemberShape(shape as MemberShape, trait) } - .toSet() - - // 2. Constraint traits on streaming blob shapes are used. Their semantics are unclear. + // 1. Constraint traits on streaming blob shapes are used. Their semantics are unclear. // TODO(https://github.com/awslabs/smithy/issues/1389) val unsupportedLengthTraitOnStreamingBlobShapeSet = walker .walkShapes(service) @@ -240,7 +229,7 @@ fun validateUnsupportedConstraints( .map { UnsupportedLengthTraitOnStreamingBlobShape(it, it.expectTrait(), it.expectTrait()) } .toSet() - // 3. Constraint traits in event streams are used. Their semantics are unclear. + // 2. Constraint traits in event streams are used. Their semantics are unclear. // TODO(https://github.com/awslabs/smithy/issues/1388) val eventStreamShapes = walker .walkShapes(service) @@ -261,7 +250,7 @@ fun validateUnsupportedConstraints( val unsupportedConstraintShapeReachableViaAnEventStreamSet = unsupportedConstraintOnNonErrorShapeReachableViaAnEventStreamSet + unsupportedConstraintErrorShapeReachableViaAnEventStreamSet - // 4. Range trait used on unsupported shapes. + // 3. Range trait used on unsupported shapes. // TODO(https://github.com/awslabs/smithy-rs/issues/1401) val unsupportedRangeTraitOnShapeSet = walker .walkShapes(service) @@ -289,8 +278,7 @@ fun validateUnsupportedConstraints( .toSet() val messages = - unsupportedConstraintOnMemberShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + - unsupportedLengthTraitOnStreamingBlobShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + + unsupportedLengthTraitOnStreamingBlobShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + unsupportedConstraintShapeReachableViaAnEventStreamSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + unsupportedRangeTraitOnShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + mapShapeReachableFromUniqueItemsListShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } 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 index eb91206b5e..7d6d3eb9c8 100644 --- 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 @@ -141,8 +141,7 @@ class SmithyValidationExceptionConversionGenerator(private val codegenContext: S Self::Length(length) => crate::model::ValidationExceptionField { message: format!("${it.validationErrorMessage()}", length, &path), path, - }, - """, + },""", ) } if (isKeyConstrained(keyShape, symbolProvider)) { @@ -217,8 +216,7 @@ class SmithyValidationExceptionConversionGenerator(private val codegenContext: S if (isMemberConstrained) { validationExceptionFields += { rust( - """ - Self::Member(index, member_constraint_violation) => + """Self::Member(index, member_constraint_violation) => member_constraint_violation.as_validation_exception_field(path + "/" + &index.to_string()) """, ) @@ -233,7 +231,7 @@ class SmithyValidationExceptionConversionGenerator(private val codegenContext: S } """, "String" to RuntimeType.String, - "AsValidationExceptionFields" to validationExceptionFields.join("\n"), + "AsValidationExceptionFields" to validationExceptionFields.join(""), ) } } 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 ef80796d5a..e2a177f536 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 @@ -6,14 +6,13 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.model.shapes.CollectionShape -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.join import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.makeRustBoxed -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.letIf +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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.canReachConstrainedShape @@ -22,7 +21,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromO class CollectionConstraintViolationGenerator( codegenContext: ServerCodegenContext, - private val modelsModuleWriter: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, private val shape: CollectionShape, private val collectionConstraintsInfo: List, private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, @@ -47,7 +46,7 @@ class CollectionConstraintViolationGenerator( val isMemberConstrained = targetShape.canReachConstrainedShape(model, symbolProvider) val constraintViolationVisibility = Visibility.publicIf(publicConstrainedTypes, Visibility.PUBCRATE) - modelsModuleWriter.withInlineModule(constraintViolationSymbol.module()) { + inlineModuleCreator(constraintViolationSymbol) { val constraintViolationVariants = constraintsInfo.map { it.constraintViolationVariant }.toMutableList() if (isMemberConstrained) { constraintViolationVariants += { @@ -76,6 +75,7 @@ class CollectionConstraintViolationGenerator( // and is for use by the framework. rustTemplate( """ + ##[allow(clippy::enum_variant_names)] ##[derive(Debug, PartialEq)] ${constraintViolationVisibility.toRustQualifier()} enum $constraintViolationName { #{ConstraintViolationVariants:W} 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 1215691787..5a1c3bc4e1 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 @@ -21,9 +21,9 @@ 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 import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.smithy.rustType import software.amazon.smithy.rust.codegen.core.util.orNull +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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 @@ -31,6 +31,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage class ConstrainedBlobGenerator( val codegenContext: ServerCodegenContext, + private val inlineModuleCreator: InlineModuleCreator, val writer: RustWriter, val shape: BlobShape, private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, @@ -110,7 +111,7 @@ class ConstrainedBlobGenerator( "From" to RuntimeType.From, ) - writer.withInlineModule(constraintViolation.module()) { + inlineModuleCreator(constraintViolation) { renderConstraintViolationEnum(this, shape, constraintViolation) } } @@ -152,8 +153,7 @@ data class BlobLength(val lengthTrait: LengthTrait) { Self::Length(length) => crate::model::ValidationExceptionField { message: format!("${lengthTrait.validationErrorMessage()}", length, &path), path, - }, - """, + },""", ) }, this::renderValidationFunction, 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 3ae5a78a08..d146f422b0 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 @@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.shapes.CollectionShape +import software.amazon.smithy.model.shapes.EnumShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.LengthTrait @@ -121,23 +122,23 @@ class ConstrainedCollectionGenerator( writer.rustTemplate( """ - impl #{TryFrom}<$inner> for $name { - type Error = #{ConstraintViolation}; + impl #{TryFrom}<$inner> for $name { + type Error = #{ConstraintViolation}; - /// ${rustDocsTryFromMethod(name, inner)} - fn try_from(value: $inner) -> Result { - #{ConstraintChecks:W} + /// ${rustDocsTryFromMethod(name, inner)} + fn try_from(value: $inner) -> Result { + #{ConstraintChecks:W} - Ok(Self(value)) - } + Ok(Self(value)) } + } - impl #{From}<$name> for $inner { - fn from(value: $name) -> Self { - value.into_inner() - } + impl #{From}<$name> for $inner { + fn from(value: $name) -> Self { + value.into_inner() } - """, + } + """, *codegenScope, "ConstraintChecks" to constraintsInfo.map { it.tryFromCheck }.join("\n"), ) @@ -146,7 +147,8 @@ class ConstrainedCollectionGenerator( if (!publicConstrainedTypes && innerShape.canReachConstrainedShape(model, symbolProvider) && innerShape !is StructureShape && - innerShape !is UnionShape + innerShape !is UnionShape && + innerShape !is EnumShape ) { writer.rustTemplate( """ diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt index e5721e7741..28b0d9f8d7 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt @@ -7,6 +7,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.MapShape +import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.LengthTrait @@ -21,6 +22,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.core.util.expectTrait 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.typeNameContainsNonPublicType /** * [ConstrainedMapGenerator] generates a wrapper tuple newtype holding a constrained `std::collections::HashMap`. @@ -130,6 +132,14 @@ class ConstrainedMapGenerator( valueShape !is StructureShape && valueShape !is UnionShape ) { + val keyShape = model.expectShape(shape.key.target, StringShape::class.java) + val keyNeedsConversion = keyShape.typeNameContainsNonPublicType(model, symbolProvider, publicConstrainedTypes) + val key = if (keyNeedsConversion) { + "k.into()" + } else { + "k" + } + writer.rustTemplate( """ impl #{From}<$name> for #{FullyUnconstrainedSymbol} { @@ -137,7 +147,7 @@ class ConstrainedMapGenerator( value .into_inner() .into_iter() - .map(|(k, v)| (k, v.into())) + .map(|(k, v)| ($key, v.into())) .collect() } } 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 6680d154e9..45ef7dd0a1 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 @@ -25,10 +25,10 @@ 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 import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE import software.amazon.smithy.rust.codegen.core.util.expectTrait import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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 @@ -41,7 +41,8 @@ import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage */ class ConstrainedNumberGenerator( val codegenContext: ServerCodegenContext, - val writer: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, + private val writer: RustWriter, val shape: NumberShape, private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { @@ -132,7 +133,7 @@ class ConstrainedNumberGenerator( writer.renderTryFrom(unconstrainedTypeName, name, constraintViolation, constraintsInfo) - writer.withInlineModule(constraintViolation.module()) { + inlineModuleCreator(constraintViolation) { rust( """ ##[derive(Debug, PartialEq)] 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 73a2ac7a0d..7d3fe75110 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 @@ -27,13 +27,13 @@ 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.expectRustMetadata import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.smithy.testModuleForShape import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.PANIC import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext @@ -48,7 +48,8 @@ import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage */ class ConstrainedStringGenerator( val codegenContext: ServerCodegenContext, - val writer: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, + private val writer: RustWriter, val shape: StringShape, private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { @@ -139,7 +140,7 @@ class ConstrainedStringGenerator( "From" to RuntimeType.From, ) - writer.withInlineModule(constraintViolation.module()) { + inlineModuleCreator(constraintViolation) { renderConstraintViolationEnum(this, shape, constraintViolation) } 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 8bbf95ed88..065a4067c7 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 @@ -8,13 +8,12 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.model.shapes.MapShape 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.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.makeRustBoxed -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.letIf +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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.ConstraintViolationRustBoxTrait @@ -22,7 +21,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromO class MapConstraintViolationGenerator( codegenContext: ServerCodegenContext, - private val modelsModuleWriter: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, val shape: MapShape, private val validationExceptionConversionGenerator: ValidationExceptionConversionGenerator, ) { @@ -67,13 +66,15 @@ class MapConstraintViolationGenerator( } else { Visibility.PUBCRATE } - modelsModuleWriter.withInlineModule(constraintViolationSymbol.module()) { + + inlineModuleCreator(constraintViolationSymbol) { // TODO(https://github.com/awslabs/smithy-rs/issues/1401) We should really have two `ConstraintViolation` // types here. One will just have variants for each constraint trait on the map shape, for use by the user. // The other one will have variants if the shape's key or value is directly or transitively constrained, // and is for use by the framework. rustTemplate( """ + ##[allow(clippy::enum_variant_names)] ##[derive(Debug, PartialEq)] pub${ if (constraintViolationVisibility == Visibility.PUBCRATE) " (crate) " else "" } enum $constraintViolationName { ${if (shape.hasTrait()) "Length(usize)," else ""} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedCollectionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedCollectionGenerator.kt index 09f9352cde..ac4426fa29 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedCollectionGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedCollectionGenerator.kt @@ -7,7 +7,6 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.model.shapes.CollectionShape import software.amazon.smithy.model.shapes.MapShape -import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock @@ -16,7 +15,7 @@ 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.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.isOptional -import software.amazon.smithy.rust.codegen.core.smithy.module +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.canReachConstrainedShape import software.amazon.smithy.rust.codegen.server.smithy.isDirectlyConstrained @@ -41,7 +40,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.typeNameContainsNonPubl */ class PubCrateConstrainedCollectionGenerator( val codegenContext: ServerCodegenContext, - val writer: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, val shape: CollectionShape, ) { private val model = codegenContext.model @@ -74,7 +73,7 @@ class PubCrateConstrainedCollectionGenerator( "From" to RuntimeType.From, ) - writer.withInlineModule(constrainedSymbol.module()) { + inlineModuleCreator(constrainedSymbol) { rustTemplate( """ ##[derive(Debug, Clone)] diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedMapGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedMapGenerator.kt index 9d5ad81125..838d4da085 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedMapGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/PubCrateConstrainedMapGenerator.kt @@ -8,7 +8,6 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.model.shapes.CollectionShape import software.amazon.smithy.model.shapes.MapShape import software.amazon.smithy.model.shapes.StringShape -import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock @@ -17,7 +16,7 @@ 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.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.isOptional -import software.amazon.smithy.rust.codegen.core.smithy.module +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.canReachConstrainedShape import software.amazon.smithy.rust.codegen.server.smithy.isDirectlyConstrained @@ -40,7 +39,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.typeNameContainsNonPubl */ class PubCrateConstrainedMapGenerator( val codegenContext: ServerCodegenContext, - val writer: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, val shape: MapShape, ) { private val model = codegenContext.model @@ -75,7 +74,7 @@ class PubCrateConstrainedMapGenerator( "From" to RuntimeType.From, ) - writer.withInlineModule(constrainedSymbol.module()) { + inlineModuleCreator(constrainedSymbol) { rustTemplate( """ ##[derive(Debug, Clone)] 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 bd968bb1b7..799997c819 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 @@ -71,7 +71,11 @@ class ServerBuilderConstraintViolations( writer.docs("Holds one variant for each of the ways the builder can fail.") if (nonExhaustive) Attribute.NonExhaustive.render(writer) val constraintViolationSymbolName = constraintViolationSymbolProvider.toSymbol(shape).name - writer.rustBlock("pub${if (visibility == Visibility.PUBCRATE) " (crate) " else ""} enum $constraintViolationSymbolName") { + writer.rustBlock( + """ + ##[allow(clippy::enum_variant_names)] + pub${if (visibility == Visibility.PUBCRATE) " (crate) " else ""} enum $constraintViolationSymbolName""", + ) { renderConstraintViolations(writer) } 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 3d65e4c89e..34a7115714 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 @@ -29,6 +29,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.smithy.isRustBoxed @@ -51,6 +52,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.hasConstraintTraitOrTar import software.amazon.smithy.rust.codegen.server.smithy.targetCanReachConstrainedShape import software.amazon.smithy.rust.codegen.server.smithy.traits.ConstraintViolationRustBoxTrait import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput +import software.amazon.smithy.rust.codegen.server.smithy.withInMemoryInlineModule import software.amazon.smithy.rust.codegen.server.smithy.wouldHaveConstrainedWrapperTupleTypeWerePublicConstrainedTypesEnabled /** @@ -87,7 +89,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.wouldHaveConstrainedWra * [derive_builder]: https://docs.rs/derive_builder/latest/derive_builder/index.html */ class ServerBuilderGenerator( - codegenContext: ServerCodegenContext, + val codegenContext: ServerCodegenContext, private val shape: StructureShape, private val customValidationExceptionWithReasonConversionGenerator: ValidationExceptionConversionGenerator, ) { @@ -153,9 +155,9 @@ class ServerBuilderGenerator( "MaybeConstrained" to RuntimeType.MaybeConstrained, ) - fun render(writer: RustWriter) { - writer.docs("See #D.", structureSymbol) - writer.withInlineModule(builderSymbol.module()) { + fun render(rustCrate: RustCrate, writer: RustWriter) { + val docWriter: () -> Unit = { writer.docs("See #D.", structureSymbol) } + rustCrate.withInMemoryInlineModule(writer, builderSymbol.module(), docWriter) { renderBuilder(this) } } @@ -569,8 +571,7 @@ class ServerBuilderGenerator( writer.rustTemplate( """ .map(|res| - res${if (constrainedTypeHoldsFinalType(member)) "" else ".map(|v| v.into())"} - ${if (errHasBox) ".map_err(Box::new)" else "" } + res${if (constrainedTypeHoldsFinalType(member)) "" else ".map(|v| v.into())"} ${if (errHasBox) ".map_err(Box::new)" else "" } .map_err(ConstraintViolation::${constraintViolation.name()}) ) .transpose()? 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 32e58fd44e..2ae17d1a80 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 @@ -23,12 +23,14 @@ 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.withBlock import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.smithy.makeOptional import software.amazon.smithy.rust.codegen.core.smithy.module 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.withInMemoryInlineModule /** * Generates a builder for the Rust type associated with the [StructureShape]. @@ -90,12 +92,12 @@ class ServerBuilderGeneratorWithoutPublicConstrainedTypes( "MaybeConstrained" to RuntimeType.MaybeConstrained, ) - fun render(writer: RustWriter) { + fun render(rustCrate: RustCrate, writer: RustWriter) { check(!codegenContext.settings.codegenConfig.publicConstrainedTypes) { "ServerBuilderGeneratorWithoutPublicConstrainedTypes should only be used when `publicConstrainedTypes` is false" } - writer.docs("See #D.", structureSymbol) - writer.withInlineModule(builderSymbol.module()) { + val docWriter = { writer.docs("See #D.", structureSymbol) } + rustCrate.withInMemoryInlineModule(writer, builderSymbol.module(), docWriter) { renderBuilder(this) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderSymbol.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderSymbol.kt index 9720717383..a605009ddf 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderSymbol.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderSymbol.kt @@ -2,7 +2,6 @@ * 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.codegen.core.Symbol @@ -23,7 +22,7 @@ fun StructureShape.serverBuilderSymbol(codegenContext: ServerCodegenContext): Sy !codegenContext.settings.codegenConfig.publicConstrainedTypes, ) -fun StructureShape.serverBuilderSymbol(symbolProvider: SymbolProvider, pubCrate: Boolean): Symbol { +fun StructureShape.serverBuilderModule(symbolProvider: SymbolProvider, pubCrate: Boolean): RustModule.LeafModule { val structureSymbol = symbolProvider.toSymbol(this) val builderNamespace = RustReservedWords.escapeIfNeeded(structureSymbol.name.toSnakeCase()) + if (pubCrate) { @@ -35,7 +34,11 @@ fun StructureShape.serverBuilderSymbol(symbolProvider: SymbolProvider, pubCrate: true -> Visibility.PUBCRATE false -> Visibility.PUBLIC } - val builderModule = RustModule.new(builderNamespace, visibility, parent = structureSymbol.module(), inline = true) + return RustModule.new(builderNamespace, visibility, parent = structureSymbol.module(), inline = true) +} + +fun StructureShape.serverBuilderSymbol(symbolProvider: SymbolProvider, pubCrate: Boolean): Symbol { + val builderModule = serverBuilderModule(symbolProvider, pubCrate) val rustType = RustType.Opaque("Builder", builderModule.fullyQualifiedPath()) return Symbol.builder() .rustType(rustType) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt index 3179b5370e..3d3105f9c7 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.Instantiator import software.amazon.smithy.rust.codegen.core.smithy.generators.InstantiatorCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.InstantiatorSection import software.amazon.smithy.rust.codegen.core.smithy.isOptional +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.isDirectlyConstrained import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput @@ -47,12 +48,26 @@ class ServerBuilderKindBehavior(val codegenContext: CodegenContext) : Instantiat override fun hasFallibleBuilder(shape: StructureShape): Boolean { // Only operation input builders take in unconstrained types. val takesInUnconstrainedTypes = shape.isReachableFromOperationInput() - return ServerBuilderGenerator.hasFallibleBuilder( - shape, - codegenContext.model, - codegenContext.symbolProvider, - takesInUnconstrainedTypes, - ) + + val publicConstrainedTypes = if (codegenContext is ServerCodegenContext) { + codegenContext.settings.codegenConfig.publicConstrainedTypes + } else { + true + } + + return if (publicConstrainedTypes) { + ServerBuilderGenerator.hasFallibleBuilder( + shape, + codegenContext.model, + codegenContext.symbolProvider, + takesInUnconstrainedTypes, + ) + } else { + ServerBuilderGeneratorWithoutPublicConstrainedTypes.hasFallibleBuilder( + shape, + codegenContext.symbolProvider, + ) + } } override fun setterName(memberShape: MemberShape): String = codegenContext.symbolProvider.toMemberName(memberShape) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGenerator.kt index 3d233d28ef..6916617b6f 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedCollectionGenerator.kt @@ -18,8 +18,8 @@ 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.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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.UnconstrainedShapeSymbolProvider @@ -40,7 +40,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.traits.ConstraintViolat */ class UnconstrainedCollectionGenerator( val codegenContext: ServerCodegenContext, - private val unconstrainedModuleWriter: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, val shape: CollectionShape, ) { private val model = codegenContext.model @@ -72,7 +72,7 @@ class UnconstrainedCollectionGenerator( val innerMemberSymbol = unconstrainedShapeSymbolProvider.toSymbol(shape.member) - unconstrainedModuleWriter.withInlineModule(symbol.module()) { + inlineModuleCreator(symbol) { rustTemplate( """ ##[derive(Debug, Clone)] diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGenerator.kt index b6445da017..0862a26987 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedMapGenerator.kt @@ -19,8 +19,8 @@ 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.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.server.smithy.InlineModuleCreator 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.canReachConstrainedShape @@ -40,7 +40,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.traits.ConstraintViolat */ class UnconstrainedMapGenerator( val codegenContext: ServerCodegenContext, - private val unconstrainedModuleWriter: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, val shape: MapShape, ) { private val model = codegenContext.model @@ -74,7 +74,7 @@ class UnconstrainedMapGenerator( val keySymbol = unconstrainedShapeSymbolProvider.toSymbol(keyShape) val valueMemberSymbol = unconstrainedShapeSymbolProvider.toSymbol(shape.value) - unconstrainedModuleWriter.withInlineModule(symbol.module()) { + inlineModuleCreator(symbol) { rustTemplate( """ ##[derive(Debug, Clone)] diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt index dac275e051..f3ec8322c7 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt @@ -23,11 +23,11 @@ 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.makeMaybeConstrained import software.amazon.smithy.rust.codegen.core.smithy.makeRustBoxed -import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.smithy.traits.RustBoxTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait 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.InlineModuleCreator 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.canReachConstrainedShape @@ -49,7 +49,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromO */ class UnconstrainedUnionGenerator( val codegenContext: ServerCodegenContext, - private val unconstrainedModuleWriter: RustWriter, + private val inlineModuleCreator: InlineModuleCreator, private val modelsModuleWriter: RustWriter, val shape: UnionShape, ) { @@ -77,7 +77,7 @@ class UnconstrainedUnionGenerator( val constraintViolationSymbol = constraintViolationSymbolProvider.toSymbol(shape) val constraintViolationName = constraintViolationSymbol.name - unconstrainedModuleWriter.withInlineModule(symbol.module()) { + inlineModuleCreator(symbol) { rustBlock( """ ##[allow(clippy::enum_variant_names)] @@ -133,11 +133,16 @@ class UnconstrainedUnionGenerator( } else { Visibility.PUBCRATE } - modelsModuleWriter.withInlineModule( - constraintViolationSymbol.module(), + + inlineModuleCreator( + constraintViolationSymbol, ) { Attribute(derive(RuntimeType.Debug, RuntimeType.PartialEq)).render(this) - rustBlock("pub${if (constraintViolationVisibility == Visibility.PUBCRATE) " (crate)" else ""} enum $constraintViolationName") { + rustBlock( + """ + ##[allow(clippy::enum_variant_names)] + pub${if (constraintViolationVisibility == Visibility.PUBCRATE) " (crate)" else ""} enum $constraintViolationName""", + ) { constraintViolations().forEach { renderConstraintViolation(this, it) } } @@ -223,9 +228,7 @@ class UnconstrainedUnionGenerator( """ { let constrained: #{ConstrainedSymbol} = $unconstrainedVar - .try_into() - $boxIt - $boxErr + .try_into() $boxIt $boxErr .map_err(Self::Error::${ConstraintViolation(member).name()})?; constrained.into() } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt index 89c82808a7..28480bbc5a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt @@ -779,80 +779,11 @@ class ServerProtocolTestGenerator( FailingTest(RestJsonValidation, "RestJsonMalformedRangeMaxFloat", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeMinFloat", TestType.MalformedRequest), - // See https://github.com/awslabs/smithy-rs/issues/1969 - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeShortOverride_case0", TestType.MalformedRequest), - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeShortOverride_case1", TestType.MalformedRequest), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeIntegerOverride_case0", - TestType.MalformedRequest, - ), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeIntegerOverride_case1", - TestType.MalformedRequest, - ), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeLongOverride_case0", - TestType.MalformedRequest, - ), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeLongOverride_case1", - TestType.MalformedRequest, - ), - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeMaxShortOverride", TestType.MalformedRequest), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeMaxIntegerOverride", - TestType.MalformedRequest, - ), - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeMaxLongOverride", TestType.MalformedRequest), - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeMinShortOverride", TestType.MalformedRequest), - FailingTest( - MalformedRangeValidation, - "RestJsonMalformedRangeMinIntegerOverride", - TestType.MalformedRequest, - ), - FailingTest(MalformedRangeValidation, "RestJsonMalformedRangeMinLongOverride", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case1", TestType.MalformedRequest), + // Tests involving floating point shapes and the `@range` trait; see https://github.com/awslabs/smithy-rs/issues/2007 FailingTest(RestJsonValidation, "RestJsonMalformedRangeFloatOverride_case0", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeFloatOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthMaxStringOverride", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthMinStringOverride", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedRangeMaxByteOverride", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeMaxFloatOverride", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedRangeMinByteOverride", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeMinFloatOverride", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternListOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternListOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapKeyOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapKeyOverride_case1", TestType.MalformedRequest), - FailingTest( - RestJsonValidation, - "RestJsonMalformedPatternMapValueOverride_case0", - TestType.MalformedRequest, - ), - FailingTest( - RestJsonValidation, - "RestJsonMalformedPatternMapValueOverride_case1", - TestType.MalformedRequest, - ), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternStringOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternStringOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthBlobOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthBlobOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthListOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthListOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthMapOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthMapOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthStringOverride_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthStringOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedLengthStringOverride_case2", TestType.MalformedRequest), // Some tests for the S3 service (restXml). FailingTest("com.amazonaws.s3#AmazonS3", "GetBucketLocationUnwrappedOutput", TestType.Response), 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 0b1660b010..46dc7348e9 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 @@ -14,6 +14,7 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget 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.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator @@ -120,14 +121,14 @@ fun serverTestCodegenContext( /** * In tests, we frequently need to generate a struct, a builder, and an impl block to access said builder. */ -fun StructureShape.serverRenderWithModelBuilder(model: Model, symbolProvider: RustSymbolProvider, writer: RustWriter) { - StructureGenerator(model, symbolProvider, writer, this).render(CodegenTarget.SERVER) +fun StructureShape.serverRenderWithModelBuilder(rustCrate: RustCrate, model: Model, symbolProvider: RustSymbolProvider, writer: RustWriter) { + StructureGenerator(model, symbolProvider, writer, this, emptyList()).render() val serverCodegenContext = serverTestCodegenContext(model) // Note that this always uses `ServerBuilderGenerator` and _not_ `ServerBuilderGeneratorWithoutPublicConstrainedTypes`, // regardless of the `publicConstrainedTypes` setting. val modelBuilder = ServerBuilderGenerator(serverCodegenContext, this, SmithyValidationExceptionConversionGenerator(serverCodegenContext)) - modelBuilder.render(writer) - writer.implBlock(this, symbolProvider) { + modelBuilder.render(rustCrate, writer) + writer.implBlock(symbolProvider.toSymbol(this)) { modelBuilder.renderConvenienceMethod(this) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/traits/SyntheticStructureFromConstrainedMemberTrait.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/traits/SyntheticStructureFromConstrainedMemberTrait.kt new file mode 100644 index 0000000000..de5890c87a --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/traits/SyntheticStructureFromConstrainedMemberTrait.kt @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.traits + +import software.amazon.smithy.model.node.Node +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.AnnotationTrait + +/** + * Trait applied to an overridden shape indicating the member of this new shape type + */ +class SyntheticStructureFromConstrainedMemberTrait(val container: Shape, val member: MemberShape) : AnnotationTrait(SyntheticStructureFromConstrainedMemberTrait.ID, Node.objectNode()) { + companion object { + val ID: ShapeId = ShapeId.from("smithy.api.internal#overriddenMember") + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/ConstrainedMemberTransform.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/ConstrainedMemberTransform.kt new file mode 100644 index 0000000000..3ab99ce64d --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/transformers/ConstrainedMemberTransform.kt @@ -0,0 +1,226 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy.transformers + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.AbstractShapeBuilder +import software.amazon.smithy.model.shapes.ListShape +import software.amazon.smithy.model.shapes.MapShape +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.shapes.UnionShape +import software.amazon.smithy.model.traits.RequiredTrait +import software.amazon.smithy.model.traits.Trait +import software.amazon.smithy.model.transform.ModelTransformer +import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker +import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait +import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait +import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE +import software.amazon.smithy.rust.codegen.core.util.orNull +import software.amazon.smithy.rust.codegen.server.smithy.allConstraintTraits +import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait +import software.amazon.smithy.utils.ToSmithyBuilder +import java.lang.IllegalStateException +import java.util.* + +/** + * Transforms all member shapes that have constraints on them into equivalent non-constrained + * member shapes targeting synthetic constrained structure shapes with the member's constraints. + * + * E.g.: + * ``` + * structure A { + * @length(min: 1, max: 69) + * string: ConstrainedString + * } + * + * @length(min: 2, max: 10) + * @pattern("^[A-Za-z]+$") + * string ConstrainedString + * ``` + * + * to + * + * ``` + * structure A { + * string: OverriddenConstrainedString + * } + * + * @length(min: 1, max: 69) + * @pattern("^[A-Za-z]+$") + * OverriddenConstrainedString + * + * @length(min: 2, max: 10) + * @pattern("^[A-Za-z]+$") + * string ConstrainedString + * ``` + */ +object ConstrainedMemberTransform { + private data class MemberShapeTransformation( + val newShape: Shape, + val memberToChange: MemberShape, + val traitsToKeep: List, + ) + + private val memberConstraintTraitsToOverride = allConstraintTraits - RequiredTrait::class.java + + private fun Shape.hasMemberConstraintTrait() = + memberConstraintTraitsToOverride.any(this::hasTrait) + + fun transform(model: Model): Model { + val additionalNames = HashSet() + val walker = DirectedWalker(model) + + // Find all synthetic input / output structures that have been added by + // the OperationNormalizer, get constrained members out of those structures, + // convert them into non-constrained members and then pass them to the transformer. + // The transformer will add new shapes, and will replace existing member shapes' target + // with the newly added shapes. + val transformations = model.operationShapes + .flatMap { listOfNotNull(it.input.orNull(), it.output.orNull()) + it.errors } + .mapNotNull { model.expectShape(it).asStructureShape().orElse(null) } + .filter { it.hasTrait(SyntheticInputTrait.ID) || it.hasTrait(SyntheticOutputTrait.ID) } + .flatMap { walker.walkShapes(it) } + .filter { it is StructureShape || it is ListShape || it is UnionShape || it is MapShape } + .flatMap { it.constrainedMembers() } + .mapNotNull { + val transformation = it.makeNonConstrained(model, additionalNames) + // Keep record of new names that have been generated to ensure none of them regenerated. + additionalNames.add(transformation.newShape.id) + + transformation + } + + return applyTransformations(model, transformations) + } + + /*** + * Returns a Model that has all the transformations applied on the original model. + */ + private fun applyTransformations( + model: Model, + transformations: List, + ): Model { + val modelBuilder = model.toBuilder() + + val memberShapesToReplace = transformations.map { + // Add the new shape to the model. + modelBuilder.addShape(it.newShape) + + it.memberToChange.toBuilder() + .target(it.newShape.id) + .traits(it.traitsToKeep) + .build() + } + + // Change all original constrained member shapes with the new standalone shapes. + return ModelTransformer.create() + .replaceShapes(modelBuilder.build(), memberShapesToReplace) + } + + /** + * Returns a list of members that have constraint traits applied to them + */ + private fun Shape.constrainedMembers(): List = + this.allMembers.values.filter { + it.hasMemberConstraintTrait() + } + + /** + * Returns the unique (within the model) shape ID of the new shape + */ + private fun overriddenShapeId( + model: Model, + additionalNames: Set, + memberShape: ShapeId, + ): ShapeId { + val structName = memberShape.name + val memberName = memberShape.member.orElse(null) + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + + fun makeStructName(suffix: String = "") = + ShapeId.from("${memberShape.namespace}#${structName}${memberName}$suffix") + + fun structNameIsUnique(newName: ShapeId) = + model.getShape(newName).isEmpty && !additionalNames.contains(newName) + + fun generateUniqueName(): ShapeId { + // Ensure the name does not already exist in the model, else make it unique + // by appending a new number as the suffix. + (0..100).forEach { + val extractedStructName = if (it == 0) makeStructName("") else makeStructName("$it") + if (structNameIsUnique(extractedStructName)) { + return extractedStructName + } + } + + throw IllegalStateException("A unique name for the overridden structure type could not be generated") + } + + return generateUniqueName() + } + + /** + * Returns the transformation that would be required to turn the given member shape + * into a non-constrained member shape. + */ + private fun MemberShape.makeNonConstrained( + model: Model, + additionalNames: MutableSet, + ): MemberShapeTransformation { + val (memberConstraintTraits, otherTraits) = this.allTraits.values + .partition { + memberConstraintTraitsToOverride.contains(it.javaClass) + } + + check(memberConstraintTraits.isNotEmpty()) { + "There must at least be one member constraint on the shape" + } + + // Build a new shape similar to the target of the constrained member shape. It should + // have all of the original constraints that have not been overridden, and the ones + // that this member shape overrides. + val targetShape = model.expectShape(this.target) + if (targetShape !is ToSmithyBuilder<*>) { + UNREACHABLE("Member target shapes will always be buildable") + } + + return when (val builder = targetShape.toBuilder()) { + is AbstractShapeBuilder<*, *> -> { + // Use the target builder to create a new standalone shape that would + // be added to the model later on. Keep all existing traits on the target + // but replace the ones that are overridden on the member shape. + val nonOverriddenConstraintTraits = + builder.allTraits.values.filter { existingTrait -> + memberConstraintTraits.none { it.toShapeId() == existingTrait.toShapeId() } + } + + // Add a synthetic constraint on all new shapes being defined, that would link + // the new shape to the root structure from which it is reachable. + val syntheticTrait = + SyntheticStructureFromConstrainedMemberTrait(model.expectShape(this.container), this) + + // Combine target traits, overridden traits and the synthetic trait + val newTraits = + nonOverriddenConstraintTraits + memberConstraintTraits + syntheticTrait + + // Create a new unique standalone shape that will be added to the model later on + val shapeId = overriddenShapeId(model, additionalNames, this.id) + val standaloneShape = builder.id(shapeId) + .traits(newTraits) + .build() + + // Since the new shape has not been added to the model as yet, the current + // memberShape's target cannot be changed to the new shape. + MemberShapeTransformation(standaloneShape, this, otherTraits) + } + + else -> UNREACHABLE("Constraint traits cannot to applied on ${this.id}") + } + } +} diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProviderTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProviderTest.kt index d96c761fa8..39a4146351 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProviderTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProviderTest.kt @@ -90,7 +90,7 @@ class ConstrainedShapeSymbolProviderTest { private val model = baseModelString.asSmithyModel() private val serviceShape = model.lookup("test#TestService") private val symbolProvider = serverTestSymbolProvider(model, serviceShape) - private val constrainedShapeSymbolProvider = ConstrainedShapeSymbolProvider(symbolProvider, model, serviceShape) + private val constrainedShapeSymbolProvider = ConstrainedShapeSymbolProvider(symbolProvider, model, serviceShape, true) companion object { @JvmStatic diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsMemberShapeTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsMemberShapeTest.kt new file mode 100644 index 0000000000..5d29d109a3 --- /dev/null +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsMemberShapeTest.kt @@ -0,0 +1,495 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.junit.jupiter.api.Test +import software.amazon.smithy.aws.traits.DataTrait +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.SourceLocation +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.RequiredTrait +import software.amazon.smithy.model.traits.Trait +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.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +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.transformers.OperationNormalizer +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext +import software.amazon.smithy.rust.codegen.core.testutil.unitTest +import software.amazon.smithy.rust.codegen.core.util.runCommand +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.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.testutil.serverTestCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.transformers.ConstrainedMemberTransform +import java.io.File +import java.nio.file.Path + +class ConstraintsMemberShapeTest { + private val outputModelOnly = """ + namespace constrainedMemberShape + + use aws.protocols#restJson1 + use aws.api#data + + @restJson1 + service ConstrainedService { + operations: [OperationUsingGet] + } + + @http(uri: "/anOperation", method: "GET") + operation OperationUsingGet { + output : OperationUsingGetOutput + } + structure OperationUsingGetOutput { + plainLong : Long + plainInteger : Integer + plainShort : Short + plainByte : Byte + plainFloat: Float + plainString: String + + @range(min: 1, max:100) + constrainedLong : Long + @range(min: 2, max:100) + constrainedInteger : Integer + @range(min: 3, max:100) + constrainedShort : Short + @range(min: 4, max:100) + constrainedByte : Byte + @length(max: 100) + constrainedString: String + + @required + @range(min: 5, max:100) + requiredConstrainedLong : Long + @required + @range(min: 6, max:100) + requiredConstrainedInteger : Integer + @required + @range(min: 7, max:100) + requiredConstrainedShort : Short + @required + @range(min: 8, max:100) + requiredConstrainedByte : Byte + @required + @length(max: 101) + requiredConstrainedString: String + + patternString : PatternString + + @data("content") + @pattern("^[g-m]+${'$'}") + constrainedPatternString : PatternString + + plainStringList : PlainStringList + patternStringList : PatternStringList + patternStringListOverride : PatternStringListOverride + + plainStructField : PlainStructWithInteger + structWithConstrainedMember : StructWithConstrainedMember + structWithConstrainedMemberOverride : StructWithConstrainedMemberOverride + + patternUnion: PatternUnion + patternUnionOverride: PatternUnionOverride + patternMap : PatternMap + patternMapOverride: PatternMapOverride + } + list ListWithIntegerMemberStruct { + member: PlainStructWithInteger + } + structure PlainStructWithInteger { + lat : Integer + long : Integer + } + structure StructWithConstrainedMember { + @range(min: 100) + lat : Integer + long : Integer + } + structure StructWithConstrainedMemberOverride { + @range(min: 10) + lat : RangedInteger + @range(min: 10, max:100) + long : RangedInteger + } + list PlainStringList { + member: String + } + list PatternStringList { + member: PatternString + } + list PatternStringListOverride { + @pattern("^[g-m]+${'$'}") + member: PatternString + } + map PatternMap { + key: PatternString, + value: PatternString + } + map PatternMapOverride { + @pattern("^[g-m]+${'$'}") + key: PatternString, + @pattern("^[g-m]+${'$'}") + value: PatternString + } + union PatternUnion { + first: PatternString, + second: PatternString + } + union PatternUnionOverride { + @pattern("^[g-m]+${'$'}") + first: PatternString, + @pattern("^[g-m]+${'$'}") + second: PatternString + } + @pattern("^[a-m]+${'$'}") + string PatternString + @range(min: 0, max:1000) + integer RangedInteger + """.asSmithyModel() + + private fun loadModel(model: Model): Model = + ConstrainedMemberTransform.transform(OperationNormalizer.transform(model)) + + @Test + fun `non constrained fields should not be changed`() { + val transformedModel = loadModel(outputModelOnly) + + fun checkFieldTargetRemainsSame(fieldName: String) { + checkMemberShapeIsSame( + transformedModel, + outputModelOnly, + "constrainedMemberShape.synthetic#OperationUsingGetOutput\$$fieldName", + "constrainedMemberShape#OperationUsingGetOutput\$$fieldName", + ) { + "OperationUsingGetOutput$fieldName has changed whereas it is not constrained and should have remained same" + } + } + + setOf( + "plainInteger", + "plainLong", + "plainByte", + "plainShort", + "plainFloat", + "patternString", + "plainStringList", + "patternStringList", + "patternStringListOverride", + "plainStructField", + "structWithConstrainedMember", + "structWithConstrainedMemberOverride", + "patternUnion", + "patternUnionOverride", + "patternMap", + "patternMapOverride", + ).forEach(::checkFieldTargetRemainsSame) + + checkMemberShapeIsSame( + transformedModel, + outputModelOnly, + "constrainedMemberShape#StructWithConstrainedMember\$long", + "constrainedMemberShape#StructWithConstrainedMember\$long", + ) + } + + @Test + fun `constrained members should have a different target now`() { + val transformedModel = loadModel(outputModelOnly) + checkMemberShapeChanged( + transformedModel, + outputModelOnly, + "constrainedMemberShape#PatternStringListOverride\$member", + "constrainedMemberShape#PatternStringListOverride\$member", + ) + + fun checkSyntheticFieldTargetChanged(fieldName: String) { + checkMemberShapeChanged( + transformedModel, + outputModelOnly, + "constrainedMemberShape.synthetic#OperationUsingGetOutput\$$fieldName", + "constrainedMemberShape#OperationUsingGetOutput\$$fieldName", + ) { + "constrained member $fieldName should have been changed into a new type." + } + } + + fun checkFieldTargetChanged(memberNameWithContainer: String) { + checkMemberShapeChanged( + transformedModel, + outputModelOnly, + "constrainedMemberShape#$memberNameWithContainer", + "constrainedMemberShape#$memberNameWithContainer", + ) { + "constrained member $memberNameWithContainer should have been changed into a new type." + } + } + + setOf( + "constrainedLong", + "constrainedByte", + "constrainedShort", + "constrainedInteger", + "constrainedString", + "requiredConstrainedString", + "requiredConstrainedLong", + "requiredConstrainedByte", + "requiredConstrainedInteger", + "requiredConstrainedShort", + "constrainedPatternString", + ).forEach(::checkSyntheticFieldTargetChanged) + + setOf( + "StructWithConstrainedMember\$lat", + "PatternMapOverride\$key", + "PatternMapOverride\$value", + "PatternStringListOverride\$member", + ).forEach(::checkFieldTargetChanged) + } + + @Test + fun `extra trait on a constrained member should remain on it`() { + val transformedModel = loadModel(outputModelOnly) + checkShapeHasTrait( + transformedModel, + outputModelOnly, + "constrainedMemberShape.synthetic#OperationUsingGetOutput\$constrainedPatternString", + "constrainedMemberShape#OperationUsingGetOutput\$constrainedPatternString", + DataTrait("content", SourceLocation.NONE), + ) + } + + @Test + fun `required remains on constrained member shape`() { + val transformedModel = loadModel(outputModelOnly) + checkShapeHasTrait( + transformedModel, + outputModelOnly, + "constrainedMemberShape.synthetic#OperationUsingGetOutput\$requiredConstrainedString", + "constrainedMemberShape#OperationUsingGetOutput\$requiredConstrainedString", + RequiredTrait(), + ) + } + + private fun runServerCodeGen(model: Model, dirToUse: File? = null, writable: Writable): Path { + val runtimeConfig = + RuntimeConfig(runtimeCrateLocation = RuntimeCrateLocation.Path(File("../rust-runtime").absolutePath)) + + val (context, dir) = generatePluginContext( + model, + runtimeConfig = runtimeConfig, + overrideTestDir = dirToUse, + ) + val codegenDecorator = + CombinedServerCodegenDecorator.fromClasspath( + context, + ServerRequiredCustomizations(), + SmithyValidationExceptionDecorator(), + CustomValidationExceptionWithReasonDecorator(), + ) + + ServerCodegenVisitor(context, codegenDecorator) + .execute() + + val codegenContext = serverTestCodegenContext(model) + val settings = ServerRustSettings.from(context.model, context.settings) + val rustCrate = RustCrate( + context.fileManifest, + codegenContext.symbolProvider, + settings.codegenConfig, + ) + + // We cannot write to the lib anymore as the RustWriter overwrites it, so writing code directly to check.rs + // and then adding a `mod check;` to the lib.rs + rustCrate.withModule(RustModule.public("check")) { + writable(this) + File("$dir/src/check.rs").writeText(toString()) + } + + val lib = File("$dir/src/lib.rs") + val libContents = lib.readText() + "\nmod check;" + lib.writeText(libContents) + + return dir + } + + @Test + fun `generate code and check member constrained shapes are in the right modules`() { + val dir = runServerCodeGen(outputModelOnly) { + fun RustWriter.testTypeExistsInBuilderModule(typeName: String) { + unitTest( + "builder_module_has_${typeName.toSnakeCase()}", + """ + #[allow(unused_imports)] use crate::output::operation_using_get_output::$typeName; + """, + ) + } + + // All directly constrained members of the output structure should be in the builder module + setOf( + "ConstrainedLong", + "ConstrainedByte", + "ConstrainedShort", + "ConstrainedInteger", + "ConstrainedString", + "RequiredConstrainedString", + "RequiredConstrainedLong", + "RequiredConstrainedByte", + "RequiredConstrainedInteger", + "RequiredConstrainedShort", + "ConstrainedPatternString", + ).forEach(::testTypeExistsInBuilderModule) + + fun Set.generateUseStatements(prefix: String) = + this.joinToString(separator = "\n") { + "#[allow(unused_imports)] use $prefix::$it;" + } + + unitTest( + "map_overridden_enum", + setOf( + "Value", + "value::ConstraintViolation as ValueCV", + "Key", + "key::ConstraintViolation as KeyCV", + ).generateUseStatements("crate::model::pattern_map_override"), + ) + + unitTest( + "union_overridden_enum", + setOf( + "First", + "first::ConstraintViolation as FirstCV", + "Second", + "second::ConstraintViolation as SecondCV", + ).generateUseStatements("crate::model::pattern_union_override"), + ) + + unitTest( + "list_overridden_enum", + setOf( + "Member", + "member::ConstraintViolation as MemberCV", + ).generateUseStatements("crate::model::pattern_string_list_override"), + ) + } + + val env = mapOf("RUSTFLAGS" to "-A dead_code") + "cargo test".runCommand(dir, env) + } + + /** + * Checks that the given member shape: + * 1. Has been changed to a new shape + * 2. New shape has the same type as the original shape's target e.g. float Centigrade, + * float newType + */ + private fun checkMemberShapeChanged( + model: Model, + baseModel: Model, + member: String, + orgModelMember: String, + lazyMessage: () -> Any = ::defaultError, + ) { + val memberId = ShapeId.from(member) + assert(model.getShape(memberId).isPresent) { lazyMessage } + + val memberShape = model.expectShape(memberId).asMemberShape().get() + val memberTargetShape = model.expectShape(memberShape.target) + val orgMemberId = ShapeId.from(orgModelMember) + assert(baseModel.getShape(orgMemberId).isPresent) { lazyMessage } + + val beforeTransformMemberShape = baseModel.expectShape(orgMemberId).asMemberShape().get() + val originalTargetShape = model.expectShape(beforeTransformMemberShape.target) + + val extractableConstraintTraits = allConstraintTraits - RequiredTrait::class.java + + // New member shape should not have the overridden constraints on it + assert(!extractableConstraintTraits.any(memberShape::hasTrait)) { lazyMessage } + + // Target shape has to be changed to a new shape + memberTargetShape.id.name shouldNotBe beforeTransformMemberShape.target.name + + // Target shape's name should match the expected name + val expectedName = memberShape.container.name.substringAfter('#') + + memberShape.memberName.substringBefore('#').toPascalCase() + + memberTargetShape.id.name shouldBe expectedName + + // New shape should have all the constraint traits that were on the member shape, + // and it should also have the traits that the target type contains. + val beforeTransformConstraintTraits = + beforeTransformMemberShape.allTraits.values.filter { allConstraintTraits.contains(it.javaClass) }.toSet() + val newShapeConstrainedTraits = + memberTargetShape.allTraits.values.filter { allConstraintTraits.contains(it.javaClass) }.toSet() + + val leftOutConstraintTrait = beforeTransformConstraintTraits - newShapeConstrainedTraits + assert( + leftOutConstraintTrait.isEmpty() || leftOutConstraintTrait.all { + it.toShapeId() == RequiredTrait.ID + }, + ) { lazyMessage } + + // In case the target shape has some more constraints, which the member shape did not override, + // then those still need to apply on the new standalone shape that has been defined. + val leftOverTraits = originalTargetShape.allTraits.values + .filter { beforeOverridingTrait -> beforeTransformConstraintTraits.none { beforeOverridingTrait.toShapeId() == it.toShapeId() } } + val allNewShapeTraits = memberTargetShape.allTraits.values.toList() + assert((leftOverTraits + newShapeConstrainedTraits).all { it in allNewShapeTraits }) { lazyMessage } + } + + private fun defaultError() = "test failed" + + /** + * Checks that the given shape has not changed in the transformed model and is exactly + * the same as the original model + */ + private fun checkMemberShapeIsSame( + model: Model, + baseModel: Model, + member: String, + orgModelMember: String, + lazyMessage: () -> Any = ::defaultError, + ) { + val memberId = ShapeId.from(member) + assert(model.getShape(memberId).isPresent) { lazyMessage } + + val memberShape = model.expectShape(memberId).asMemberShape().get() + val memberTargetShape = model.expectShape(memberShape.target) + val originalShape = baseModel.expectShape(ShapeId.from(orgModelMember)).asMemberShape().get() + + // Member shape should not have any constraints on it + assert(!memberShape.hasConstraintTrait()) { lazyMessage } + // Target shape has to be same as the original shape + memberTargetShape.id shouldBe originalShape.target + } + + private fun checkShapeHasTrait( + model: Model, + orgModel: Model, + member: String, + orgModelMember: String, + trait: Trait, + ) { + val memberId = ShapeId.from(member) + val memberShape = model.expectShape(memberId).asMemberShape().get() + val orgMemberShape = orgModel.expectShape(ShapeId.from(orgModelMember)).asMemberShape().get() + + val newMemberTrait = memberShape.expectTrait(trait::class.java) + val oldMemberTrait = orgMemberShape.expectTrait(trait::class.java) + + newMemberTrait shouldBe oldMemberTrait + } +} diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriterTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriterTest.kt new file mode 100644 index 0000000000..3637548d9a --- /dev/null +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriterTest.kt @@ -0,0 +1,271 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.server.smithy + +import io.kotest.matchers.collections.shouldContain +import org.junit.jupiter.api.Test +import software.amazon.smithy.model.Model +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.rustlang.Visibility +import software.amazon.smithy.rust.codegen.core.rustlang.comment +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.smithy.RuntimeConfig +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.TestWorkspace +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.generatePluginContext +import software.amazon.smithy.rust.codegen.core.testutil.unitTest +import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestSymbolProvider +import java.io.File + +class RustCrateInlineModuleComposingWriterTest { + private val rustCrate: RustCrate + private val codegenContext: ServerCodegenContext + private val model: Model = """ + ${'$'}version: "2.0" + namespace test + + use aws.api#data + use aws.protocols#restJson1 + + @title("Weather Service") + @restJson1 + service WeatherService { + operations: [MalformedPatternOverride] + } + + @suppress(["UnstableTrait"]) + @http(uri: "/MalformedPatternOverride", method: "GET") + operation MalformedPatternOverride { + output: MalformedPatternOverrideInput, + errors: [] + } + + structure MalformedPatternOverrideInput { + @pattern("^[g-m]+${'$'}") + string: PatternString, + } + + @pattern("^[a-m]+${'$'}") + string PatternString + """.trimIndent().asSmithyModel() + + init { + codegenContext = serverTestCodegenContext(model) + val runtimeConfig = + RuntimeConfig(runtimeCrateLocation = RuntimeCrateLocation.Path(File("../rust-runtime").absolutePath)) + + val (context, _) = generatePluginContext( + model, + runtimeConfig = runtimeConfig, + ) + val settings = ServerRustSettings.from(context.model, context.settings) + rustCrate = RustCrate(context.fileManifest, codegenContext.symbolProvider, settings.codegenConfig) + } + + private fun createTestInlineModule(parentModule: RustModule, moduleName: String, documentation: String? = null): RustModule.LeafModule = + RustModule.new( + moduleName, + visibility = Visibility.PUBLIC, + documentation = documentation ?: moduleName, + parent = parentModule, + inline = true, + ) + + private fun createTestOrphanInlineModule(moduleName: String): RustModule.LeafModule = + RustModule.new( + moduleName, + visibility = Visibility.PUBLIC, + documentation = moduleName, + parent = RustModule.LibRs, + inline = true, + ) + + private fun helloWorld(writer: RustWriter, moduleName: String) { + writer.rustBlock("pub fn hello_world()") { + writer.comment("Module $moduleName") + } + } + + private fun byeWorld(writer: RustWriter, moduleName: String) { + writer.rustBlock("pub fn bye_world()") { + writer.comment("Module $moduleName") + writer.rust("""println!("from inside $moduleName");""") + } + } + + @Test + fun `calling withModule multiple times returns same object on rustModule`() { + val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) + val writers: MutableSet = mutableSetOf() + testProject.withModule(ServerRustModule.Model) { + writers.add(this) + } + testProject.withModule(ServerRustModule.Model) { + writers shouldContain this + } + } + + @Test + fun `simple inline module works`() { + val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) + val moduleA = createTestInlineModule(ServerRustModule.Model, "a") + testProject.withModule(ServerRustModule.Model) { + testProject.getInlineModuleWriter().withInlineModule(this, moduleA) { + helloWorld(this, "a") + } + } + + testProject.getInlineModuleWriter().render() + testProject.withModule(ServerRustModule.Model) { + this.unitTest("test_a") { + rust("crate::model::a::hello_world();") + } + } + testProject.compileAndTest() + } + + @Test + fun `creating nested modules works from different rustWriters`() { + // Define the following functions in different inline modules. + // crate::model::a::hello_world(); + // crate::model::a::bye_world(); + // crate::model::b::hello_world(); + // crate::model::b::bye_world(); + // crate::model::b::c::hello_world(); + // crate::model::b::c::bye_world(); + // crate::input::e::hello_world(); + // crate::output::f::hello_world(); + // crate::output::f::g::hello_world(); + // crate::output::h::hello_world(); + // crate::output::h::i::hello_world(); + + val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) + val modules = hashMapOf( + "a" to createTestOrphanInlineModule("a"), + "d" to createTestOrphanInlineModule("d"), + "e" to createTestOrphanInlineModule("e"), + "i" to createTestOrphanInlineModule("i"), + ) + + modules["b"] = createTestInlineModule(ServerRustModule.Model, "b") + modules["c"] = createTestInlineModule(modules["b"]!!, "c") + modules["f"] = createTestInlineModule(ServerRustModule.Output, "f") + modules["g"] = createTestInlineModule(modules["f"]!!, "g") + modules["h"] = createTestInlineModule(ServerRustModule.Output, "h") + // A different kotlin object but would still go in the right place + + testProject.withModule(ServerRustModule.Model) { + testProject.getInlineModuleWriter().withInlineModule(this, modules["a"]!!) { + helloWorld(this, "a") + } + testProject.getInlineModuleWriter().withInlineModule(this, modules["b"]!!) { + helloWorld(this, "b") + testProject.getInlineModuleWriter().withInlineModule(this, modules["c"]!!) { + byeWorld(this, "b::c") + } + } + // Writing to the same module crate::model::a second time should work. + testProject.getInlineModuleWriter().withInlineModule(this, modules["a"]!!) { + byeWorld(this, "a") + } + // Writing to model::b, when model::b and model::b::c have already been written to + // should work. + testProject.getInlineModuleWriter().withInlineModule(this, modules["b"]!!) { + byeWorld(this, "b") + } + } + + // Write directly to an inline module without specifying the immediate parent. crate::model::b::c + // should have a `hello_world` fn in it now. + testProject.withModule(ServerRustModule.Model) { + testProject.getInlineModuleWriter().withInlineModuleHierarchy(this, modules["c"]!!) { + helloWorld(this, "c") + } + } + // Write to a different top level module to confirm that works. + testProject.withModule(ServerRustModule.Input) { + testProject.getInlineModuleWriter().withInlineModuleHierarchy(this, modules["e"]!!) { + helloWorld(this, "e") + } + } + + // Create a descendent inner module crate::output::f::g and then try writing to the intermediate inner module + // that did not exist before the descendent was dded. + testProject.getInlineModuleWriter().withInlineModuleHierarchyUsingCrate(testProject, modules["f"]!!) { + testProject.getInlineModuleWriter().withInlineModuleHierarchyUsingCrate(testProject, modules["g"]!!) { + helloWorld(this, "g") + } + } + + testProject.getInlineModuleWriter().withInlineModuleHierarchyUsingCrate(testProject, modules["f"]!!) { + helloWorld(this, "f") + } + + // It should work even if the inner descendent module was added using `withInlineModuleHierarchy` and then + // code is added to the intermediate module using `withInlineModuleHierarchyUsingCrate` + testProject.withModule(ServerRustModule.Output) { + testProject.getInlineModuleWriter().withInlineModuleHierarchy(this, modules["h"]!!) { + testProject.getInlineModuleWriter().withInlineModuleHierarchy(this, modules["i"]!!) { + helloWorld(this, "i") + } + testProject.withModule(ServerRustModule.Model) { + // While writing to output::h::i, it should be able to a completely different module + testProject.getInlineModuleWriter().withInlineModuleHierarchy(this, modules["b"]!!) { + rustBlock("pub fn some_other_writer_wrote_this()") { + rust("""println!("from inside crate::model::b::some_other_writer_wrote_this");""") + } + } + } + } + } + testProject.getInlineModuleWriter().withInlineModuleHierarchyUsingCrate(testProject, modules["h"]!!) { + helloWorld(this, "h") + } + + // Render all of the code. + testProject.getInlineModuleWriter().render() + + testProject.withModule(ServerRustModule.Model) { + this.unitTest("test_a") { + rust("crate::model::a::hello_world();") + rust("crate::model::a::bye_world();") + } + this.unitTest("test_b") { + rust("crate::model::b::hello_world();") + rust("crate::model::b::bye_world();") + } + this.unitTest("test_someother_writer_wrote") { + rust("crate::model::b::some_other_writer_wrote_this();") + } + this.unitTest("test_b_c") { + rust("crate::model::b::c::hello_world();") + rust("crate::model::b::c::bye_world();") + } + this.unitTest("test_e") { + rust("crate::input::e::hello_world();") + } + this.unitTest("test_f") { + rust("crate::output::f::hello_world();") + } + this.unitTest("test_g") { + rust("crate::output::f::g::hello_world();") + } + this.unitTest("test_h") { + rust("crate::output::h::hello_world();") + } + this.unitTest("test_h_i") { + rust("crate::output::h::i::hello_world();") + } + } + testProject.compileAndTest() + } +} 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 9a7ba2bf95..bb19a86deb 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 @@ -75,39 +75,6 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { """.trimIndent() } - @Test - fun `it should detect when unsupported constraint traits on member shapes are used`() { - val model = - """ - $baseModel - - structure TestInputOutput { - @length(min: 1, max: 69) - lengthString: String - } - """.asSmithyModel() - val validationResult = validateModel(model) - - validationResult.messages shouldHaveSize 1 - validationResult.messages[0].message shouldContain "The member shape `test#TestInputOutput\$lengthString` has the constraint trait `smithy.api#length` attached" - } - - @Test - fun `it should not detect when the required trait on a member shape is used`() { - val model = - """ - $baseModel - - structure TestInputOutput { - @required - string: String - } - """.asSmithyModel() - val validationResult = validateModel(model) - - validationResult.messages shouldHaveSize 0 - } - private val constraintTraitOnStreamingBlobShapeModel = """ $baseModel 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 0cabbac74d..060e0166a4 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.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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator 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 @@ -69,8 +70,10 @@ class ConstrainedBlobGeneratorTest { project.withModule(ServerRustModule.Model) { addDependency(RuntimeType.blob(codegenContext.runtimeConfig).toSymbol()) + ConstrainedBlobGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, constrainedBlobShape, SmithyValidationExceptionConversionGenerator(codegenContext), @@ -129,6 +132,7 @@ class ConstrainedBlobGeneratorTest { ConstrainedBlobGenerator( codegenContext, + writer.createTestInlineModuleCreator(), writer, constrainedBlobShape, SmithyValidationExceptionConversionGenerator(codegenContext), 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 5d0e19cbc0..cce7da4aa6 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.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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator 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 @@ -287,7 +288,7 @@ class ConstrainedCollectionGeneratorTest { ConstrainedCollectionGenerator(codegenContext, writer, constrainedCollectionShape, constraintsInfo).render() CollectionConstraintViolationGenerator( codegenContext, - writer, + writer.createTestInlineModuleCreator(), constrainedCollectionShape, constraintsInfo, SmithyValidationExceptionConversionGenerator(codegenContext), 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 f9c84b7db8..0eebb7e36b 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.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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator 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 @@ -156,7 +157,7 @@ class ConstrainedMapGeneratorTest { ConstrainedMapGenerator(codegenContext, writer, constrainedMapShape).render() MapConstraintViolationGenerator( codegenContext, - writer, + writer.createTestInlineModuleCreator(), 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 0f26bdaaee..5a78574c93 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.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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator 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 @@ -74,6 +75,7 @@ class ConstrainedNumberGeneratorTest { project.withModule(ServerRustModule.Model) { ConstrainedNumberGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, shape, SmithyValidationExceptionConversionGenerator(codegenContext), @@ -140,6 +142,7 @@ class ConstrainedNumberGeneratorTest { val writer = RustWriter.forModule(ServerRustModule.Model.name) ConstrainedNumberGenerator( codegenContext, + writer.createTestInlineModuleCreator(), writer, constrainedShape, SmithyValidationExceptionConversionGenerator(codegenContext), 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 6c8ddd8d67..62a7d061c3 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.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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator 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 @@ -85,6 +86,7 @@ class ConstrainedStringGeneratorTest { project.withModule(ServerRustModule.Model) { ConstrainedStringGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, constrainedStringShape, SmithyValidationExceptionConversionGenerator(codegenContext), @@ -144,6 +146,7 @@ class ConstrainedStringGeneratorTest { ConstrainedStringGenerator( codegenContext, + writer.createTestInlineModuleCreator(), writer, constrainedStringShape, SmithyValidationExceptionConversionGenerator(codegenContext), @@ -176,12 +179,14 @@ class ConstrainedStringGeneratorTest { val validationExceptionConversionGenerator = SmithyValidationExceptionConversionGenerator(codegenContext) ConstrainedStringGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, constrainedStringShape, validationExceptionConversionGenerator, ).render() ConstrainedStringGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, sensitiveConstrainedStringShape, validationExceptionConversionGenerator, @@ -225,6 +230,7 @@ class ConstrainedStringGeneratorTest { project.withModule(ServerRustModule.Model) { ConstrainedStringGenerator( codegenContext, + this.createTestInlineModuleCreator(), this, constrainedStringShape, SmithyValidationExceptionConversionGenerator(codegenContext), 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 ef29d6a6e8..83e6e7bd27 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 @@ -19,6 +19,7 @@ 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.rustlang.withBlock 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.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock @@ -32,6 +33,7 @@ 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.renderInlineMemoryModules 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 @@ -105,10 +107,10 @@ class ServerBuilderDefaultValuesTest { project.withModule(RustModule.public("model")) { when (builderGeneratorKind) { BuilderGeneratorKind.SERVER_BUILDER_GENERATOR -> { - writeServerBuilderGenerator(this, model, symbolProvider) + writeServerBuilderGenerator(project, this, model, symbolProvider) } BuilderGeneratorKind.SERVER_BUILDER_GENERATOR_WITHOUT_PUBLIC_CONSTRAINED_TYPES -> { - writeServerBuilderGeneratorWithoutPublicConstrainedTypes(this, model, symbolProvider) + writeServerBuilderGeneratorWithoutPublicConstrainedTypes(project, this, model, symbolProvider) } } @@ -144,6 +146,7 @@ class ServerBuilderDefaultValuesTest { ) } + project.renderInlineMemoryModules() // Run clippy because the builder's code for handling `@default` is prone to upset it. project.compileAndTest(runClippy = true) } @@ -168,7 +171,7 @@ class ServerBuilderDefaultValuesTest { .map { it.key to "${it.value}.into()" } } - private fun writeServerBuilderGeneratorWithoutPublicConstrainedTypes(writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { + private fun writeServerBuilderGeneratorWithoutPublicConstrainedTypes(rustCrate: RustCrate, writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { val struct = model.lookup("com.test#MyStruct") val codegenContext = serverTestCodegenContext( model, @@ -181,7 +184,7 @@ class ServerBuilderDefaultValuesTest { writer.implBlock(struct, symbolProvider) { builderGenerator.renderConvenienceMethod(writer) } - builderGenerator.render(writer) + builderGenerator.render(rustCrate, writer) ServerEnumGenerator( codegenContext, @@ -191,7 +194,7 @@ class ServerBuilderDefaultValuesTest { StructureGenerator(model, symbolProvider, writer, struct).render() } - private fun writeServerBuilderGenerator(writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { + private fun writeServerBuilderGenerator(rustCrate: RustCrate, writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { val struct = model.lookup("com.test#MyStruct") val codegenContext = serverTestCodegenContext(model) val builderGenerator = ServerBuilderGenerator(codegenContext, struct, SmithyValidationExceptionConversionGenerator(codegenContext)) @@ -199,7 +202,7 @@ class ServerBuilderDefaultValuesTest { writer.implBlock(struct, symbolProvider) { builderGenerator.renderConvenienceMethod(writer) } - builderGenerator.render(writer) + builderGenerator.render(rustCrate, writer) ServerEnumGenerator( codegenContext, 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 515bbb58f8..d26e6b35db 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 @@ -7,14 +7,17 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import org.junit.jupiter.api.Test import software.amazon.smithy.model.shapes.StructureShape -import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock +import software.amazon.smithy.rust.codegen.core.rustlang.rust 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.TestWorkspace 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.ServerRustModule import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext class ServerBuilderGeneratorTest { @@ -36,23 +39,40 @@ class ServerBuilderGeneratorTest { """.asSmithyModel() val codegenContext = serverTestCodegenContext(model) - 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, SmithyValidationExceptionConversionGenerator(codegenContext)) - builderGenerator.render(writer) - writer.implBlock(shape, codegenContext.symbolProvider) { - builderGenerator.renderConvenienceMethod(this) + val project = TestWorkspace.testProject() + project.withModule(ServerRustModule.Model) { + val writer = this + val shape = model.lookup("test#Credentials") + + StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList()).render() + val builderGenerator = ServerBuilderGenerator( + codegenContext, + shape, + SmithyValidationExceptionConversionGenerator(codegenContext), + ) + + builderGenerator.render(project, writer) + + writer.implBlock(codegenContext.symbolProvider.toSymbol(shape)) { + builderGenerator.renderConvenienceMethod(this) + } + + project.renderInlineMemoryModules() + } + + project.unitTest { + rust( + """ + use super::*; + use crate::model::*; + let builder = Credentials::builder() + .username(Some("admin".to_owned())) + .password(Some("pswd".to_owned())) + .secret_key(Some("12345".to_owned())); + assert_eq!(format!("{:?}", builder), "Builder { username: Some(\"admin\"), password: \"*** Sensitive Data Redacted ***\", secret_key: \"*** Sensitive Data Redacted ***\" }"); + """, + ) } - writer.compileAndTest( - """ - use super::*; - let builder = Credentials::builder() - .username(Some("admin".to_owned())) - .password(Some("pswd".to_owned())) - .secret_key(Some("12345".to_owned())); - assert_eq!(format!("{:?}", builder), "Builder { username: Some(\"admin\"), password: \"*** Sensitive Data Redacted ***\", secret_key: \"*** Sensitive Data Redacted ***\" }"); - """, - ) + project.compileAndTest() } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt index c7f2b2e5ff..eef80be1c4 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt @@ -23,6 +23,7 @@ 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.ServerRustModule import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -138,10 +139,11 @@ class ServerInstantiatorTest { val data = Node.parse("{}") val project = TestWorkspace.testProject() + project.withModule(ServerRustModule.Model) { - structure.serverRenderWithModelBuilder(model, symbolProvider, this) - inner.serverRenderWithModelBuilder(model, symbolProvider, this) - nestedStruct.serverRenderWithModelBuilder(model, symbolProvider, this) + structure.serverRenderWithModelBuilder(project, model, symbolProvider, this) + inner.serverRenderWithModelBuilder(project, model, symbolProvider, this) + nestedStruct.serverRenderWithModelBuilder(project, model, symbolProvider, this) UnionGenerator(model, symbolProvider, this, union).render() withInlineModule(RustModule.inlineTests()) { @@ -180,6 +182,7 @@ class ServerInstantiatorTest { } } } + project.renderInlineMemoryModules() project.compileAndTest() } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGeneratorTest.kt index 31acf95b47..5851344d96 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGeneratorTest.kt @@ -14,6 +14,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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestSymbolProvider @@ -53,7 +54,7 @@ class ServerOperationErrorGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ServerRustModule.Error) { listOf("FooException", "ComplexError", "InvalidGreeting", "Deprecated").forEach { - model.lookup("error#$it").serverRenderWithModelBuilder(model, symbolProvider, this) + model.lookup("error#$it").serverRenderWithModelBuilder(project, model, symbolProvider, this) } ServerOperationErrorGenerator( model, @@ -94,7 +95,7 @@ class ServerOperationErrorGeneratorTest { let error: GreetingError = variant.into(); """, ) - + project.renderInlineMemoryModules() project.compileAndTest() } } 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 64117fd0b7..28a8c1ef4c 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 @@ -14,7 +14,9 @@ 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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -50,12 +52,16 @@ class UnconstrainedCollectionGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ServerRustModule.Model) { - model.lookup("test#StructureC").serverRenderWithModelBuilder(model, symbolProvider, this) + model.lookup("test#StructureC").serverRenderWithModelBuilder(project, model, symbolProvider, this) } project.withModule(ServerRustModule.ConstrainedModule) { listOf(listA, listB).forEach { - PubCrateConstrainedCollectionGenerator(codegenContext, this, it).render() + PubCrateConstrainedCollectionGenerator( + codegenContext, + this.createTestInlineModuleCreator(), + it, + ).render() } } project.withModule(ServerRustModule.UnconstrainedModule) unconstrainedModuleWriter@{ @@ -63,13 +69,13 @@ class UnconstrainedCollectionGeneratorTest { listOf(listA, listB).forEach { UnconstrainedCollectionGenerator( codegenContext, - this@unconstrainedModuleWriter, + this@unconstrainedModuleWriter.createTestInlineModuleCreator(), it, ).render() CollectionConstraintViolationGenerator( codegenContext, - this@modelsModuleWriter, + this@modelsModuleWriter.createTestInlineModuleCreator(), it, CollectionTraitInfo.fromShape(it, codegenContext.constrainedShapeSymbolProvider), SmithyValidationExceptionConversionGenerator(codegenContext), @@ -126,6 +132,7 @@ class UnconstrainedCollectionGeneratorTest { ) } } + project.renderInlineMemoryModules() project.compileAndTest() } } 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 b091d7a38d..c60d10d09f 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 @@ -15,7 +15,9 @@ 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.ServerRustModule import software.amazon.smithy.rust.codegen.server.smithy.ServerRustModule.Model +import software.amazon.smithy.rust.codegen.server.smithy.createTestInlineModuleCreator import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -53,22 +55,29 @@ class UnconstrainedMapGeneratorTest { val project = TestWorkspace.testProject(symbolProvider, debugMode = true) project.withModule(Model) { - model.lookup("test#StructureC").serverRenderWithModelBuilder(model, symbolProvider, this) + model.lookup("test#StructureC").serverRenderWithModelBuilder(project, model, symbolProvider, this) } project.withModule(ServerRustModule.ConstrainedModule) { listOf(mapA, mapB).forEach { - PubCrateConstrainedMapGenerator(codegenContext, this, it).render() + PubCrateConstrainedMapGenerator( + codegenContext, + this.createTestInlineModuleCreator(), + it, + ).render() } } project.withModule(ServerRustModule.UnconstrainedModule) unconstrainedModuleWriter@{ project.withModule(Model) modelsModuleWriter@{ listOf(mapA, mapB).forEach { - UnconstrainedMapGenerator(codegenContext, this@unconstrainedModuleWriter, it).render() + UnconstrainedMapGenerator( + codegenContext, + this@unconstrainedModuleWriter.createTestInlineModuleCreator(), it, + ).render() MapConstraintViolationGenerator( codegenContext, - this@modelsModuleWriter, + this@modelsModuleWriter.createTestInlineModuleCreator(), it, SmithyValidationExceptionConversionGenerator(codegenContext), ).render() @@ -164,7 +173,7 @@ class UnconstrainedMapGeneratorTest { ) } } - + project.renderInlineMemoryModules() project.compileAndTest() } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGeneratorTest.kt index 2b745e270b..bf1904757e 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGeneratorTest.kt @@ -15,6 +15,8 @@ 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.ServerRustModule +import software.amazon.smithy.rust.codegen.server.smithy.createInlineModuleCreator +import software.amazon.smithy.rust.codegen.server.smithy.renderInlineMemoryModules import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverRenderWithModelBuilder import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -42,15 +44,16 @@ class UnconstrainedUnionGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ServerRustModule.Model) { - model.lookup("test#Structure").serverRenderWithModelBuilder(model, symbolProvider, this) + model.lookup("test#Structure").serverRenderWithModelBuilder(project, model, symbolProvider, this) } project.withModule(ServerRustModule.Model) { UnionGenerator(model, symbolProvider, this, unionShape, renderUnknownVariant = false).render() } + project.withModule(ServerRustModule.UnconstrainedModule) unconstrainedModuleWriter@{ project.withModule(ServerRustModule.Model) modelsModuleWriter@{ - UnconstrainedUnionGenerator(codegenContext, this@unconstrainedModuleWriter, this@modelsModuleWriter, unionShape).render() + UnconstrainedUnionGenerator(codegenContext, project.createInlineModuleCreator(), this@modelsModuleWriter, unionShape).render() this@unconstrainedModuleWriter.unitTest( name = "unconstrained_union_fail_to_constrain", @@ -96,6 +99,7 @@ class UnconstrainedUnionGeneratorTest { ) } } + project.renderInlineMemoryModules() project.compileAndTest() } } 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 d9fca2df9f..d7abf4d363 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 @@ -15,6 +15,7 @@ import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels @@ -65,6 +66,7 @@ abstract class ServerEventStreamBaseRequirements : EventStreamTestRequirements