From 8916d9dad3bd3a5eb8d2c5612df0471a90a2832d Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Mon, 20 Feb 2023 21:47:39 -0600 Subject: [PATCH 1/8] Add support for the awsQueryCompatible trait This commit adds support for the awsQueryCompatible trait. This allows services already supporting custom error codes through the AWS Query protocol with the awsQueryError trait to continue supporting them after the services switch to the AWS JSON 1.0 protocol. --- .../smithy/protocols/ClientProtocolLoader.kt | 14 +- .../protocols/AwsQueryCompatibleTest.kt | 147 ++++++++++++++++++ .../codegen/core/rustlang/CargoDependency.kt | 7 + .../rust/codegen/core/smithy/RuntimeType.kt | 1 + .../smithy/protocols/AwsQueryCompatible.kt | 94 +++++++++++ .../src/aws_query_compatible_errors.rs | 104 +++++++++++++ rust-runtime/inlineable/src/lib.rs | 2 + 7 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt create mode 100644 codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt create mode 100644 rust-runtime/inlineable/src/aws_query_compatible_errors.rs diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt index ecfea77ff5..a194dc98a2 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt @@ -7,16 +7,19 @@ package software.amazon.smithy.rust.codegen.client.smithy.protocols import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait +import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait import software.amazon.smithy.aws.traits.protocols.RestJson1Trait import software.amazon.smithy.aws.traits.protocols.RestXmlTrait +import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsJson import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsJsonVersion +import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsQueryCompatible import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsQueryProtocol import software.amazon.smithy.rust.codegen.core.smithy.protocols.Ec2QueryProtocol import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol @@ -25,6 +28,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolLoader import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestXml +import software.amazon.smithy.rust.codegen.core.util.hasTrait class ClientProtocolLoader(supportedProtocols: ProtocolMap) : ProtocolLoader(supportedProtocols) { @@ -57,12 +61,20 @@ private val CLIENT_PROTOCOL_SUPPORT = ProtocolSupport( private class ClientAwsJsonFactory(private val version: AwsJsonVersion) : ProtocolGeneratorFactory { - override fun protocol(codegenContext: ClientCodegenContext): Protocol = AwsJson(codegenContext, version) + override fun protocol(codegenContext: ClientCodegenContext): Protocol = + if (compatibleWithAwsQuery(codegenContext.serviceShape, version)) { + AwsQueryCompatible(codegenContext, AwsJson(codegenContext, version)) + } else { + AwsJson(codegenContext, version) + } override fun buildProtocolGenerator(codegenContext: ClientCodegenContext): HttpBoundProtocolGenerator = HttpBoundProtocolGenerator(codegenContext, protocol(codegenContext)) override fun support(): ProtocolSupport = CLIENT_PROTOCOL_SUPPORT + + private fun compatibleWithAwsQuery(serviceShape: ServiceShape, version: AwsJsonVersion) = + serviceShape.hasTrait() && version == AwsJsonVersion.Json10 } private class ClientAwsQueryFactory : ProtocolGeneratorFactory { diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt new file mode 100644 index 0000000000..fbf3a59297 --- /dev/null +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -0,0 +1,147 @@ +package software.amazon.smithy.rust.codegen.client.smithy.protocols + +import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.integrationTest + +class AwsQueryCompatibleTest { + @Test + fun `aws-query-compatible json with aws query error should allow for retrieving error code and type from custom header`() { + val model = """ + namespace test + use aws.protocols#awsJson1_0 + use aws.protocols#awsQueryCompatible + use aws.protocols#awsQueryError + + @awsQueryCompatible + @awsJson1_0 + service TestService { + version: "2023-02-20", + operations: [SomeOperation] + } + + operation SomeOperation { + input: SomeOperationInputOutput, + output: SomeOperationInputOutput, + errors: [InvalidThingException], + } + + structure SomeOperationInputOutput { + a: String, + b: Integer + } + + @awsQueryError( + code: "InvalidThing", + httpResponseCode: 400, + ) + @error("client") + structure InvalidThingException { + message: String + } + """.asSmithyModel() + + clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + val moduleName = clientCodegenContext.moduleUseName() + rustCrate.integrationTest("should_parse_code_and_type_fields") { + rust( + """ + ##[test] + fn should_parse_code_and_type_fields() { + use aws_smithy_http::response::ParseStrictResponse; + + let response = http::Response::builder() + .header( + "x-amzn-query-error", + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), + ) + .status(400) + .body( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"##, + ) + .unwrap(); + let some_operation = $moduleName::operation::SomeOperation::new(); + let error = some_operation + .parse(&response.map(bytes::Bytes::from)) + .err() + .unwrap(); + assert_eq!( + error.meta().code(), + Some("AWS.SimpleQueueService.NonExistentQueue"), + ); + assert_eq!(error.meta().extra("type"), Some("Sender")); + } + """, + ) + } + } + } + + @Test + fun `aws-query-compatible json without aws query error should allow for retrieving error code from payload`() { + val model = """ + namespace test + use aws.protocols#awsJson1_0 + use aws.protocols#awsQueryCompatible + + @awsQueryCompatible + @awsJson1_0 + service TestService { + version: "2023-02-20", + operations: [SomeOperation] + } + + operation SomeOperation { + input: SomeOperationInputOutput, + output: SomeOperationInputOutput, + errors: [InvalidThingException], + } + + structure SomeOperationInputOutput { + a: String, + b: Integer + } + + @error("client") + structure InvalidThingException { + message: String + } + """.asSmithyModel() + + clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + val moduleName = clientCodegenContext.moduleUseName() + rustCrate.integrationTest("should_parse_code_from_payload") { + rust( + """ + ##[test] + fn should_parse_code_from_payload() { + use aws_smithy_http::response::ParseStrictResponse; + + let response = http::Response::builder() + .status(400) + .body( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"##, + ) + .unwrap(); + let some_operation = $moduleName::operation::SomeOperation::new(); + let error = some_operation + .parse(&response.map(bytes::Bytes::from)) + .err() + .unwrap(); + assert_eq!(error.meta().code(), Some("QueueDoesNotExist")); + assert_eq!(error.meta().extra("type"), None); + } + """, + ) + } + } + } +} diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index 8424219a0f..167fd2997f 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -95,6 +95,13 @@ class InlineDependency( CargoDependency.Http, ) + fun awsQueryCompatibleErrors(runtimeConfig: RuntimeConfig) = + forInlineableRustFile( + "aws_query_compatible_errors", + CargoDependency.smithyJson(runtimeConfig), + CargoDependency.Http, + ) + fun idempotencyToken() = forInlineableRustFile("idempotency_token", CargoDependency.FastRand) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt index 5dcb704c83..df1b2cc791 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt @@ -279,6 +279,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun provideErrorMetadataTrait(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("error::metadata::ProvideErrorMetadata") fun unhandledError(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("error::Unhandled") fun jsonErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.jsonErrors(runtimeConfig)) + fun awsQueryCompatibleErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.awsQueryCompatibleErrors(runtimeConfig)) fun labelFormat(runtimeConfig: RuntimeConfig, func: String) = smithyHttp(runtimeConfig).resolve("label::$func") fun operation(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation::Operation") fun operationModule(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation") diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt new file mode 100644 index 0000000000..041b30b1cd --- /dev/null +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt @@ -0,0 +1,94 @@ +package software.amazon.smithy.rust.codegen.core.smithy.protocols + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ToShapeId +import software.amazon.smithy.model.traits.HttpTrait +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.protocols.parse.StructuredDataParserGenerator +import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.StructuredDataSerializerGenerator + +class AwsQueryCompatibleHttpBindingResolver( + private val awsQueryBindingResolver: AwsQueryBindingResolver, + private val awsJsonHttpBindingResolver: AwsJsonHttpBindingResolver, +) : HttpBindingResolver { + override fun httpTrait(operationShape: OperationShape): HttpTrait = + awsJsonHttpBindingResolver.httpTrait(operationShape) + + override fun requestBindings(operationShape: OperationShape): List = + awsJsonHttpBindingResolver.requestBindings(operationShape) + + override fun responseBindings(operationShape: OperationShape): List = + awsJsonHttpBindingResolver.responseBindings(operationShape) + + override fun errorResponseBindings(errorShape: ToShapeId): List = + awsJsonHttpBindingResolver.errorResponseBindings(errorShape) + + override fun errorCode(errorShape: ToShapeId): String = + awsQueryBindingResolver.errorCode(errorShape) + + override fun requestContentType(operationShape: OperationShape): String = + awsJsonHttpBindingResolver.requestContentType(operationShape) + + override fun responseContentType(operationShape: OperationShape): String = + awsJsonHttpBindingResolver.requestContentType(operationShape) +} + +class AwsQueryCompatible( + val codegenContext: CodegenContext, + private val awsJson: AwsJson, +) : Protocol { + private val runtimeConfig = codegenContext.runtimeConfig + private val errorScope = arrayOf( + "Bytes" to RuntimeType.Bytes, + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), + "HeaderMap" to RuntimeType.Http.resolve("HeaderMap"), + "HeaderValue" to RuntimeType.Http.resolve("HeaderValue"), + "JsonError" to CargoDependency.smithyJson(runtimeConfig).toType() + .resolve("deserialize::error::DeserializeError"), + "Response" to RuntimeType.Http.resolve("Response"), + "json_errors" to RuntimeType.jsonErrors(runtimeConfig), + "aws_query_compatible_errors" to RuntimeType.awsQueryCompatibleErrors(runtimeConfig), + ) + private val jsonDeserModule = RustModule.private("json_deser") + + override val httpBindingResolver: HttpBindingResolver = + AwsQueryCompatibleHttpBindingResolver( + AwsQueryBindingResolver(codegenContext.model), + AwsJsonHttpBindingResolver(codegenContext.model, awsJson.version), + ) + + override val defaultTimestampFormat = awsJson.defaultTimestampFormat + + override fun structuredDataParser(operationShape: OperationShape): StructuredDataParserGenerator = + awsJson.structuredDataParser(operationShape) + + override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator = + awsJson.structuredDataSerializer(operationShape) + + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", jsonDeserModule) { + rustTemplate( + """ + pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> { + let mut builder = + #{json_errors}::parse_error_metadata(response.body(), response.headers())?; + if let Some((error_code, error_type)) = + #{aws_query_compatible_errors}::parse_aws_query_compatible_error(response.headers()) + { + builder = builder.code(error_code); + builder = builder.custom("type", error_type); + } + Ok(builder) + } + """, + *errorScope, + ) + } + + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + awsJson.parseEventStreamErrorMetadata(operationShape) +} diff --git a/rust-runtime/inlineable/src/aws_query_compatible_errors.rs b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs new file mode 100644 index 0000000000..90679b7ded --- /dev/null +++ b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs @@ -0,0 +1,104 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use http::header::ToStrError; +use http::{HeaderMap, HeaderValue}; + +const X_AMZN_QUERY_ERROR: &str = "x-amzn-query-error"; +const QUERY_COMPATIBLE_ERRORCODE_DELIMITER: char = ';'; + +fn aws_query_compatible_error_from_header( + headers: &HeaderMap, +) -> Result, ToStrError> { + headers + .get(X_AMZN_QUERY_ERROR) + .map(|v| v.to_str()) + .transpose() +} + +/// Obtains custom error code and error type from the given `headers`. +/// +/// Looks up a value for the `X_AMZN_QUERY_ERROR` header and if found, the value should be in the +/// form of `;`. The function then splits it into two parts and returns +/// a (error code, error type) as a tuple. +/// +/// Any execution path besides the above happy path will yield a `None`. +pub fn parse_aws_query_compatible_error(headers: &HeaderMap) -> Option<(&str, &str)> { + let header_value = match aws_query_compatible_error_from_header(headers) { + Ok(error) => error?, + _ => return None, + }; + + match header_value.find(QUERY_COMPATIBLE_ERRORCODE_DELIMITER) { + Some(idx) => Some((&header_value[..idx], &header_value[idx + 1..])), + None => None, + } +} + +#[cfg(test)] +mod test { + use crate::aws_query_compatible_errors::{ + aws_query_compatible_error_from_header, parse_aws_query_compatible_error, + X_AMZN_QUERY_ERROR, + }; + + #[test] + fn aws_query_compatible_error_from_header_should_provide_value_for_custom_header() { + let mut response: http::Response<()> = http::Response::default(); + response.headers_mut().insert( + X_AMZN_QUERY_ERROR, + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), + ); + + let actual = aws_query_compatible_error_from_header(response.headers()).unwrap(); + + assert_eq!( + actual, + Some("AWS.SimpleQueueService.NonExistentQueue;Sender") + ); + } + + #[test] + fn parse_aws_query_compatible_error_should_parse_code_and_type_fields() { + let mut response: http::Response<()> = http::Response::default(); + response.headers_mut().insert( + X_AMZN_QUERY_ERROR, + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), + ); + + let actual = parse_aws_query_compatible_error(response.headers()); + + assert_eq!( + actual, + Some(("AWS.SimpleQueueService.NonExistentQueue", "Sender")) + ); + } + + #[test] + fn parse_aws_query_compatible_error_should_return_none_when_header_value_has_no_delimiter() { + let mut response: http::Response<()> = http::Response::default(); + response.headers_mut().insert( + X_AMZN_QUERY_ERROR, + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue"), + ); + + let actual = parse_aws_query_compatible_error(response.headers()); + + assert_eq!(actual, None,); + } + + #[test] + fn parse_aws_query_compatible_error_should_return_none_when_there_is_no_target_header() { + let mut response: http::Response<()> = http::Response::default(); + response.headers_mut().insert( + "x-amzn-requestid", + http::HeaderValue::from_static("a918fbf2-457a-4fe1-99ba-5685ce220fc1"), + ); + + let actual = parse_aws_query_compatible_error(response.headers()); + + assert_eq!(actual, None,); + } +} diff --git a/rust-runtime/inlineable/src/lib.rs b/rust-runtime/inlineable/src/lib.rs index 41af358919..e53b81db7d 100644 --- a/rust-runtime/inlineable/src/lib.rs +++ b/rust-runtime/inlineable/src/lib.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#[allow(dead_code)] +mod aws_query_compatible_errors; #[allow(unused)] mod constrained; #[allow(dead_code)] From 6e41d00bbd9856729a8ce320911f0e3b100dafab Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Tue, 21 Feb 2023 10:34:31 -0600 Subject: [PATCH 2/8] Add copyright header --- .../client/smithy/protocols/AwsQueryCompatibleTest.kt | 5 +++++ .../rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt index fbf3a59297..cad6d913b8 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.rust.codegen.client.smithy.protocols import org.junit.jupiter.api.Test diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt index 041b30b1cd..05578eba8b 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.rust.codegen.core.smithy.protocols import software.amazon.smithy.model.shapes.OperationShape From 2ae325ed12e801fead9643b337636e2e86fd019e Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Tue, 21 Feb 2023 10:35:04 -0600 Subject: [PATCH 3/8] Fix clippy warning for clippy::manual-map --- rust-runtime/inlineable/src/aws_query_compatible_errors.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rust-runtime/inlineable/src/aws_query_compatible_errors.rs b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs index 90679b7ded..361f5db578 100644 --- a/rust-runtime/inlineable/src/aws_query_compatible_errors.rs +++ b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs @@ -31,10 +31,9 @@ pub fn parse_aws_query_compatible_error(headers: &HeaderMap) -> Opt _ => return None, }; - match header_value.find(QUERY_COMPATIBLE_ERRORCODE_DELIMITER) { - Some(idx) => Some((&header_value[..idx], &header_value[idx + 1..])), - None => None, - } + header_value + .find(QUERY_COMPATIBLE_ERRORCODE_DELIMITER) + .map(|idx| (&header_value[..idx], &header_value[idx + 1..])) } #[cfg(test)] From fdd98b1403e1d345b58025be9e98ee96e026074e Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Tue, 21 Feb 2023 20:41:40 -0600 Subject: [PATCH 4/8] Update CHANGELOG.next.toml --- CHANGELOG.next.toml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index d776a86f6a..db26d4e8a1 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -235,3 +235,39 @@ message = "Support for constraint traits on member shapes (constraint trait prec references = ["smithy-rs#1969"] meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "server" } author = "drganjoo" + +[[aws-sdk-rust]] +message = """ +Add support for the `awsQueryCompatible` trait. This allows services to continue supporting a custom error code (via the `awsQueryError` trait) when the services migrate their protocol from `awsQuery` to `awsJson1_0` annotated with `awsQueryCompatible`. After the migration, services will include an additional header `x-amzn-query-error` in their responses whose value is in the form of `;`. An example response looks something like +``` +HTTP/1.1 400 +x-amzn-query-error: AWS.SimpleQueueService.NonExistentQueue;Sender +Date: Wed, 08 Sep 2021 23:46:52 GMT +Content-Type: application/x-amz-json-1.0 +Content-Length: 163 + +{ + "__type": "com.amazonaws.sqs#QueueDoesNotExist", + "message": "some user-visible message" +} +``` +`` is `AWS.SimpleQueueService.NonExistentQueue` and `` is `Sender`. + +If an operation results in an error that causes a service to send back the response above, you can access `` and `` as follows: +```rust +match client.some_operation().send().await { + Ok(_) => { /* success */ } + Err(sdk_err) => { + let err = sdk_err.into_service_error(); + assert_eq!( + error.meta().code(), + Some("AWS.SimpleQueueService.NonExistentQueue"), + ); + assert_eq!(error.meta().extra("type"), Some("Sender")); + } +} +``` +""" +references = ["smithy-rs#2398"] +meta = { "breaking" = false, "tada" = true, "bug" = false } +author = "ysaito1001" From e0d0f467a8bb4a24ed89a5bf4a404222024eab40 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Wed, 22 Feb 2023 12:11:48 -0600 Subject: [PATCH 5/8] Update CHANGELOG.next.toml --- CHANGELOG.next.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index db26d4e8a1..189832088e 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -236,7 +236,7 @@ references = ["smithy-rs#1969"] meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "server" } author = "drganjoo" -[[aws-sdk-rust]] +[[smithy-rs]] message = """ Add support for the `awsQueryCompatible` trait. This allows services to continue supporting a custom error code (via the `awsQueryError` trait) when the services migrate their protocol from `awsQuery` to `awsJson1_0` annotated with `awsQueryCompatible`. After the migration, services will include an additional header `x-amzn-query-error` in their responses whose value is in the form of `;`. An example response looks something like ``` @@ -271,3 +271,9 @@ match client.some_operation().send().await { references = ["smithy-rs#2398"] meta = { "breaking" = false, "tada" = true, "bug" = false } author = "ysaito1001" + +[[aws-sdk-rust]] +message = "Add support for the `awsQueryCompatible` trait, allowing the Rust SDK to migrate from the AWS Query protocol to the AWS JSON 1.0 protocol to continue supporting custom error codes." +references = ["smithy-rs#2398"] +meta = { "breaking" = false, "tada" = true, "bug" = false } +author = "ysaito1001" From a8386bddd4c6ae4320e4b8c079aba3b0d456235b Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Wed, 22 Feb 2023 14:22:13 -0600 Subject: [PATCH 6/8] Update CHANGELOG.next.toml --- CHANGELOG.next.toml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 189832088e..8e2684683b 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -238,7 +238,11 @@ author = "drganjoo" [[smithy-rs]] message = """ -Add support for the `awsQueryCompatible` trait. This allows services to continue supporting a custom error code (via the `awsQueryError` trait) when the services migrate their protocol from `awsQuery` to `awsJson1_0` annotated with `awsQueryCompatible`. After the migration, services will include an additional header `x-amzn-query-error` in their responses whose value is in the form of `;`. An example response looks something like +Add support for the `awsQueryCompatible` trait. This allows services to continue supporting a custom error code (via the `awsQueryError` trait) when the services migrate their protocol from `awsQuery` to `awsJson1_0` annotated with `awsQueryCompatible`. +
+Click to expand for more details... + +After the migration, services will include an additional header `x-amzn-query-error` in their responses whose value is in the form of `;`. An example response looks something like ``` HTTP/1.1 400 x-amzn-query-error: AWS.SimpleQueueService.NonExistentQueue;Sender @@ -266,14 +270,9 @@ match client.some_operation().send().await { assert_eq!(error.meta().extra("type"), Some("Sender")); } } +
``` """ references = ["smithy-rs#2398"] meta = { "breaking" = false, "tada" = true, "bug" = false } author = "ysaito1001" - -[[aws-sdk-rust]] -message = "Add support for the `awsQueryCompatible` trait, allowing the Rust SDK to migrate from the AWS Query protocol to the AWS JSON 1.0 protocol to continue supporting custom error codes." -references = ["smithy-rs#2398"] -meta = { "breaking" = false, "tada" = true, "bug" = false } -author = "ysaito1001" From dd1c3126aefa02043bdf4915b2d53b67712209fd Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Wed, 22 Feb 2023 14:23:29 -0600 Subject: [PATCH 7/8] Remove unused variables from `errorScope` This commit addresses https://github.com/awslabs/smithy-rs/pull/2398#discussion_r1114763528 --- .../rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt index 05578eba8b..32f9fdbb60 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQueryCompatible.kt @@ -50,8 +50,6 @@ class AwsQueryCompatible( private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), - "HeaderMap" to RuntimeType.Http.resolve("HeaderMap"), - "HeaderValue" to RuntimeType.Http.resolve("HeaderValue"), "JsonError" to CargoDependency.smithyJson(runtimeConfig).toType() .resolve("deserialize::error::DeserializeError"), "Response" to RuntimeType.Http.resolve("Response"), From befa41c9b0c632d0032b315f3a4d94f63ca7dc4e Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Wed, 22 Feb 2023 14:42:13 -0600 Subject: [PATCH 8/8] Reorder arguments for test verification This commit addresses https://github.com/awslabs/smithy-rs/pull/2398#discussion_r1114766817 --- .../client/smithy/protocols/AwsQueryCompatibleTest.kt | 8 ++++---- .../inlineable/src/aws_query_compatible_errors.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt index cad6d913b8..df33d83151 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -76,10 +76,10 @@ class AwsQueryCompatibleTest { .err() .unwrap(); assert_eq!( - error.meta().code(), Some("AWS.SimpleQueueService.NonExistentQueue"), + error.meta().code(), ); - assert_eq!(error.meta().extra("type"), Some("Sender")); + assert_eq!(Some("Sender"), error.meta().extra("type")); } """, ) @@ -141,8 +141,8 @@ class AwsQueryCompatibleTest { .parse(&response.map(bytes::Bytes::from)) .err() .unwrap(); - assert_eq!(error.meta().code(), Some("QueueDoesNotExist")); - assert_eq!(error.meta().extra("type"), None); + assert_eq!(Some("QueueDoesNotExist"), error.meta().code()); + assert_eq!(None, error.meta().extra("type")); } """, ) diff --git a/rust-runtime/inlineable/src/aws_query_compatible_errors.rs b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs index 361f5db578..7a94064d71 100644 --- a/rust-runtime/inlineable/src/aws_query_compatible_errors.rs +++ b/rust-runtime/inlineable/src/aws_query_compatible_errors.rs @@ -54,8 +54,8 @@ mod test { let actual = aws_query_compatible_error_from_header(response.headers()).unwrap(); assert_eq!( + Some("AWS.SimpleQueueService.NonExistentQueue;Sender"), actual, - Some("AWS.SimpleQueueService.NonExistentQueue;Sender") ); } @@ -70,8 +70,8 @@ mod test { let actual = parse_aws_query_compatible_error(response.headers()); assert_eq!( + Some(("AWS.SimpleQueueService.NonExistentQueue", "Sender")), actual, - Some(("AWS.SimpleQueueService.NonExistentQueue", "Sender")) ); } @@ -85,7 +85,7 @@ mod test { let actual = parse_aws_query_compatible_error(response.headers()); - assert_eq!(actual, None,); + assert_eq!(None, actual); } #[test] @@ -98,6 +98,6 @@ mod test { let actual = parse_aws_query_compatible_error(response.headers()); - assert_eq!(actual, None,); + assert_eq!(None, actual); } }