Skip to content

Commit f57e0c3

Browse files
author
Zelda Hessler
authored
Merge branch 'main' into landonxjames/obs
2 parents ad61026 + ae7b403 commit f57e0c3

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

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

+37
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,43 @@ class UnconstrainedCollectionGenerator(
143143
"InnerConstraintViolationSymbol" to innerConstraintViolationSymbol,
144144
"ConstrainValueWritable" to constrainValueWritable,
145145
)
146+
147+
val constrainedValueTypeIsNotFinalType =
148+
resolvesToNonPublicConstrainedValueType && shape.isDirectlyConstrained(symbolProvider)
149+
if (constrainedValueTypeIsNotFinalType) {
150+
// Refer to the comments under `UnconstrainedMapGenerator` for a more in-depth explanation
151+
// of this process. Consider the following Smithy model where a constrained list contains
152+
// an indirectly constrained shape as a member:
153+
//
154+
// ```smithy
155+
// @length(min: 1, max: 100)
156+
// list ItemList {
157+
// member: Item
158+
// }
159+
//
160+
// list Item {
161+
// member: ItemName
162+
// }
163+
//
164+
// @length(min: 1, max: 100)
165+
// string ItemName
166+
// ```
167+
//
168+
// The final type exposed to the user is `ItemList<Vec<Vec<ItemName>>>`. However, the
169+
// intermediate representation generated is `Vec<ItemConstrained>`. This needs to be
170+
// transformed into `Vec<Vec<ItemName>>` to satisfy the `TryFrom` implementation and
171+
// successfully construct an `ItemList` instance.
172+
//
173+
// This transformation is necessary due to the nested nature of the constraints and
174+
// the difference between the internal constrained representation and the external
175+
// user-facing type.
176+
rustTemplate(
177+
"""
178+
let inner: Vec<#{FinalType}> = inner.into_iter().map(|value| value.into()).collect();
179+
""",
180+
"FinalType" to symbolProvider.toSymbol(shape.member),
181+
)
182+
}
146183
} else {
147184
rust("let inner = value.0;")
148185
}

codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt

+134
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import software.amazon.smithy.model.shapes.StructureShape
2525
import software.amazon.smithy.model.traits.AbstractTrait
2626
import software.amazon.smithy.model.transform.ModelTransformer
2727
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait
28+
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
29+
import software.amazon.smithy.rust.codegen.core.testutil.ServerAdditionalSettings
2830
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
2931
import software.amazon.smithy.rust.codegen.core.util.lookup
32+
import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest
3033
import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestSymbolProvider
3134
import java.io.File
3235

@@ -219,4 +222,135 @@ class ConstraintsTest {
219222
structWithInnerDefault.canReachConstrainedShape(model, symbolProvider) shouldBe false
220223
primitiveBoolean.isDirectlyConstrained(symbolProvider) shouldBe false
221224
}
225+
226+
// TODO(#3895): Move tests that use `generateAndCompileServer` into `constraints.smithy` once issue is resolved
227+
private fun generateAndCompileServer(
228+
model: Model,
229+
pubConstraints: Boolean = true,
230+
dir: File? = null,
231+
) {
232+
if (dir?.exists() == true) {
233+
dir.deleteRecursively()
234+
}
235+
236+
// Simply compiling the crate is sufficient as a test.
237+
serverIntegrationTest(
238+
model,
239+
IntegrationTestParams(
240+
service = "test#SampleService",
241+
additionalSettings =
242+
ServerAdditionalSettings.builder()
243+
.publicConstrainedTypes(pubConstraints)
244+
.toObjectNode(),
245+
overrideTestDir = dir,
246+
),
247+
) { _, _ ->
248+
}
249+
}
250+
251+
private fun createModel(
252+
inputMemberShape: String,
253+
additionalShapes: () -> String,
254+
) = """
255+
namespace test
256+
use aws.protocols#restJson1
257+
use smithy.framework#ValidationException
258+
259+
@restJson1
260+
service SampleService {
261+
operations: [SampleOp]
262+
}
263+
264+
@http(uri: "/sample", method: "POST")
265+
operation SampleOp {
266+
input := {
267+
items : $inputMemberShape
268+
}
269+
errors: [ValidationException]
270+
}
271+
@length(min: 0 max: 65535)
272+
string ItemName
273+
string ItemDescription
274+
${additionalShapes()}
275+
""".asSmithyModel(smithyVersion = "2")
276+
277+
@Test
278+
fun `constrained map with an indirectly constrained nested list should compile`() {
279+
val model =
280+
createModel("ItemMap") {
281+
"""
282+
@length(min: 1 max: 100)
283+
map ItemMap {
284+
key: ItemName,
285+
value: ItemListA
286+
}
287+
list ItemListA {
288+
member: ItemListB
289+
}
290+
list ItemListB {
291+
member: ItemDescription
292+
}
293+
"""
294+
}
295+
generateAndCompileServer(model)
296+
}
297+
298+
@Test
299+
fun `constrained list with an indirectly constrained map should compile`() {
300+
val model =
301+
createModel("ItemList") {
302+
"""
303+
@length(min: 1 max: 100)
304+
list ItemList {
305+
member: Item
306+
}
307+
map Item {
308+
key: ItemName
309+
value: ItemDescription
310+
}
311+
"""
312+
}
313+
generateAndCompileServer(model)
314+
}
315+
316+
@Test
317+
fun `constrained list with an indirectly constrained nested list should compile`() {
318+
val model =
319+
createModel("ItemList") {
320+
"""
321+
@length(min: 1 max: 100)
322+
list ItemList {
323+
member: ItemA
324+
}
325+
list ItemA {
326+
member: ItemB
327+
}
328+
list ItemB {
329+
member: ItemName
330+
}
331+
"""
332+
}
333+
generateAndCompileServer(model)
334+
}
335+
336+
@Test
337+
fun `constrained list with an indirectly constrained list that has an indirectly constrained map should compile`() {
338+
val model =
339+
createModel("ItemList") {
340+
"""
341+
@length(min: 1 max: 100)
342+
list ItemList {
343+
member: NestedItemList
344+
}
345+
list NestedItemList {
346+
member: Item
347+
}
348+
map Item {
349+
key: ItemName
350+
value: ItemDescription
351+
}
352+
"""
353+
}
354+
generateAndCompileServer(model)
355+
}
222356
}

0 commit comments

Comments
 (0)