Skip to content

Commit 57c9306

Browse files
author
Fahad Zubair
committed
Implement Display for shapes reachable from error shapes
1 parent 5f849fd commit 57c9306

File tree

7 files changed

+334
-14
lines changed

7 files changed

+334
-14
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerat
4242
import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator
4343
import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator
4444
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory
45+
import software.amazon.smithy.rust.codegen.core.smithy.transformers.AddSyntheticTraitForImplDisplay
4546
import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer
4647
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
4748
import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer
@@ -146,6 +147,9 @@ class ClientCodegenVisitor(
146147
.let(EventStreamNormalizer::transform)
147148
// Mark operations incompatible with stalled stream protection as such
148149
.let(DisableStalledStreamProtection::transformModel)
150+
// Add synthetic trait to shapes referenced by error types to ensure they implement `Display`.
151+
// This ensures error formatting works correctly for nested structures.
152+
.let(AddSyntheticTraitForImplDisplay::transform)
149153

150154
/**
151155
* Execute code generation

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCus
1414
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
1515
import software.amazon.smithy.rust.codegen.client.smithy.generators.http.ResponseBindingGenerator
1616
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
17+
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
1718
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
1819
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
1920
import software.amazon.smithy.rust.codegen.core.rustlang.assignment
@@ -33,6 +34,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpLocation
3334
import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
3435
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolFunctions
3536
import software.amazon.smithy.rust.codegen.core.smithy.protocols.parse.StructuredDataParserGenerator
37+
import software.amazon.smithy.rust.codegen.core.smithy.rustType
3638
import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors
3739
import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE
3840
import software.amazon.smithy.rust.codegen.core.util.dq
@@ -163,10 +165,11 @@ class ProtocolParserGenerator(
163165
}
164166
}
165167
val errorMessageMember = errorShape.errorMessageMember()
166-
// If the message member is optional and wasn't set, we set a generic error message.
168+
// If the message member is optional, is of `String` Rust type and wasn't set, we set a generic error message.
167169
if (errorMessageMember != null) {
168170
val symbol = symbolProvider.toSymbol(errorMessageMember)
169-
if (symbol.isOptional()) {
171+
val currentRustType = symbol.rustType()
172+
if (symbol.isOptional() && currentRustType == RustType.String) {
170173
rust(
171174
"""
172175
if tmp.message.is_none() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package software.amazon.smithy.rust.codegen.client.smithy.generators
2+
3+
import org.junit.jupiter.api.Test
4+
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
5+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
6+
import java.io.File
7+
8+
class ClientErrorReachableShapesDisplayTest {
9+
@Test
10+
fun correctMissingFields() {
11+
var sampleModel = File("../codegen-core/common-test-models/nested-error.smithy").readText().asSmithyModel()
12+
clientIntegrationTest(sampleModel) { _, _ ->
13+
// It should compile.
14+
}
15+
}
16+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
$version: "2"
2+
3+
namespace sample
4+
5+
use smithy.framework#ValidationException
6+
use aws.protocols#restJson1
7+
8+
@restJson1
9+
service SampleService {
10+
operations: [SampleOperation]
11+
}
12+
13+
@http(uri: "/anOperation", method: "POST")
14+
operation SampleOperation {
15+
output:= {}
16+
input:= {}
17+
errors: [
18+
SimpleError,
19+
ErrorWithCompositeShape,
20+
ErrorWithDeepCompositeShape,
21+
ComposedSensitiveError
22+
]
23+
}
24+
25+
@error("client")
26+
structure ErrorWithCompositeShape {
27+
message: ErrorMessage
28+
}
29+
30+
@error("client")
31+
structure SimpleError {
32+
message: String
33+
}
34+
35+
structure ErrorMessage {
36+
@required
37+
statusCode: String
38+
@required
39+
errorMessage: String
40+
requestId: String
41+
@required
42+
toolName: String
43+
}
44+
45+
structure WrappedErrorMessage {
46+
someValue: Integer
47+
contained: ErrorMessage
48+
}
49+
50+
@error("client")
51+
structure ErrorWithDeepCompositeShape {
52+
message: WrappedErrorMessage
53+
}
54+
55+
@sensitive
56+
structure SensitiveMessage {
57+
nothing: String
58+
should: String
59+
bePrinted: String
60+
}
61+
62+
@error("server")
63+
structure ComposedSensitiveError {
64+
message: SensitiveMessage
65+
}
66+
67+
@error("server")
68+
structure ErrorWithNestedError {
69+
message: ErrorWithDeepCompositeShape
70+
}

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

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ import software.amazon.smithy.rust.codegen.core.rustlang.isDeref
2424
import software.amazon.smithy.rust.codegen.core.rustlang.render
2525
import software.amazon.smithy.rust.codegen.core.rustlang.rust
2626
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
27+
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
2728
import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter
2829
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
30+
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
2931
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
3032
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
3133
import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
3234
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
3335
import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata
36+
import software.amazon.smithy.rust.codegen.core.smithy.isOptional
37+
import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.ValueExpression
3438
import software.amazon.smithy.rust.codegen.core.smithy.renamedFrom
3539
import software.amazon.smithy.rust.codegen.core.smithy.rustType
40+
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticImplDisplayTrait
3641
import software.amazon.smithy.rust.codegen.core.util.REDACTION
3742
import software.amazon.smithy.rust.codegen.core.util.dq
3843
import software.amazon.smithy.rust.codegen.core.util.getTrait
@@ -104,20 +109,9 @@ open class StructureGenerator(
104109
) {
105110
writer.rustBlock("fn fmt(&self, f: &mut #1T::Formatter<'_>) -> #1T::Result", RuntimeType.stdFmt) {
106111
rust("""let mut formatter = f.debug_struct(${name.dq()});""")
107-
108112
members.forEach { member ->
109113
val memberName = symbolProvider.toMemberName(member)
110-
// If the struct is marked sensitive all fields get redacted, otherwise each field is determined on its own
111-
val fieldValue =
112-
if (shape.shouldRedact(model)) {
113-
REDACTION
114-
} else {
115-
member.redactIfNecessary(
116-
model,
117-
"self.$memberName",
118-
)
119-
}
120-
114+
val fieldValue = getFieldValue(member, memberName)
121115
rust(
122116
"formatter.field(${memberName.dq()}, &$fieldValue);",
123117
)
@@ -128,6 +122,66 @@ open class StructureGenerator(
128122
}
129123
}
130124

125+
// If the struct is marked sensitive all fields get redacted, otherwise each field is determined on its own.
126+
private fun getFieldValue(
127+
member: MemberShape,
128+
memberName: String?,
129+
): String {
130+
val fieldValue =
131+
if (shape.shouldRedact(model)) {
132+
REDACTION
133+
} else {
134+
member.redactIfNecessary(
135+
model,
136+
"self.$memberName",
137+
)
138+
}
139+
return fieldValue
140+
}
141+
142+
private fun renderImplDisplayIfSyntheticImplDisplayTraitApplied() {
143+
if (shape.getTrait<SyntheticImplDisplayTrait>() == null) {
144+
return
145+
}
146+
147+
val lifetime = shape.lifetimeDeclaration(symbolProvider)
148+
writer.rustBlock(
149+
"impl ${shape.lifetimeDeclaration(symbolProvider)} #T for $name $lifetime",
150+
RuntimeType.Display,
151+
) {
152+
writer.rustBlock("fn fmt(&self, f: &mut #1T::Formatter<'_>) -> #1T::Result", RuntimeType.stdFmt) {
153+
write("""::std::write!(f, "$name {{")?;""")
154+
155+
members.forEachIndexed { index, member ->
156+
val separator = if (index > 0) ", " else ""
157+
val memberName = symbolProvider.toMemberName(member)
158+
val shouldRedact = shape.shouldRedact(model) || member.shouldRedact(model)
159+
160+
// If the shape is redacted then each member shape will be redacted.
161+
if (shouldRedact) {
162+
write("""::std::write!(f, "$separator$memberName={}", $REDACTION)?;""")
163+
} else {
164+
val variable = ValueExpression.Reference("&self.$memberName")
165+
val memberSymbol = symbolProvider.toSymbol(member)
166+
167+
if (memberSymbol.isOptional()) {
168+
rustBlockTemplate("if let #{Some}(inner) = ${variable.asRef()}", *preludeScope) {
169+
write("""::std::write!(f, "$separator$memberName=Some({})", inner)?;""")
170+
}
171+
rustBlock("else") {
172+
write("""::std::write!(f, "$separator$memberName=None")?;""")
173+
}
174+
} else {
175+
write("""::std::write!(f, "$separator$memberName={}", ${variable.asRef()})?;""")
176+
}
177+
}
178+
}
179+
180+
write("""::std::write!(f, "}}")""")
181+
}
182+
}
183+
}
184+
131185
private fun renderStructureImpl() {
132186
if (accessorMembers.isEmpty()) {
133187
return
@@ -209,6 +263,7 @@ open class StructureGenerator(
209263
if (!containerMeta.hasDebugDerive()) {
210264
renderDebugImpl()
211265
}
266+
renderImplDisplayIfSyntheticImplDisplayTraitApplied()
212267

213268
writer.writeCustomizations(customizations, StructureSection.AdditionalTraitImpls(shape, name))
214269
}

0 commit comments

Comments
 (0)