Skip to content

Commit c187e3d

Browse files
authored
Render full shape IDs in server error responses (#1982)
RestJson1 implementations can denote errors in responses in several ways. New server-side protocol implementations MUST use a header field named `X-Amzn-Errortype`. Note that the spec says that implementations SHOULD strip the error shape ID's namespace. However, our server implementation renders the full shape ID (including namespace), since some existing clients rely on it to deserialize the error shape and fail if only the shape name is present. This is compliant with the spec, see smithy-lang/smithy#1493. See smithy-lang/smithy#1494 too.
1 parent 1f405f6 commit c187e3d

File tree

5 files changed

+55
-5
lines changed

5 files changed

+55
-5
lines changed

CHANGELOG.next.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,29 @@ message = "`Endpoint::immutable` now takes `impl AsRef<str>` instead of `Uri`. F
431431
references = ["smithy-rs#1984", "smithy-rs#1496"]
432432
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
433433
author = "jdisanti"
434+
435+
[[smithy-rs]]
436+
message = """
437+
[RestJson1](https://awslabs.github.io/smithy/2.0/aws/protocols/aws-restjson1-protocol.html#operation-error-serialization) server SDKs now serialize the [full shape ID](https://smithy.io/2.0/spec/model.html#shape-id) (including namespace) in operation error responses.
438+
439+
Example server error response before:
440+
441+
```
442+
HTTP/1.1 400 Bad Request
443+
content-type: application/json
444+
x-amzn-errortype: InvalidRequestException
445+
...
446+
```
447+
448+
Example server error response now:
449+
450+
```
451+
HTTP/1.1 400 Bad Request
452+
content-type: application/json
453+
x-amzn-errortype: com.example.service#InvalidRequestException
454+
...
455+
```
456+
"""
457+
references = ["smithy-rs#1982"]
458+
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" }
459+
author = "david-perez"

codegen-core/common-test-models/rest-json-extras.smithy

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,16 @@ service RestJsonExtras {
8181
appliesTo: "client",
8282
},
8383
{
84-
documentation: "Upper case error modeled lower case",
84+
documentation: """
85+
Upper case error modeled lower case.
86+
Servers render the full shape ID (including namespace), since some
87+
existing clients rely on it to deserialize the error shape and fail
88+
if only the shape name is present.""",
8589
id: "ServiceLevelErrorServer",
8690
protocol: "aws.protocols#restJson1",
8791
code: 500,
8892
body: "{}",
89-
headers: { "X-Amzn-Errortype": "ExtraError" },
93+
headers: { "X-Amzn-Errortype": "aws.protocoltests.restjson#ExtraError" },
9094
params: {},
9195
appliesTo: "server",
9296
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,15 @@ open class RestJson(val codegenContext: CodegenContext) : Protocol {
8383
/**
8484
* RestJson1 implementations can denote errors in responses in several ways.
8585
* New server-side protocol implementations MUST use a header field named `X-Amzn-Errortype`.
86+
*
87+
* Note that the spec says that implementations SHOULD strip the error shape ID's namespace.
88+
* However, our server implementation renders the full shape ID (including namespace), since some
89+
* existing clients rely on it to deserialize the error shape and fail if only the shape name is present.
90+
* This is compliant with the spec, see https://github.com/awslabs/smithy/pull/1493.
91+
* See https://github.com/awslabs/smithy/issues/1494 too.
8692
*/
8793
override fun additionalErrorResponseHeaders(errorShape: StructureShape): List<Pair<String, String>> =
88-
listOf("x-amzn-errortype" to errorShape.id.name)
94+
listOf("x-amzn-errortype" to errorShape.id.toString())
8995

9096
override fun structuredDataParser(operationShape: OperationShape): StructuredDataParserGenerator {
9197
fun builderSymbol(shape: StructureShape): Symbol =

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,13 @@ class ServerProtocolTestGenerator(
11701170
): HttpRequestTestCase =
11711171
testCase.toBuilder().putHeader("x-amz-target", "JsonProtocol.${operationShape.id.name}").build()
11721172

1173+
private fun fixRestJsonInvalidGreetingError(testCase: HttpResponseTestCase): HttpResponseTestCase =
1174+
testCase.toBuilder().putHeader("X-Amzn-Errortype", "aws.protocoltests.restjson#InvalidGreeting").build()
1175+
private fun fixRestJsonEmptyComplexErrorWithNoMessage(testCase: HttpResponseTestCase): HttpResponseTestCase =
1176+
testCase.toBuilder().putHeader("X-Amzn-Errortype", "aws.protocoltests.restjson#ComplexError").build()
1177+
private fun fixRestJsonComplexErrorWithNoMessage(testCase: HttpResponseTestCase): HttpResponseTestCase =
1178+
testCase.toBuilder().putHeader("X-Amzn-Errortype", "aws.protocoltests.restjson#ComplexError").build()
1179+
11731180
// These are tests whose definitions in the `awslabs/smithy` repository are wrong.
11741181
// This is because they have not been written from a server perspective, and as such the expected `params` field is incomplete.
11751182
// TODO(https://github.com/awslabs/smithy-rs/issues/1288): Contribute a PR to fix them upstream.
@@ -1246,6 +1253,11 @@ class ServerProtocolTestGenerator(
12461253
)
12471254

12481255
private val BrokenResponseTests: Map<Pair<String, String>, KFunction1<HttpResponseTestCase, HttpResponseTestCase>> =
1249-
mapOf()
1256+
// TODO(https://github.com/awslabs/smithy/issues/1494)
1257+
mapOf(
1258+
Pair(RestJson, "RestJsonInvalidGreetingError") to ::fixRestJsonInvalidGreetingError,
1259+
Pair(RestJson, "RestJsonEmptyComplexErrorWithNoMessage") to ::fixRestJsonEmptyComplexErrorWithNoMessage,
1260+
Pair(RestJson, "RestJsonComplexErrorWithNoMessage") to ::fixRestJsonComplexErrorWithNoMessage,
1261+
)
12501262
}
12511263
}

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
3333
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
3434
import software.amazon.smithy.rust.codegen.core.rustlang.asType
3535
import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlock
36+
import software.amazon.smithy.rust.codegen.core.rustlang.escape
37+
import software.amazon.smithy.rust.codegen.core.rustlang.render
3638
import software.amazon.smithy.rust.codegen.core.rustlang.rust
3739
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
3840
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
@@ -636,7 +638,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
636638
builder = #{header_util}::set_response_header_if_absent(
637639
builder,
638640
http::header::HeaderName::from_static("$headerName"),
639-
"$headerValue"
641+
"${escape(headerValue)}"
640642
);
641643
""",
642644
*codegenScope,

0 commit comments

Comments
 (0)