Skip to content

Commit 5259ca2

Browse files
committed
Add union serialization tests and fix unused variable warnings
- Add comprehensive union serialization tests for JSON, Query, and CBOR protocols - Fix unused variable warnings in serialization generators - Update test structure and model for proper union serialization testing
1 parent 052538d commit 5259ca2

File tree

11 files changed

+3976
-587
lines changed

11 files changed

+3976
-587
lines changed

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,28 @@ class JsonSerializerGenerator(
555555
}
556556
rustBlock("match input") {
557557
for (member in context.shape.members()) {
558+
val memberShape = model.expectShape(member.target)
558559
val variantName =
559560
if (member.isTargetUnit()) {
560561
"${symbolProvider.toMemberName(member)}"
562+
} else if (memberShape.isStructureShape &&
563+
memberShape.asStructureShape().get().allMembers.isEmpty()
564+
) {
565+
// Unit structs don't serialize inner, so it is never accessed
566+
"${symbolProvider.toMemberName(member)}(_inner)"
561567
} else {
562568
"${symbolProvider.toMemberName(member)}(inner)"
563569
}
564570
withBlock("#T::$variantName => {", "},", unionSymbol) {
565-
serializeMember(MemberContext.unionMember(context, "inner", member, jsonName))
571+
val innerRef =
572+
if (memberShape.isStructureShape &&
573+
memberShape.asStructureShape().get().allMembers.isEmpty()
574+
) {
575+
"_inner"
576+
} else {
577+
"inner"
578+
}
579+
serializeMember(MemberContext.unionMember(context, innerRef, member, jsonName))
566580
}
567581
}
568582
if (codegenTarget.renderUnknownVariant()) {

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,17 +357,32 @@ abstract class QuerySerializerGenerator(private val codegenContext: CodegenConte
357357
) {
358358
rustBlock("match input") {
359359
for (member in context.shape.members()) {
360+
val memberShape = model.expectShape(member.target)
361+
val memberName = symbolProvider.toMemberName(member)
360362
val variantName =
361363
if (member.isTargetUnit()) {
362-
"${symbolProvider.toMemberName(member)}"
364+
"$memberName"
365+
} else if (memberShape.isStructureShape &&
366+
memberShape.asStructureShape().get().allMembers.isEmpty()
367+
) {
368+
// Unit structs don't serialize inner, so it is never accessed
369+
"$memberName(_inner)"
363370
} else {
364-
"${symbolProvider.toMemberName(member)}(inner)"
371+
"$memberName(inner)"
365372
}
366373
withBlock("#T::$variantName => {", "},", unionSymbol) {
374+
val innerRef =
375+
if (memberShape.isStructureShape &&
376+
memberShape.asStructureShape().get().allMembers.isEmpty()
377+
) {
378+
"_inner"
379+
} else {
380+
"inner"
381+
}
367382
serializeMember(
368383
MemberContext.unionMember(
369384
context.copy(writerExpression = "writer"),
370-
"inner",
385+
innerRef,
371386
member,
372387
),
373388
)

codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGeneratorTest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
package software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize
77

8+
import org.junit.jupiter.api.Test
89
import org.junit.jupiter.params.ParameterizedTest
910
import org.junit.jupiter.params.provider.CsvSource
1011
import software.amazon.smithy.model.knowledge.NullableIndex
1112
import software.amazon.smithy.model.shapes.OperationShape
1213
import software.amazon.smithy.model.shapes.StringShape
1314
import software.amazon.smithy.model.shapes.StructureShape
15+
import software.amazon.smithy.model.shapes.UnionShape
1416
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
1517
import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGenerator
1618
import software.amazon.smithy.rust.codegen.core.smithy.generators.TestEnumType
@@ -342,4 +344,57 @@ class JsonSerializerGeneratorTest {
342344
}
343345
project.compileAndTest()
344346
}
347+
348+
@Test
349+
fun `union with unit struct doesn't cause unused variable warning`() {
350+
// Regression test for https://github.com/smithy-lang/smithy-rs/issues/4308
351+
// This test ensures that union serialization with unit structs compiles without unused variable warnings.
352+
val model = RecursiveShapeBoxer().transform(OperationNormalizer.transform(QuerySerializerGeneratorTest.unionWithUnitStructModel))
353+
354+
val codegenContext = testCodegenContext(model)
355+
val symbolProvider = codegenContext.symbolProvider
356+
val project = TestWorkspace.testProject(symbolProvider)
357+
358+
// Generate the JSON serializer that will create the union serialization code
359+
val jsonSerializer =
360+
JsonSerializerGenerator(
361+
codegenContext,
362+
HttpTraitHttpBindingResolver(model, ProtocolContentTypes.consistent("application/json")),
363+
::restJsonFieldName,
364+
)
365+
val operationGenerator = jsonSerializer.operationInputSerializer(model.lookup("test#TestOp"))
366+
367+
// Render all necessary structures and unions
368+
model.lookup<StructureShape>("test#Unit").renderWithModelBuilder(model, symbolProvider, project)
369+
model.lookup<OperationShape>("test#TestOp").inputShape(model).renderWithModelBuilder(model, symbolProvider, project)
370+
371+
project.moduleFor(model.lookup<UnionShape>("test#TestUnion")) {
372+
UnionGenerator(model, symbolProvider, this, model.lookup("test#TestUnion")).render()
373+
}
374+
375+
// Generate the actual protocol_serde module with union serialization
376+
project.lib {
377+
unitTest(
378+
"json_union_serialization",
379+
"""
380+
use test_model::{TestUnion, Unit};
381+
382+
// Create a test input to actually use the serializer
383+
let input = crate::test_input::TestOpInput::builder()
384+
.union(TestUnion::UnitMember(Unit::builder().build()))
385+
.build()
386+
.unwrap();
387+
388+
// This will generate and use the serialization code that should not have unused variable warnings
389+
let _serialized = ${format(operationGenerator!!)};
390+
let _result = _serialized(&input);
391+
392+
// Test that the code compiles and runs - this validates our fix works
393+
""",
394+
)
395+
}
396+
397+
// The test passes if the generated code compiles without unused variable warnings
398+
project.compileAndTest()
399+
}
345400
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.core.smithy.protocols.serialize
7+
8+
import org.junit.jupiter.api.Test
9+
import software.amazon.smithy.model.shapes.OperationShape
10+
import software.amazon.smithy.model.shapes.StructureShape
11+
import software.amazon.smithy.model.shapes.UnionShape
12+
import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator
13+
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
14+
import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer
15+
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
16+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
17+
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
18+
import software.amazon.smithy.rust.codegen.core.testutil.renderWithModelBuilder
19+
import software.amazon.smithy.rust.codegen.core.testutil.testCodegenContext
20+
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
21+
import software.amazon.smithy.rust.codegen.core.util.inputShape
22+
import software.amazon.smithy.rust.codegen.core.util.lookup
23+
24+
class QuerySerializerGeneratorTest {
25+
companion object {
26+
val unionWithUnitStructModel =
27+
"""
28+
namespace test
29+
30+
union TestUnion {
31+
unitMember: Unit
32+
}
33+
34+
structure Unit {}
35+
36+
@http(uri: "/test", method: "POST")
37+
operation TestOp {
38+
input: TestInput
39+
}
40+
41+
structure TestInput {
42+
union: TestUnion
43+
}
44+
""".asSmithyModel()
45+
}
46+
47+
@Test
48+
fun `union with unit struct doesn't cause unused variable warning`() {
49+
// Regression test for https://github.com/smithy-lang/smithy-rs/issues/4308
50+
val model = RecursiveShapeBoxer().transform(OperationNormalizer.transform(unionWithUnitStructModel))
51+
52+
val codegenContext = testCodegenContext(model)
53+
val symbolProvider = codegenContext.symbolProvider
54+
val project = TestWorkspace.testProject(symbolProvider)
55+
56+
val querySerializer = Ec2QuerySerializerGenerator(codegenContext)
57+
val operationGenerator = querySerializer.operationInputSerializer(model.lookup("test#TestOp"))
58+
59+
// Render all necessary structures and unions
60+
model.lookup<StructureShape>("test#Unit").renderWithModelBuilder(model, symbolProvider, project)
61+
model.lookup<OperationShape>("test#TestOp").inputShape(model).renderWithModelBuilder(model, symbolProvider, project)
62+
63+
project.moduleFor(model.lookup<UnionShape>("test#TestUnion")) {
64+
UnionGenerator(model, symbolProvider, this, model.lookup("test#TestUnion")).render()
65+
}
66+
67+
// Generate the serialization module that will contain the union serialization code
68+
project.lib {
69+
unitTest(
70+
"test_query_union_serialization",
71+
"""
72+
use test_model::{TestUnion, Unit};
73+
74+
// Create a test input to actually use the serializer
75+
let input = crate::test_input::TestOpInput::builder()
76+
.union(TestUnion::UnitMember(Unit::builder().build()))
77+
.build()
78+
.unwrap();
79+
80+
// This will generate and use the serialization code that should not have unused variable warnings
81+
let _serialized = ${format(operationGenerator!!)};
82+
let _result = _serialized(&input);
83+
84+
// Test that the code compiles and runs - this validates our fix works
85+
""",
86+
)
87+
}
88+
89+
project.compileAndTest()
90+
}
91+
}

0 commit comments

Comments
 (0)