Skip to content

Commit 6532a2b

Browse files
authored
Error out for non-escaped special chars in @patterns (#2752)
## Motivation and Context Closes #2508 ## Testing Modify `codegen-core/common-test-models/pokemon.smithy`: ```diff diff --git a/codegen-core/common-test-models/pokemon.smithy b/codegen-core/common-test-models/pokemon.smithy index 014ee61..2dd37046e 100644 --- a/codegen-core/common-test-models/pokemon.smithy +++ b/codegen-core/common-test-models/pokemon.smithy @@ -59,9 +59,12 @@ structure GetStorageInput { passcode: String, } +@pattern("[.\\n\r]+") +string Species + /// A list of Pokémon species. list SpeciesCollection { - member: String + member: Species } /// Contents of the Pokémon storage. ``` Try to codegen example service and see the error: ```bash $ cd examples $ make codegen ... [ERROR] com.aws.example#Species: Non-escaped special characters used inside `@pattern`. You must escape them: `@pattern("[.\\n\\r]+")`. See #2508 for more details. | PatternTraitEscapedSpecialChars /smithy-rs/codegen-server-test/../codegen-core/common-test-models/pokemon.smithy:62:1 ``` (For some reason validation errors reported by plugins get formatted by [LineValidationEventFormatter](https://github.com/awslabs/smithy/blob/aca7df7daf31a0e71aebfeb2e72aee06ff707568/smithy-model/src/main/java/software/amazon/smithy/model/validation/LineValidationEventFormatter.java) but errors reported by smithy-cli formatted by [PrettyAnsiValidationFormatter](https://github.com/awslabs/smithy/blob/aca7df7daf31a0e71aebfeb2e72aee06ff707568/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatter.java). Might be a bug in smithy-cli) Replace pattern with `@pattern("[.\\n\\r]+")` and observe that error is gone: ```bash $ make codegen ``` ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent ba726ef commit 6532a2b

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.server.smithy
7+
8+
import software.amazon.smithy.model.Model
9+
import software.amazon.smithy.model.shapes.Shape
10+
import software.amazon.smithy.model.traits.PatternTrait
11+
import software.amazon.smithy.model.validation.AbstractValidator
12+
import software.amazon.smithy.model.validation.ValidationEvent
13+
import software.amazon.smithy.rust.codegen.core.util.dq
14+
import software.amazon.smithy.rust.codegen.core.util.expectTrait
15+
16+
class PatternTraitEscapedSpecialCharsValidator : AbstractValidator() {
17+
private val specialCharsWithEscapes = mapOf(
18+
'\b' to "\\b",
19+
'\u000C' to "\\f",
20+
'\n' to "\\n",
21+
'\r' to "\\r",
22+
'\t' to "\\t",
23+
)
24+
private val specialChars = specialCharsWithEscapes.keys
25+
26+
override fun validate(model: Model): List<ValidationEvent> {
27+
val shapes = model.getStringShapesWithTrait(PatternTrait::class.java) +
28+
model.getMemberShapesWithTrait(PatternTrait::class.java)
29+
return shapes
30+
.filter { shape -> checkMisuse(shape) }
31+
.map { shape -> makeError(shape) }
32+
.toList()
33+
}
34+
35+
private fun makeError(shape: Shape): ValidationEvent {
36+
val pattern = shape.expectTrait<PatternTrait>()
37+
val replacement = pattern.pattern.toString()
38+
.map { specialCharsWithEscapes.getOrElse(it) { it.toString() } }
39+
.joinToString("")
40+
.dq()
41+
val message =
42+
"""
43+
Non-escaped special characters used inside `@pattern`.
44+
You must escape them: `@pattern($replacement)`.
45+
See https://github.com/awslabs/smithy-rs/issues/2508 for more details.
46+
""".trimIndent()
47+
return error(shape, pattern, message)
48+
}
49+
50+
private fun checkMisuse(shape: Shape): Boolean {
51+
val pattern = shape.expectTrait<PatternTrait>().pattern.pattern()
52+
return pattern.any(specialChars::contains)
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
software.amazon.smithy.rust.codegen.server.smithy.PatternTraitEscapedSpecialCharsValidator
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.server.smithy
7+
8+
import io.kotest.assertions.throwables.shouldThrow
9+
import io.kotest.matchers.collections.shouldHaveSize
10+
import io.kotest.matchers.shouldBe
11+
import org.junit.jupiter.api.Test
12+
import software.amazon.smithy.model.shapes.ShapeId
13+
import software.amazon.smithy.model.validation.Severity
14+
import software.amazon.smithy.model.validation.ValidatedResultException
15+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
16+
17+
class PatternTraitEscapedSpecialCharsValidatorTest {
18+
@Test
19+
fun `should error out with a suggestion if non-escaped special chars used inside @pattern`() {
20+
val exception = shouldThrow<ValidatedResultException> {
21+
"""
22+
namespace test
23+
24+
@pattern("\t")
25+
string MyString
26+
""".asSmithyModel(smithyVersion = "2")
27+
}
28+
val events = exception.validationEvents.filter { it.severity == Severity.ERROR }
29+
30+
events shouldHaveSize 1
31+
events[0].shapeId.get() shouldBe ShapeId.from("test#MyString")
32+
events[0].message shouldBe """
33+
Non-escaped special characters used inside `@pattern`.
34+
You must escape them: `@pattern("\\t")`.
35+
See https://github.com/awslabs/smithy-rs/issues/2508 for more details.
36+
""".trimIndent()
37+
}
38+
39+
@Test
40+
fun `should suggest escaping spacial characters properly`() {
41+
val exception = shouldThrow<ValidatedResultException> {
42+
"""
43+
namespace test
44+
45+
@pattern("[.\n\\r]+")
46+
string MyString
47+
""".asSmithyModel(smithyVersion = "2")
48+
}
49+
val events = exception.validationEvents.filter { it.severity == Severity.ERROR }
50+
51+
events shouldHaveSize 1
52+
events[0].shapeId.get() shouldBe ShapeId.from("test#MyString")
53+
events[0].message shouldBe """
54+
Non-escaped special characters used inside `@pattern`.
55+
You must escape them: `@pattern("[.\\n\\r]+")`.
56+
See https://github.com/awslabs/smithy-rs/issues/2508 for more details.
57+
""".trimIndent()
58+
}
59+
60+
@Test
61+
fun `should report all non-escaped special characters`() {
62+
val exception = shouldThrow<ValidatedResultException> {
63+
"""
64+
namespace test
65+
66+
@pattern("\b")
67+
string MyString
68+
69+
@pattern("^\n$")
70+
string MyString2
71+
72+
@pattern("^[\n]+$")
73+
string MyString3
74+
75+
@pattern("^[\r\t]$")
76+
string MyString4
77+
""".asSmithyModel(smithyVersion = "2")
78+
}
79+
val events = exception.validationEvents.filter { it.severity == Severity.ERROR }
80+
events shouldHaveSize 4
81+
}
82+
83+
@Test
84+
fun `should report errors on string members`() {
85+
val exception = shouldThrow<ValidatedResultException> {
86+
"""
87+
namespace test
88+
89+
@pattern("\t")
90+
string MyString
91+
92+
structure MyStructure {
93+
@pattern("\b")
94+
field: String
95+
}
96+
""".asSmithyModel(smithyVersion = "2")
97+
}
98+
val events = exception.validationEvents.filter { it.severity == Severity.ERROR }
99+
100+
events shouldHaveSize 2
101+
events[0].shapeId.get() shouldBe ShapeId.from("test#MyString")
102+
events[1].shapeId.get() shouldBe ShapeId.from("test#MyStructure\$field")
103+
}
104+
105+
@Test
106+
fun `shouldn't error out if special chars are properly escaped`() {
107+
"""
108+
namespace test
109+
110+
@pattern("\\t")
111+
string MyString
112+
113+
@pattern("[.\\n\\r]+")
114+
string MyString2
115+
116+
@pattern("\\b\\f\\n\\r\\t")
117+
string MyString3
118+
119+
@pattern("\\w+")
120+
string MyString4
121+
""".asSmithyModel(smithyVersion = "2")
122+
}
123+
}

0 commit comments

Comments
 (0)