Skip to content

Commit 090d3e8

Browse files
drganjooFahad Zubair
authored andcommitted
Constraint member types are refactored as standalone shapes. (#2256)
* Constraint member types are refactored as standalone shapes. * ModelModule to ServerRustModule.model * Constraints are written to the correct module * Code generates for non-public constrained types. * Removed a comment * Using ConcurrentHashmap just to be on the safe side * Clippy warnings removed on constraints, k.into() if gated * Wordings for some of the checks changed * Test need to call rustCrate.renderInlineMemoryModules * ktlintFormat related changes * RustCrate need to be passed for server builder * Param renamed in getParentAndInlineModuleForConstrainedMember * pubCrate to publicConstrainedType rename * PythonServer symbol builder needed to pass publicConstrainedTypes * @required still remains on the member shape after transformation * ConcurrentLinkedQueue used for root RustWriters * runTestCase does not run the tests but just sets them up, hence has been renamed * CHANGELOG added --------- Co-authored-by: Fahad Zubair <[email protected]>
1 parent cec8741 commit 090d3e8

File tree

57 files changed

+1901
-315
lines changed

Some content is hidden

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

57 files changed

+1901
-315
lines changed

CHANGELOG.next.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ produced Rust code that did not compile"""
1717
references = ["smithy-rs#2352", "smithy-rs#2343"]
1818
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "server"}
1919
author = "82marbag"
20+
21+
[[smithy-rs]]
22+
message = "Support for constraint traits on member shapes (constraint trait precedence) has been added."
23+
references = ["smithy-rs#1969"]
24+
meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "server" }
25+
author = "drganjoo"

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.client.testutil.clientTestRustSetting
1919
import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
2020
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
2121
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
22+
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
2223
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
2324
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
2425
import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator
@@ -48,6 +49,7 @@ abstract class ClientEventStreamBaseRequirements : EventStreamTestRequirements<C
4849
)
4950

5051
override fun renderBuilderForShape(
52+
rustCrate: RustCrate,
5153
writer: RustWriter,
5254
codegenContext: ClientCodegenContext,
5355
shape: StructureShape,
@@ -66,6 +68,23 @@ abstract class ClientEventStreamBaseRequirements : EventStreamTestRequirements<C
6668
symbolProvider: RustSymbolProvider,
6769
operationOrEventStream: Shape,
6870
) {
69-
OperationErrorGenerator(model, symbolProvider, operationOrEventStream).render(writer)
71+
OperationErrorGenerator(model, symbolProvider, operationOrEventStream, emptyList()).render(writer)
72+
}
73+
74+
override fun renderError(
75+
rustCrate: RustCrate,
76+
writer: RustWriter,
77+
codegenContext: ClientCodegenContext,
78+
shape: StructureShape,
79+
) {
80+
val errorTrait = shape.expectTrait<ErrorTrait>()
81+
ErrorGenerator(
82+
codegenContext.model,
83+
codegenContext.symbolProvider,
84+
writer,
85+
shape,
86+
errorTrait,
87+
emptyList(),
88+
).render()
7089
}
7190
}

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools
1717
import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety
1818
import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject
1919
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
20+
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
2021

2122
class ClientEventStreamMarshallerGeneratorTest {
2223
@ParameterizedTest
2324
@ArgumentsSource(TestCasesProvider::class)
2425
fun test(testCase: EventStreamTestModels.TestCase) {
25-
EventStreamTestTools.runTestCase(
26+
EventStreamTestTools.setupTestCase(
2627
testCase,
2728
object : ClientEventStreamBaseRequirements() {
2829
override fun renderGenerator(
@@ -41,6 +42,6 @@ class ClientEventStreamMarshallerGeneratorTest {
4142
},
4243
CodegenTarget.CLIENT,
4344
EventStreamTestVariety.Marshall,
44-
)
45+
).compileAndTest()
4546
}
4647
}

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels
1919
import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools
2020
import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety
2121
import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject
22+
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
2223

2324
class ClientEventStreamUnmarshallerGeneratorTest {
2425
@ParameterizedTest
2526
@ArgumentsSource(TestCasesProvider::class)
2627
fun test(testCase: EventStreamTestModels.TestCase) {
27-
EventStreamTestTools.runTestCase(
28+
EventStreamTestTools.setupTestCase(
2829
testCase,
2930
object : ClientEventStreamBaseRequirements() {
3031
override fun renderGenerator(
@@ -44,6 +45,6 @@ class ClientEventStreamUnmarshallerGeneratorTest {
4445
},
4546
CodegenTarget.CLIENT,
4647
EventStreamTestVariety.Unmarshall,
47-
)
48+
).compileAndTest()
4849
}
4950
}

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
1818
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
1919
import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker
2020
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
21+
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
2122
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
2223
import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator
2324
import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator
@@ -63,6 +64,7 @@ interface EventStreamTestRequirements<C : CodegenContext> {
6364

6465
/** Render a builder for the given shape */
6566
fun renderBuilderForShape(
67+
rustCrate: RustCrate,
6668
writer: RustWriter,
6769
codegenContext: C,
6870
shape: StructureShape,
@@ -75,16 +77,23 @@ interface EventStreamTestRequirements<C : CodegenContext> {
7577
symbolProvider: RustSymbolProvider,
7678
operationOrEventStream: Shape,
7779
)
80+
81+
/** Render an error struct and builder */
82+
fun renderError(rustCrate: RustCrate, writer: RustWriter, codegenContext: C, shape: StructureShape)
7883
}
7984

8085
object EventStreamTestTools {
81-
fun <C : CodegenContext> runTestCase(
86+
fun <C : CodegenContext> setupTestCase(
8287
testCase: EventStreamTestModels.TestCase,
8388
requirements: EventStreamTestRequirements<C>,
8489
codegenTarget: CodegenTarget,
8590
variety: EventStreamTestVariety,
86-
) {
87-
val model = EventStreamNormalizer.transform(OperationNormalizer.transform(testCase.model))
91+
transformers: List<(Model) -> Model> = listOf(),
92+
): TestWriterDelegator {
93+
val model = (listOf(OperationNormalizer::transform, EventStreamNormalizer::transform) + transformers).fold(testCase.model) { model, transformer ->
94+
transformer(model)
95+
}
96+
8897
val serviceShape = model.expectShape(ShapeId.from("test#TestService")) as ServiceShape
8998
val codegenContext = requirements.createCodegenContext(
9099
model,
@@ -102,7 +111,8 @@ object EventStreamTestTools {
102111
EventStreamTestVariety.Unmarshall -> writeUnmarshallTestCases(testCase, codegenTarget, generator)
103112
}
104113
}
105-
test.project.compileAndTest()
114+
115+
return test.project
106116
}
107117

108118
private fun <C : CodegenContext> generateTestProject(
@@ -115,7 +125,6 @@ object EventStreamTestTools {
115125
val operationShape = model.expectShape(ShapeId.from("test#TestStreamOp")) as OperationShape
116126
val unionShape = model.expectShape(ShapeId.from("test#TestStream")) as UnionShape
117127
val walker = DirectedWalker(model)
118-
119128
val project = TestWorkspace.testProject(symbolProvider)
120129
val errors = model.serviceShapes
121130
.flatMap { walker.walkShapes(it) }
@@ -126,8 +135,7 @@ object EventStreamTestTools {
126135
requirements.renderOperationError(this, model, symbolProvider, operationShape)
127136
requirements.renderOperationError(this, model, symbolProvider, unionShape)
128137
for (shape in errors) {
129-
StructureGenerator(model, symbolProvider, this, shape).render(codegenTarget)
130-
requirements.renderBuilderForShape(this, codegenContext, shape)
138+
requirements.renderError(project, this, codegenContext, shape)
131139
}
132140
}
133141
val inputOutput = model.lookup<StructureShape>("test#TestStreamInputOutput")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class PythonServerCodegenVisitor(
7575
serviceShape: ServiceShape,
7676
symbolVisitorConfig: SymbolVisitorConfig,
7777
publicConstrainedTypes: Boolean,
78+
includeConstraintShapeProvider: Boolean,
7879
) = RustServerCodegenPythonPlugin.baseSymbolProvider(model, serviceShape, symbolVisitorConfig, publicConstrainedTypes)
7980

8081
val serverSymbolProviders = ServerSymbolProviders.from(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class RustServerCodegenPythonPlugin : SmithyBuildPlugin {
7979
// Generate public constrained types for directly constrained shapes.
8080
// In the Python server project, this is only done to generate constrained types for simple shapes (e.g.
8181
// a `string` shape with the `length` trait), but these always remain `pub(crate)`.
82-
.let { if (constrainedTypes) ConstrainedShapeSymbolProvider(it, model, serviceShape) else it }
82+
.let { if (constrainedTypes) ConstrainedShapeSymbolProvider(it, model, serviceShape, constrainedTypes) else it }
8383
// Generate different types for EventStream shapes (e.g. transcribe streaming)
8484
.let { EventStreamSymbolProvider(symbolVisitorConfig.runtimeConfig, it, model, CodegenTarget.SERVER) }
8585
// Add Rust attributes (like `#[derive(PartialEq)]`) to generated shapes

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstrainedShapeSymbolProvider.kt

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ import software.amazon.smithy.model.shapes.ServiceShape
1919
import software.amazon.smithy.model.shapes.Shape
2020
import software.amazon.smithy.model.shapes.ShortShape
2121
import software.amazon.smithy.model.shapes.StringShape
22+
import software.amazon.smithy.model.shapes.StructureShape
2223
import software.amazon.smithy.model.traits.LengthTrait
24+
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
25+
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
2326
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
27+
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
2428
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
2529
import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider
2630
import software.amazon.smithy.rust.codegen.core.smithy.contextName
@@ -29,9 +33,13 @@ import software.amazon.smithy.rust.codegen.core.smithy.handleRustBoxing
2933
import software.amazon.smithy.rust.codegen.core.smithy.locatedIn
3034
import software.amazon.smithy.rust.codegen.core.smithy.rustType
3135
import software.amazon.smithy.rust.codegen.core.smithy.symbolBuilder
36+
import software.amazon.smithy.rust.codegen.core.util.getTrait
3237
import software.amazon.smithy.rust.codegen.core.util.hasTrait
3338
import software.amazon.smithy.rust.codegen.core.util.orNull
3439
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
40+
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
41+
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderModule
42+
import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait
3543

3644
/**
3745
* The [ConstrainedShapeSymbolProvider] returns, for a given _directly_
@@ -56,14 +64,16 @@ class ConstrainedShapeSymbolProvider(
5664
private val base: RustSymbolProvider,
5765
private val model: Model,
5866
private val serviceShape: ServiceShape,
67+
private val publicConstrainedTypes: Boolean,
5968
) : WrappingSymbolProvider(base) {
6069
private val nullableIndex = NullableIndex.of(model)
6170

6271
private fun publicConstrainedSymbolForMapOrCollectionShape(shape: Shape): Symbol {
6372
check(shape is MapShape || shape is CollectionShape)
6473

65-
val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase())
66-
return symbolBuilder(shape, rustType).locatedIn(ServerRustModule.Model).build()
74+
val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes)
75+
val rustType = RustType.Opaque(name)
76+
return symbolBuilder(shape, rustType).locatedIn(module).build()
6777
}
6878

6979
override fun toSymbol(shape: Shape): Symbol {
@@ -74,8 +84,14 @@ class ConstrainedShapeSymbolProvider(
7484
val target = model.expectShape(shape.target)
7585
val targetSymbol = this.toSymbol(target)
7686
// Handle boxing first, so we end up with `Option<Box<_>>`, not `Box<Option<_>>`.
77-
handleOptionality(handleRustBoxing(targetSymbol, shape), shape, nullableIndex, base.config().nullabilityCheckMode)
87+
handleOptionality(
88+
handleRustBoxing(targetSymbol, shape),
89+
shape,
90+
nullableIndex,
91+
base.config().nullabilityCheckMode,
92+
)
7893
}
94+
7995
is MapShape -> {
8096
if (shape.isDirectlyConstrained(base)) {
8197
check(shape.hasTrait<LengthTrait>()) {
@@ -91,6 +107,7 @@ class ConstrainedShapeSymbolProvider(
91107
.build()
92108
}
93109
}
110+
94111
is CollectionShape -> {
95112
if (shape.isDirectlyConstrained(base)) {
96113
check(constrainedCollectionCheck(shape)) {
@@ -105,8 +122,11 @@ class ConstrainedShapeSymbolProvider(
105122

106123
is StringShape, is IntegerShape, is ShortShape, is LongShape, is ByteShape, is BlobShape -> {
107124
if (shape.isDirectlyConstrained(base)) {
108-
val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase())
109-
symbolBuilder(shape, rustType).locatedIn(ServerRustModule.Model).build()
125+
// A standalone constrained shape goes into `ModelsModule`, but one
126+
// arising from a constrained member shape goes into a module for the container.
127+
val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes)
128+
val rustType = RustType.Opaque(name)
129+
symbolBuilder(shape, rustType).locatedIn(module).build()
110130
} else {
111131
base.toSymbol(shape)
112132
}
@@ -122,9 +142,51 @@ class ConstrainedShapeSymbolProvider(
122142
* - That it has no unsupported constraints applied.
123143
*/
124144
private fun constrainedCollectionCheck(shape: CollectionShape): Boolean {
125-
val supportedConstraintTraits = supportedCollectionConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet()
145+
val supportedConstraintTraits =
146+
supportedCollectionConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet()
126147
val allConstraintTraits = allConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet()
127148

128-
return supportedConstraintTraits.isNotEmpty() && allConstraintTraits.subtract(supportedConstraintTraits).isEmpty()
149+
return supportedConstraintTraits.isNotEmpty() && allConstraintTraits.subtract(supportedConstraintTraits)
150+
.isEmpty()
151+
}
152+
153+
/**
154+
* Returns the pair (Rust Symbol Name, Inline Module) for the shape. At the time of model transformation all
155+
* constrained member shapes are extracted and are given a model-wide unique name. However, the generated code
156+
* for the new shapes is in a module that is named after the containing shape (structure, list, map or union).
157+
* The new shape's Rust Symbol is renamed from `{structureName}{memberName}` to `{structure_name}::{member_name}`
158+
*/
159+
private fun getMemberNameAndModule(
160+
shape: Shape,
161+
serviceShape: ServiceShape,
162+
defaultModule: RustModule.LeafModule,
163+
pubCrateServerBuilder: Boolean,
164+
): Pair<String, RustModule.LeafModule> {
165+
val syntheticMemberTrait = shape.getTrait<SyntheticStructureFromConstrainedMemberTrait>()
166+
?: return Pair(shape.contextName(serviceShape), defaultModule)
167+
168+
return if (syntheticMemberTrait.container is StructureShape) {
169+
val builderModule = syntheticMemberTrait.container.serverBuilderModule(base, pubCrateServerBuilder)
170+
val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name
171+
Pair(renameTo.toPascalCase(), builderModule)
172+
} else {
173+
// For non-structure shapes, the new shape defined for a constrained member shape
174+
// needs to be placed in an inline module named `pub {container_name_in_snake_case}`.
175+
val moduleName = RustReservedWords.escapeIfNeeded(syntheticMemberTrait.container.id.name.toSnakeCase())
176+
val innerModuleName = moduleName + if (pubCrateServerBuilder) {
177+
"_internal"
178+
} else {
179+
""
180+
}
181+
182+
val innerModule = RustModule.new(
183+
innerModuleName,
184+
visibility = Visibility.publicIf(!pubCrateServerBuilder, Visibility.PUBCRATE),
185+
parent = defaultModule,
186+
inline = true,
187+
)
188+
val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name
189+
Pair(renameTo.toPascalCase(), innerModule)
190+
}
129191
}
130192
}

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintViolationSymbolProvider.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ import software.amazon.smithy.rust.codegen.core.smithy.contextName
2929
import software.amazon.smithy.rust.codegen.core.smithy.locatedIn
3030
import software.amazon.smithy.rust.codegen.core.smithy.module
3131
import software.amazon.smithy.rust.codegen.core.smithy.rustType
32+
import software.amazon.smithy.rust.codegen.core.util.getTrait
3233
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
3334
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderSymbol
35+
import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait
3436

3537
/**
3638
* The [ConstraintViolationSymbolProvider] returns, for a given constrained
@@ -79,15 +81,29 @@ class ConstraintViolationSymbolProvider(
7981

8082
private fun Shape.shapeModule(): RustModule.LeafModule {
8183
val documentation = if (publicConstrainedTypes && this.isDirectlyConstrained(base)) {
82-
"See [`${this.contextName(serviceShape)}`]."
84+
val symbol = base.toSymbol(this)
85+
"See [`${this.contextName(serviceShape)}`]($symbol)."
8386
} else {
8487
null
8588
}
86-
return RustModule.new(
89+
90+
val syntheticTrait = getTrait<SyntheticStructureFromConstrainedMemberTrait>()
91+
92+
val (module, name) = if (syntheticTrait != null) {
93+
// For constrained member shapes, the ConstraintViolation code needs to go in an inline rust module
94+
// that is a descendant of the module that contains the extracted shape itself.
95+
val overriddenMemberModule = this.getParentAndInlineModuleForConstrainedMember(base, publicConstrainedTypes)!!
96+
val name = syntheticTrait.member.memberName
97+
Pair(overriddenMemberModule.second, RustReservedWords.escapeIfNeeded(name).toSnakeCase())
98+
} else {
8799
// Need to use the context name so we get the correct name for maps.
88-
name = RustReservedWords.escapeIfNeeded(this.contextName(serviceShape)).toSnakeCase(),
100+
Pair(ServerRustModule.Model, RustReservedWords.escapeIfNeeded(this.contextName(serviceShape)).toSnakeCase())
101+
}
102+
103+
return RustModule.new(
104+
name = name,
89105
visibility = visibility,
90-
parent = ServerRustModule.Model,
106+
parent = module,
91107
inline = true,
92108
documentation = documentation,
93109
)

0 commit comments

Comments
 (0)