Skip to content

Commit 2e2f51f

Browse files
david-perezaws-sdk-rust-ci
authored andcommitted
Disallow @uniqueItems-constrained list shapes that reach a map shape (#2375)
* Disallow `@uniqueItems`-constrained list shapes that reach a map shape The server SDK codegen generates Rust code that does not compile when a `@uniqueItems`-constrained list shape reaches a map shape, essentially because `std::collections::HashMap` does not implement `std::hash::Hash`. A ticket with the Smithy team was opened in smithy-lang/smithy#1567 to disallow such models. This commit makes it so that codegen aborts with an informative message when such models are encountered, instead of going ahead and producing code that does not compile. This commit also slightly adjusts the error messages produced when unsupported constraint traits are found. * ./gradlew ktlintFormat
1 parent 78cca71 commit 2e2f51f

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

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

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import software.amazon.smithy.model.shapes.BlobShape
1010
import software.amazon.smithy.model.shapes.ByteShape
1111
import software.amazon.smithy.model.shapes.EnumShape
1212
import software.amazon.smithy.model.shapes.IntegerShape
13+
import software.amazon.smithy.model.shapes.ListShape
1314
import software.amazon.smithy.model.shapes.LongShape
15+
import software.amazon.smithy.model.shapes.MapShape
1416
import software.amazon.smithy.model.shapes.MemberShape
1517
import software.amazon.smithy.model.shapes.OperationShape
1618
import software.amazon.smithy.model.shapes.ServiceShape
@@ -36,13 +38,17 @@ private sealed class UnsupportedConstraintMessageKind {
3638
private val constraintTraitsUberIssue = "https://github.com/awslabs/smithy-rs/issues/1401"
3739

3840
fun intoLogMessage(ignoreUnsupportedConstraints: Boolean): LogMessage {
39-
fun buildMessage(intro: String, willSupport: Boolean, trackingIssue: String, canBeIgnored: Boolean = true): String {
41+
fun buildMessage(intro: String, willSupport: Boolean, trackingIssue: String? = null, canBeIgnored: Boolean = true): String {
4042
var msg = """
41-
$intro
43+
$intro
4244
This is not supported in the smithy-rs server SDK."""
4345
if (willSupport) {
4446
msg += """
45-
It will be supported in the future. See the tracking issue ($trackingIssue)."""
47+
It will be supported in the future."""
48+
}
49+
if (trackingIssue != null) {
50+
msg += """
51+
For more information, and to report if you're affected by this, please use the tracking issue: $trackingIssue."""
4652
}
4753
if (canBeIgnored) {
4854
msg += """
@@ -106,6 +112,19 @@ private sealed class UnsupportedConstraintMessageKind {
106112
level,
107113
buildMessageShapeHasUnsupportedConstraintTrait(shape, uniqueItemsTrait, constraintTraitsUberIssue),
108114
)
115+
116+
is UnsupportedMapShapeReachableFromUniqueItemsList -> LogMessage(
117+
Level.SEVERE,
118+
buildMessage(
119+
"""
120+
The map shape `${mapShape.id}` is reachable from the list shape `${listShape.id}`, which has the
121+
`@uniqueItems` trait attached.
122+
""".trimIndent().replace("\n", " "),
123+
willSupport = false,
124+
trackingIssue = "https://github.com/awslabs/smithy/issues/1567",
125+
canBeIgnored = false,
126+
),
127+
)
109128
}
110129
}
111130
}
@@ -129,6 +148,12 @@ private data class UnsupportedRangeTraitOnShape(val shape: Shape, val rangeTrait
129148
private data class UnsupportedUniqueItemsTraitOnShape(val shape: Shape, val uniqueItemsTrait: UniqueItemsTrait) :
130149
UnsupportedConstraintMessageKind()
131150

151+
private data class UnsupportedMapShapeReachableFromUniqueItemsList(
152+
val listShape: ListShape,
153+
val uniqueItemsTrait: UniqueItemsTrait,
154+
val mapShape: MapShape,
155+
) : UnsupportedConstraintMessageKind()
156+
132157
data class LogMessage(val level: Level, val message: String)
133158
data class ValidationResult(val shouldAbort: Boolean, val messages: List<LogMessage>) :
134159
Throwable(message = messages.joinToString("\n") { it.message })
@@ -246,11 +271,29 @@ fun validateUnsupportedConstraints(
246271
.map { (shape, rangeTrait) -> UnsupportedRangeTraitOnShape(shape, rangeTrait as RangeTrait) }
247272
.toSet()
248273

274+
// 5. `@uniqueItems` cannot reach a map shape.
275+
// See https://github.com/awslabs/smithy/issues/1567.
276+
val mapShapeReachableFromUniqueItemsListShapeSet = walker
277+
.walkShapes(service)
278+
.asSequence()
279+
.filterMapShapesToTraits(setOf(UniqueItemsTrait::class.java))
280+
.flatMap { (listShape, uniqueItemsTrait) ->
281+
walker.walkShapes(listShape).filterIsInstance<MapShape>().map { mapShape ->
282+
UnsupportedMapShapeReachableFromUniqueItemsList(
283+
listShape as ListShape,
284+
uniqueItemsTrait as UniqueItemsTrait,
285+
mapShape,
286+
)
287+
}
288+
}
289+
.toSet()
290+
249291
val messages =
250292
unsupportedConstraintOnMemberShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } +
251293
unsupportedLengthTraitOnStreamingBlobShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } +
252294
unsupportedConstraintShapeReachableViaAnEventStreamSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } +
253-
unsupportedRangeTraitOnShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) }
295+
unsupportedRangeTraitOnShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } +
296+
mapShapeReachableFromUniqueItemsListShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) }
254297

255298
return ValidationResult(shouldAbort = messages.any { it.level == Level.SEVERE }, messages)
256299
}

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest {
2727
namespace test
2828
2929
service TestService {
30-
version: "123",
3130
operations: [TestOperation]
3231
}
3332
@@ -186,6 +185,47 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest {
186185
}
187186
}
188187

188+
private val mapShapeReachableFromUniqueItemsListShapeModel =
189+
"""
190+
$baseModel
191+
192+
structure TestInputOutput {
193+
uniqueItemsList: UniqueItemsList
194+
}
195+
196+
@uniqueItems
197+
list UniqueItemsList {
198+
member: Map
199+
}
200+
201+
map Map {
202+
key: String
203+
value: String
204+
}
205+
""".asSmithyModel()
206+
207+
@Test
208+
fun `it should detect when a map shape is reachable from a uniqueItems list shape`() {
209+
val validationResult = validateModel(mapShapeReachableFromUniqueItemsListShapeModel)
210+
211+
validationResult.messages shouldHaveSize 1
212+
validationResult.shouldAbort shouldBe true
213+
validationResult.messages[0].message shouldContain """
214+
The map shape `test#Map` is reachable from the list shape `test#UniqueItemsList`, which has the
215+
`@uniqueItems` trait attached.
216+
""".trimIndent().replace("\n", " ")
217+
}
218+
219+
@Test
220+
fun `it should abort when a map shape is reachable from a uniqueItems list shape, despite opting into ignoreUnsupportedConstraintTraits`() {
221+
val validationResult = validateModel(
222+
mapShapeReachableFromUniqueItemsListShapeModel,
223+
ServerCodegenConfig().copy(ignoreUnsupportedConstraints = true),
224+
)
225+
226+
validationResult.shouldAbort shouldBe true
227+
}
228+
189229
@Test
190230
fun `it should abort when constraint traits in event streams are used, despite opting into ignoreUnsupportedConstraintTraits`() {
191231
val validationResult = validateModel(

0 commit comments

Comments
 (0)