Skip to content

Commit 7991280

Browse files
udayprakashUday Prakashaajtoddlandonxjames
authored
fix: Handle # characters in enum values during code generation (#4476)
## Description Use `rawRust()` instead of `rust()` when generating enum match arms to prevent template formatting errors when enum values contain `#` characters (e.g., `Fresh#AE`). ## Problem The `rust()` method interprets `#` as a template placeholder, causing errors like: ``` Given 0 arguments but attempted to format index 0 (template: "Fresh#AE" => BusinessRealm::FreshAe,) ``` ## Solution This change uses `rawRust()` which bypasses template formatting for: - `ClientEnumGenerator.kt`: FromStr and Display implementations - `EnumGenerator.kt`: as_str() and VALUES constant generation ## Testing Tested with enum values containing `#` characters (e.g., `Fresh#AE`, `GO#UK`, `WFM#US`). --------- Co-authored-by: Uday Prakash <udapraka@amazon.com> Co-authored-by: Aaron Todd <aajtodd@users.noreply.github.com> Co-authored-by: Landon James <lnj@amazon.com>
1 parent 125c625 commit 7991280

File tree

5 files changed

+107
-10
lines changed

5 files changed

+107
-10
lines changed

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
1313
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
1414
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
1515
import software.amazon.smithy.rust.codegen.core.rustlang.docs
16+
import software.amazon.smithy.rust.codegen.core.rustlang.rawRust
1617
import software.amazon.smithy.rust.codegen.core.rustlang.rust
1718
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
1819
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
@@ -54,7 +55,7 @@ data class InfallibleEnumType(
5455
"matchArms" to
5556
writable {
5657
context.sortedMembers.forEach { member ->
57-
rust("${member.value.dq()} => ${context.enumName}::${member.derivedName()},")
58+
rawRust("${member.value.dq()} => ${context.enumName}::${member.derivedName()},\n")
5859
}
5960
rust(
6061
"other => ${context.enumName}::$UNKNOWN_VARIANT(#T(other.to_owned()))",
@@ -149,11 +150,7 @@ data class InfallibleEnumType(
149150
"matchArms" to
150151
writable {
151152
context.sortedMembers.forEach { member ->
152-
rust(
153-
"""
154-
${context.enumName}::${member.derivedName()} => write!(f, ${member.value.dq()}),
155-
""",
156-
)
153+
rawRust("${context.enumName}::${member.derivedName()} => write!(f, ${member.value.dq()}),\n")
157154
}
158155
rust("""${context.enumName}::Unknown(value) => write!(f, "{value}")""")
159156
},

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,38 @@ class ClientEnumGeneratorTest {
225225
}
226226
project.compileAndTest()
227227
}
228+
229+
@Test
230+
fun `it handles enum values containing hash characters`() {
231+
val model =
232+
"""
233+
namespace test
234+
@enum([
235+
{ value: "Fruits#Apple", name: "FRUITS_APPLE" },
236+
{ value: "Fruits#Banana", name: "FRUITS_BANANA" },
237+
{ value: "Veggies#Carrot", name: "VEGGIES_CARROT" },
238+
])
239+
string FoodCategory
240+
""".asSmithyModel()
241+
242+
val shape = model.lookup<StringShape>("test#FoodCategory")
243+
val context = testClientCodegenContext(model)
244+
val project = TestWorkspace.testProject(context.symbolProvider)
245+
project.moduleFor(shape) {
246+
rust("##![allow(deprecated)]")
247+
ClientEnumGenerator(context, shape, emptyList()).render(this)
248+
unitTest(
249+
"enum_values_with_hash_characters",
250+
"""
251+
assert_eq!(FoodCategory::FruitsApple.as_str(), "Fruits#Apple");
252+
assert_eq!(FoodCategory::FruitsBanana.as_str(), "Fruits#Banana");
253+
assert_eq!(FoodCategory::VeggiesCarrot.as_str(), "Veggies#Carrot");
254+
assert_eq!(FoodCategory::from("Fruits#Apple"), FoodCategory::FruitsApple);
255+
assert_eq!(FoodCategory::from("Veggies#Carrot"), FoodCategory::VeggiesCarrot);
256+
assert_eq!(format!("{}", FoodCategory::FruitsApple), "Fruits#Apple");
257+
""",
258+
)
259+
}
260+
project.compileAndTest()
261+
}
228262
}

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape
2121
import software.amazon.smithy.rust.codegen.core.rustlang.docs
2222
import software.amazon.smithy.rust.codegen.core.rustlang.documentShape
2323
import software.amazon.smithy.rust.codegen.core.rustlang.escape
24+
import software.amazon.smithy.rust.codegen.core.rustlang.rawRust
2425
import software.amazon.smithy.rust.codegen.core.rustlang.rust
2526
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
2627
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
@@ -245,7 +246,10 @@ open class EnumGenerator(
245246
writable {
246247
rustBlock("match self") {
247248
context.sortedMembers.forEach { member ->
248-
rust("""${context.enumName}::${member.derivedName()} => ${member.value.dq()},""")
249+
rawRust(
250+
"""${context.enumName}::${member.derivedName()} => ${member.value.dq()},
251+
""",
252+
)
249253
}
250254
enumType.additionalAsStrMatchArms(context)(this)
251255
}
@@ -322,7 +326,7 @@ open class EnumGenerator(
322326
"asStrImpl" to asStrImpl,
323327
"Values" to
324328
writable {
325-
rust(context.sortedMembers.joinToString(", ") { it.value.dq() })
329+
rawRust(context.sortedMembers.joinToString(", ") { it.value.dq() })
326330
},
327331
)
328332
}

codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGeneratorTest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,5 +553,66 @@ class EnumGeneratorTest {
553553
}
554554
project.compileAndTest()
555555
}
556+
557+
@Test
558+
fun `it handles named enum values containing hash characters`() {
559+
val model =
560+
"""
561+
namespace test
562+
@enum([
563+
{ value: "Fruits#Apple", name: "FRUITS_APPLE" },
564+
{ value: "Veggies#Carrot", name: "VEGGIES_CARROT" },
565+
{ value: "Dairy#Milk#Whole", name: "DAIRY_MILK_WHOLE" },
566+
])
567+
string FoodCategory
568+
""".asSmithyModel()
569+
570+
val shape = model.lookup<StringShape>("test#FoodCategory")
571+
val provider = testSymbolProvider(model)
572+
val project = TestWorkspace.testProject(provider)
573+
project.moduleFor(shape) {
574+
renderEnum(model, provider, shape)
575+
unitTest(
576+
"named_enum_values_with_hash_characters",
577+
"""
578+
assert_eq!(FoodCategory::FruitsApple.as_str(), "Fruits#Apple");
579+
assert_eq!(FoodCategory::VeggiesCarrot.as_str(), "Veggies#Carrot");
580+
assert_eq!(FoodCategory::DairyMilkWhole.as_str(), "Dairy#Milk#Whole");
581+
assert_eq!(FoodCategory::from("Fruits#Apple"), FoodCategory::FruitsApple);
582+
assert_eq!(FoodCategory::values(), &["Dairy#Milk#Whole", "Fruits#Apple", "Veggies#Carrot"]);
583+
""",
584+
)
585+
}
586+
project.compileAndTest()
587+
}
588+
589+
@Test
590+
fun `it handles unnamed enum values containing hash characters`() {
591+
val model =
592+
"""
593+
namespace test
594+
@enum([
595+
{ value: "Fruits#Apple" },
596+
{ value: "Veggies#Carrot" },
597+
])
598+
string FoodCategory
599+
""".asSmithyModel()
600+
601+
val shape = model.lookup<StringShape>("test#FoodCategory")
602+
val provider = testSymbolProvider(model)
603+
val project = TestWorkspace.testProject(provider)
604+
project.moduleFor(shape) {
605+
renderEnum(model, provider, shape)
606+
unitTest(
607+
"unnamed_enum_values_with_hash_characters",
608+
"""
609+
assert_eq!(FoodCategory::from("Fruits#Apple").as_str(), "Fruits#Apple");
610+
assert_eq!(FoodCategory::from("Veggies#Carrot").as_str(), "Veggies#Carrot");
611+
assert_eq!(FoodCategory::values(), &["Fruits#Apple", "Veggies#Carrot"]);
612+
""",
613+
)
614+
}
615+
project.compileAndTest()
616+
}
556617
}
557618
}

codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/TestEnumType.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package software.amazon.smithy.rust.codegen.core.smithy.generators
77

88
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
9+
import software.amazon.smithy.rust.codegen.core.rustlang.rawRust
910
import software.amazon.smithy.rust.codegen.core.rustlang.rust
1011
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
1112
import software.amazon.smithy.rust.codegen.core.rustlang.writable
@@ -30,9 +31,9 @@ object TestEnumType : EnumType() {
3031
"matchArms" to
3132
writable {
3233
context.sortedMembers.forEach { member ->
33-
rust("${member.value.dq()} => ${context.enumName}::${member.derivedName()},")
34+
rawRust("${member.value.dq()} => ${context.enumName}::${member.derivedName()},\n")
3435
}
35-
rust("_ => panic!()")
36+
rawRust("_ => panic!()")
3637
},
3738
)
3839
}

0 commit comments

Comments
 (0)