Skip to content

Commit 4369a6d

Browse files
authored
fix(codegen): http error registration (#118)
1 parent 7a79e13 commit 4369a6d

File tree

4 files changed

+225
-7
lines changed

4 files changed

+225
-7
lines changed

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/RestJsonErrorMiddleware.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import aws.sdk.kotlin.codegen.AwsKotlinDependency
99
import aws.sdk.kotlin.codegen.protocols.middleware.ModeledExceptionsMiddleware
1010
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
1111
import software.amazon.smithy.kotlin.codegen.core.addImport
12+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
13+
import software.amazon.smithy.kotlin.codegen.model.getTrait
1214
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver
1315
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
16+
import software.amazon.smithy.model.traits.ErrorTrait
1417
import software.amazon.smithy.model.traits.HttpErrorTrait
1518

1619
class RestJsonErrorMiddleware(
@@ -31,10 +34,11 @@ class RestJsonErrorMiddleware(
3134
val code = errShape.id.name
3235
val symbol = ctx.symbolProvider.toSymbol(errShape)
3336
val deserializerName = "${symbol.name}Deserializer"
34-
val httpStatusCode: Int? = errShape.getTrait(HttpErrorTrait::class.java).map { it.code }.orElse(null)
35-
if (httpStatusCode != null) {
36-
writer.write("register(code = #S, deserializer = $deserializerName(), httpStatusCode = $httpStatusCode)", code)
37-
}
37+
// If model specifies error code use it otherwise default to 400 (client) or 500 (server)
38+
// See https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httperror-trait
39+
val defaultCode = if (errShape.expectTrait<ErrorTrait>().isClientError) 400 else 500
40+
val httpStatusCode = errShape.getTrait<HttpErrorTrait>()?.code ?: defaultCode
41+
writer.write("register(code = #S, deserializer = $deserializerName(), httpStatusCode = $httpStatusCode)", code)
3842
}
3943
}
4044
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/xml/RestXmlErrorMiddleware.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import aws.sdk.kotlin.codegen.AwsKotlinDependency
99
import aws.sdk.kotlin.codegen.protocols.middleware.ModeledExceptionsMiddleware
1010
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
1111
import software.amazon.smithy.kotlin.codegen.core.addImport
12+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
1213
import software.amazon.smithy.kotlin.codegen.model.getTrait
1314
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver
1415
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
16+
import software.amazon.smithy.model.traits.ErrorTrait
1517
import software.amazon.smithy.model.traits.HttpErrorTrait
1618

1719
class RestXmlErrorMiddleware(
@@ -32,9 +34,11 @@ class RestXmlErrorMiddleware(
3234
val code = errShape.id.name
3335
val symbol = ctx.symbolProvider.toSymbol(errShape)
3436
val deserializerName = "${symbol.name}Deserializer"
35-
errShape.getTrait<HttpErrorTrait>()?.code?.let { httpStatusCode ->
36-
writer.write("register(code = #S, deserializer = $deserializerName(), httpStatusCode = $httpStatusCode)", code)
37-
}
37+
// If model specifies error code use it otherwise default to 400 (client) or 500 (server)
38+
// See https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httperror-trait
39+
val defaultCode = if (errShape.expectTrait<ErrorTrait>().isClientError) 400 else 500
40+
val httpStatusCode = errShape.getTrait<HttpErrorTrait>()?.code ?: defaultCode
41+
writer.write("register(code = #S, deserializer = $deserializerName(), httpStatusCode = $httpStatusCode)", code)
3842
}
3943
}
4044
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package aws.sdk.kotlin.codegen.protocols.json
2+
3+
import org.junit.jupiter.api.Test
4+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpTraitResolver
5+
import software.amazon.smithy.kotlin.codegen.test.*
6+
7+
class AwsJsonErrorMiddlewareTest {
8+
9+
@Test
10+
fun `it registers error deserializers for client error shapes`() {
11+
val model = """
12+
@http(method: "PUT", uri: "/test", code: 200)
13+
operation TestOperation {
14+
output: TestResponse,
15+
errors: [TestError]
16+
}
17+
18+
structure TestResponse {
19+
someVal: Integer
20+
}
21+
22+
@error("client")
23+
structure TestError {
24+
someCode: Integer,
25+
someMessage: String
26+
}
27+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
28+
.toSmithyModel()
29+
30+
val (ctx, _, _) = model.newTestContext()
31+
32+
val unit = RestJsonErrorMiddleware(ctx, AwsJsonHttpBindingResolver(ctx, "application/x-amz-json-1.0"))
33+
34+
val actual = generateCode { writer ->
35+
unit.renderRegisterErrors(writer)
36+
}
37+
38+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 400)")
39+
}
40+
41+
@Test
42+
fun `it registers error deserializers for server error shapes`() {
43+
val model = """
44+
@http(method: "PUT", uri: "/test", code: 200)
45+
operation TestOperation {
46+
output: TestResponse,
47+
errors: [TestError]
48+
}
49+
50+
structure TestResponse {
51+
someVal: Integer
52+
}
53+
54+
@error("server")
55+
structure TestError {
56+
someCode: Integer,
57+
someMessage: String
58+
}
59+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
60+
.toSmithyModel()
61+
62+
val (ctx, _, _) = model.newTestContext()
63+
64+
val unit = RestJsonErrorMiddleware(ctx, AwsJsonHttpBindingResolver(ctx, "application/x-amz-json-1.0"))
65+
66+
val actual = generateCode { writer ->
67+
unit.renderRegisterErrors(writer)
68+
}
69+
70+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 500)")
71+
}
72+
73+
@Test
74+
fun `it registers error deserializer with custom code for error shapes`() {
75+
val model = """
76+
@http(method: "PUT", uri: "/test", code: 200)
77+
operation TestOperation {
78+
output: TestResponse,
79+
errors: [TestError]
80+
}
81+
82+
structure TestResponse {
83+
someVal: Integer
84+
}
85+
86+
@error("client")
87+
@httpError(404)
88+
structure TestError {
89+
someCode: Integer,
90+
someMessage: String
91+
}
92+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
93+
.toSmithyModel()
94+
95+
val (ctx, _, _) = model.newTestContext()
96+
97+
val unit = RestJsonErrorMiddleware(ctx, HttpTraitResolver(ctx, "application/xml"))
98+
99+
val actual = generateCode { writer ->
100+
unit.renderRegisterErrors(writer)
101+
}
102+
103+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 404)")
104+
}
105+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package aws.sdk.kotlin.codegen.protocols.xml
2+
3+
import org.junit.jupiter.api.Test
4+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpTraitResolver
5+
import software.amazon.smithy.kotlin.codegen.test.*
6+
7+
class RestXmlErrorMiddlewareTest {
8+
9+
@Test
10+
fun `it registers error deserializers for client error shapes`() {
11+
val model = """
12+
@http(method: "PUT", uri: "/test", code: 200)
13+
operation TestOperation {
14+
output: TestResponse,
15+
errors: [TestError]
16+
}
17+
18+
structure TestResponse {
19+
someVal: Integer
20+
}
21+
22+
@error("client")
23+
structure TestError {
24+
someCode: Integer,
25+
someMessage: String
26+
}
27+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
28+
.toSmithyModel()
29+
30+
val (ctx, _, _) = model.newTestContext()
31+
32+
val unit = RestXmlErrorMiddleware(ctx, HttpTraitResolver(ctx, "application/xml"))
33+
34+
val actual = generateCode { writer ->
35+
unit.renderRegisterErrors(writer)
36+
}
37+
38+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 400)")
39+
}
40+
41+
@Test
42+
fun `it registers error deserializers for server error shapes`() {
43+
val model = """
44+
@http(method: "PUT", uri: "/test", code: 200)
45+
operation TestOperation {
46+
output: TestResponse,
47+
errors: [TestError]
48+
}
49+
50+
structure TestResponse {
51+
someVal: Integer
52+
}
53+
54+
@error("server")
55+
structure TestError {
56+
someCode: Integer,
57+
someMessage: String
58+
}
59+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
60+
.toSmithyModel()
61+
62+
val (ctx, _, _) = model.newTestContext()
63+
64+
val unit = RestXmlErrorMiddleware(ctx, HttpTraitResolver(ctx, "application/xml"))
65+
66+
val actual = generateCode { writer ->
67+
unit.renderRegisterErrors(writer)
68+
}
69+
70+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 500)")
71+
}
72+
73+
@Test
74+
fun `it registers error deserializer with custom code for error shapes`() {
75+
val model = """
76+
@http(method: "PUT", uri: "/test", code: 200)
77+
operation TestOperation {
78+
output: TestResponse,
79+
errors: [TestError]
80+
}
81+
82+
structure TestResponse {
83+
someVal: Integer
84+
}
85+
86+
@error("client")
87+
@httpError(404)
88+
structure TestError {
89+
someCode: Integer,
90+
someMessage: String
91+
}
92+
""".prependNamespaceAndService(operations = listOf("TestOperation"))
93+
.toSmithyModel()
94+
95+
val (ctx, _, _) = model.newTestContext()
96+
97+
val unit = RestXmlErrorMiddleware(ctx, HttpTraitResolver(ctx, "application/xml"))
98+
99+
val actual = generateCode { writer ->
100+
unit.renderRegisterErrors(writer)
101+
}
102+
103+
actual.shouldContainOnlyOnceWithDiff("register(code = \"TestError\", deserializer = TestErrorDeserializer(), httpStatusCode = 404)")
104+
}
105+
}

0 commit comments

Comments
 (0)