Skip to content

Commit d6928be

Browse files
authored
refactor: delegate exception error messages to exception base class logic (#1044)
1 parent 9bf8804 commit d6928be

File tree

5 files changed

+70
-28
lines changed

5 files changed

+70
-28
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "f274caeb-9450-45b1-935c-5f61905de53b",
3+
"type": "misc",
4+
"description": "Refactor exception codegen to delegate message field to exception base class"
5+
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/StructureGenerator.kt

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,33 +66,29 @@ class StructureGenerator(
6666
// generate the immutable properties that are set from a builder
6767
sortedMembers.forEach {
6868
val (memberName, memberSymbol) = memberNameSymbolIndex[it]!!
69+
// Throwable.message is handled special and passed as a constructor parameter to the parent exception base class
70+
if (shape.isError && memberName == "message") {
71+
val targetShape = model.expectShape(it.target)
72+
if (!targetShape.isStringShape) {
73+
throw CodegenException("message is a reserved name for exception types and cannot be used for any other property")
74+
}
75+
return@forEach
76+
}
6977
writer.renderMemberDocumentation(model, it)
7078
writer.renderAnnotations(it)
71-
renderImmutableProperty(it, memberName, memberSymbol)
79+
renderImmutableProperty(memberName, memberSymbol)
7280
}
7381
}
7482

75-
private fun renderImmutableProperty(memberShape: MemberShape, memberName: String, memberSymbol: Symbol) {
76-
// override Throwable's message property
77-
val prefix = if (shape.isError && memberName == "message") {
78-
val targetShape = model.expectShape(memberShape.target)
79-
if (!targetShape.isStringShape) {
80-
throw CodegenException("message is a reserved name for exception types and cannot be used for any other property")
81-
}
82-
"override"
83-
} else {
84-
"public"
85-
}
86-
83+
private fun renderImmutableProperty(memberName: String, memberSymbol: Symbol) {
8784
if (memberSymbol.isRequiredWithNoDefault) {
8885
writer.write(
89-
"""#1L val #2L: #3F = requireNotNull(builder.#2L) { "A non-null value must be provided for #2L" }""",
90-
prefix,
86+
"""public val #1L: #2F = requireNotNull(builder.#1L) { "A non-null value must be provided for #1L" }""",
9187
memberName,
9288
memberSymbol,
9389
)
9490
} else {
95-
writer.write("#1L val #2L: #3F = builder.#2L", prefix, memberName, memberSymbol)
91+
writer.write("public val #1L: #2F = builder.#1L", memberName, memberSymbol)
9692
}
9793
}
9894

@@ -316,10 +312,16 @@ class StructureGenerator(
316312
val exceptionBaseClass = ExceptionBaseClassGenerator.baseExceptionSymbol(ctx.settings)
317313
writer.addImport(exceptionBaseClass)
318314

315+
val superParam = shape.members().find {
316+
symbolProvider.toMemberName(it) == "message"
317+
}?.let { "builder.message" } ?: ""
318+
319319
writer.openBlock(
320-
"#L class #T private constructor(builder: Builder) : ${exceptionBaseClass.name}() {",
320+
"#L class #T private constructor(builder: Builder) : #L(#L) {",
321321
ctx.settings.api.visibility,
322322
symbol,
323+
exceptionBaseClass.name,
324+
superParam,
323325
)
324326
.write("")
325327
.call { renderImmutableProperties() }

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ExceptionGeneratorTest.kt

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,53 @@ class ExceptionGeneratorTest {
8686
clientErrorTestContents.shouldContainWithDiff(expectedClientClassDecl)
8787

8888
val expectedServerClassDecl = """
89-
class InternalServerException private constructor(builder: Builder) : TestException() {
89+
class InternalServerException private constructor(builder: Builder) : TestException(builder.message) {
9090
""".trimIndent()
9191

9292
serverErrorTestContents.shouldContainWithDiff(expectedServerClassDecl)
9393
}
9494

95+
@Test
96+
fun `throwable message special cases`() {
97+
val model = """
98+
@httpError(500)
99+
@error("server")
100+
structure CapitalizedMessageMemberException {
101+
Message: String
102+
}
103+
104+
@httpError(500)
105+
@error("server")
106+
structure NoMessageMemberException { }
107+
"""
108+
.prependNamespaceAndService(version = "2.0")
109+
.toSmithyModel()
110+
111+
val expectedCapMessageClassDecl = """
112+
class CapitalizedMessageMemberException private constructor(builder: Builder) : TestException(builder.message) {
113+
""".trimIndent()
114+
115+
val expectedNoMessageClassDecl = """
116+
class NoMessageMemberException private constructor(builder: Builder) : TestException() {
117+
""".trimIndent()
118+
119+
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model)
120+
121+
listOf(
122+
"com.test#CapitalizedMessageMemberException" to expectedCapMessageClassDecl,
123+
"com.test#NoMessageMemberException" to expectedNoMessageClassDecl,
124+
).forEach { (shapeId, expected) ->
125+
126+
val writer = KotlinWriter(TestModelDefault.NAMESPACE)
127+
val struct = model.expectShape<StructureShape>(shapeId)
128+
val renderingCtx = RenderingContext(writer, struct, model, provider, model.defaultSettings())
129+
StructureGenerator(renderingCtx).render()
130+
131+
val generated = writer.toString()
132+
generated.shouldContainOnlyOnceWithDiff(expected)
133+
}
134+
}
135+
95136
@Test
96137
fun `error generator sets error type correctly`() {
97138
val expectedClientClassDecl = "sdkErrorMetadata.attributes[ServiceErrorMetadata.ErrorType] = ErrorType.Client"
@@ -109,16 +150,6 @@ class ExceptionGeneratorTest {
109150
serverErrorTestContents.assertBalancedBracesAndParens()
110151
}
111152

112-
@Test
113-
fun `error generator renders override with message member`() {
114-
val expected = """
115-
override val message: kotlin.String = requireNotNull(builder.message) { "A non-null value must be provided for message" }
116-
"""
117-
118-
serverErrorTestContents.shouldContainWithDiff(expected)
119-
clientErrorTestContents.shouldNotContain(expected)
120-
}
121-
122153
@Test
123154
fun `error generator renders isRetryable`() {
124155
val expected = "sdkErrorMetadata.attributes[ErrorMetadata.Retryable] = true"

runtime/runtime-core/api/runtime-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class aws/smithy/kotlin/runtime/ServiceException : aws/smithy/kotlin/runt
5858
public fun <init> (Ljava/lang/String;)V
5959
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
6060
public fun <init> (Ljava/lang/Throwable;)V
61+
public fun getMessage ()Ljava/lang/String;
6162
public synthetic fun getSdkErrorMetadata ()Laws/smithy/kotlin/runtime/ErrorMetadata;
6263
public fun getSdkErrorMetadata ()Laws/smithy/kotlin/runtime/ServiceErrorMetadata;
6364
}

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/Exceptions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,8 @@ public open class ServiceException : SdkBaseException {
149149

150150
public constructor(cause: Throwable?) : super(cause)
151151

152+
override val message: String?
153+
get() = super.message ?: sdkErrorMetadata.errorMessage
154+
152155
override val sdkErrorMetadata: ServiceErrorMetadata = ServiceErrorMetadata()
153156
}

0 commit comments

Comments
 (0)