diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index f7915b8265..7e1b9862cb 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -28,3 +28,167 @@ message = "Adds jitter to `LazyCredentialsCache`. This allows credentials with t references = ["smithy-rs#2335"] meta = { "breaking" = false, "tada" = false, "bug" = false } author = "ysaito1001" + +[[aws-sdk-rust]] +message = """Request IDs can now be easily retrieved on successful responses. For example, with S3: +```rust +// Import the trait to get the `request_id` method on outputs +use aws_sdk_s3::types::RequestId; +let output = client.list_buckets().send().await?; +println!("Request ID: {:?}", output.request_id()); +``` +""" +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[aws-sdk-rust]] +message = """Retrieving a request ID from errors now requires importing the `RequestId` trait. For example, with S3: +```rust +use aws_sdk_s3::types::RequestId; +println!("Request ID: {:?}", error.request_id()); +``` +""" +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[smithy-rs]] +message = "Generic clients no longer expose a `request_id()` function on errors. To get request ID functionality, use the SDK code generator." +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"} +author = "jdisanti" + +[[aws-sdk-rust]] +message = "The `message()` and `code()` methods on errors have been moved into `ProvideErrorMetadata` trait. This trait will need to be imported to continue calling these." +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[smithy-rs]] +message = "The `message()` and `code()` methods on errors have been moved into `ProvideErrorMetadata` trait. This trait will need to be imported to continue calling these." +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"} +author = "jdisanti" + +[[aws-sdk-rust]] +message = """ +The `*Error` and `*ErrorKind` types have been combined to make error matching simpler. +
+Example with S3 +**Before:** +```rust +let result = client + .get_object() + .bucket(BUCKET_NAME) + .key("some-key") + .send() + .await; +match result { + Ok(_output) => { /* Do something with the output */ } + Err(err) => match err.into_service_error() { + GetObjectError { kind, .. } => match kind { + GetObjectErrorKind::InvalidObjectState(value) => println!("invalid object state: {:?}", value), + GetObjectErrorKind::NoSuchKey(_) => println!("object didn't exist"), + } + err @ GetObjectError { .. } if err.code() == Some("SomeUnmodeledError") => {} + err @ _ => return Err(err.into()), + }, +} +``` +**After:** +```rust +// Needed to access the `.code()` function on the error type: +use aws_sdk_s3::types::ProvideErrorMetadata; +let result = client + .get_object() + .bucket(BUCKET_NAME) + .key("some-key") + .send() + .await; +match result { + Ok(_output) => { /* Do something with the output */ } + Err(err) => match err.into_service_error() { + GetObjectError::InvalidObjectState(value) => { + println!("invalid object state: {:?}", value); + } + GetObjectError::NoSuchKey(_) => { + println!("object didn't exist"); + } + err if err.code() == Some("SomeUnmodeledError") => {} + err @ _ => return Err(err.into()), + }, +} +``` +
+""" +references = ["smithy-rs#76", "smithy-rs#2129", "smithy-rs#2075"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[smithy-rs]] +message = """ +The `*Error` and `*ErrorKind` types have been combined to make error matching simpler. +
+Example with S3 +**Before:** +```rust +let result = client + .get_object() + .bucket(BUCKET_NAME) + .key("some-key") + .send() + .await; +match result { + Ok(_output) => { /* Do something with the output */ } + Err(err) => match err.into_service_error() { + GetObjectError { kind, .. } => match kind { + GetObjectErrorKind::InvalidObjectState(value) => println!("invalid object state: {:?}", value), + GetObjectErrorKind::NoSuchKey(_) => println!("object didn't exist"), + } + err @ GetObjectError { .. } if err.code() == Some("SomeUnmodeledError") => {} + err @ _ => return Err(err.into()), + }, +} +``` +**After:** +```rust +// Needed to access the `.code()` function on the error type: +use aws_sdk_s3::types::ProvideErrorMetadata; +let result = client + .get_object() + .bucket(BUCKET_NAME) + .key("some-key") + .send() + .await; +match result { + Ok(_output) => { /* Do something with the output */ } + Err(err) => match err.into_service_error() { + GetObjectError::InvalidObjectState(value) => { + println!("invalid object state: {:?}", value); + } + GetObjectError::NoSuchKey(_) => { + println!("object didn't exist"); + } + err if err.code() == Some("SomeUnmodeledError") => {} + err @ _ => return Err(err.into()), + }, +} +``` +
+""" +references = ["smithy-rs#76", "smithy-rs#2129", "smithy-rs#2075"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"} +author = "jdisanti" + +[[smithy-rs]] +message = "`aws_smithy_types::Error` has been renamed to `aws_smithy_types::error::ErrorMetadata`." +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"} +author = "jdisanti" + +[[aws-sdk-rust]] +message = "`aws_smithy_types::Error` has been renamed to `aws_smithy_types::error::ErrorMetadata`." +references = ["smithy-rs#76", "smithy-rs#2129"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" diff --git a/aws/rust-runtime/aws-config/src/sts/assume_role.rs b/aws/rust-runtime/aws-config/src/sts/assume_role.rs index 422b644151..9561909ed3 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -7,7 +7,7 @@ use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; -use aws_sdk_sts::error::AssumeRoleErrorKind; +use aws_sdk_sts::error::AssumeRoleError; use aws_sdk_sts::middleware::DefaultMiddleware; use aws_sdk_sts::model::PolicyDescriptorType; use aws_sdk_sts::operation::AssumeRole; @@ -266,9 +266,9 @@ impl Inner { } Err(SdkError::ServiceError(ref context)) if matches!( - context.err().kind, - AssumeRoleErrorKind::RegionDisabledException(_) - | AssumeRoleErrorKind::MalformedPolicyDocumentException(_) + context.err(), + AssumeRoleError::RegionDisabledException(_) + | AssumeRoleError::MalformedPolicyDocumentException(_) ) => { Err(CredentialsError::invalid_configuration( diff --git a/aws/rust-runtime/aws-http/src/lib.rs b/aws/rust-runtime/aws-http/src/lib.rs index 1ec861b954..d5307bcba3 100644 --- a/aws/rust-runtime/aws-http/src/lib.rs +++ b/aws/rust-runtime/aws-http/src/lib.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Provides user agent and credentials middleware for the AWS SDK. +//! AWS-specific middleware implementations and HTTP-related features. #![allow(clippy::derive_partial_eq_without_eq)] #![warn( @@ -28,3 +28,6 @@ pub mod user_agent; /// AWS-specific content-encoding tools pub mod content_encoding; + +/// AWS-specific request ID support +pub mod request_id; diff --git a/aws/rust-runtime/aws-http/src/request_id.rs b/aws/rust-runtime/aws-http/src/request_id.rs new file mode 100644 index 0000000000..c3f1927228 --- /dev/null +++ b/aws/rust-runtime/aws-http/src/request_id.rs @@ -0,0 +1,182 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_http::http::HttpHeaders; +use aws_smithy_http::operation; +use aws_smithy_http::result::SdkError; +use aws_smithy_types::error::metadata::{ + Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata, +}; +use aws_smithy_types::error::Unhandled; +use http::{HeaderMap, HeaderValue}; + +/// Constant for the [`ErrorMetadata`] extra field that contains the request ID +const AWS_REQUEST_ID: &str = "aws_request_id"; + +/// Implementers add a function to return an AWS request ID +pub trait RequestId { + /// Returns the request ID, or `None` if the service could not be reached. + fn request_id(&self) -> Option<&str>; +} + +impl RequestId for SdkError +where + R: HttpHeaders, +{ + fn request_id(&self) -> Option<&str> { + match self { + Self::ResponseError(err) => extract_request_id(err.raw().http_headers()), + Self::ServiceError(err) => extract_request_id(err.raw().http_headers()), + _ => None, + } + } +} + +impl RequestId for ErrorMetadata { + fn request_id(&self) -> Option<&str> { + self.extra(AWS_REQUEST_ID) + } +} + +impl RequestId for Unhandled { + fn request_id(&self) -> Option<&str> { + self.meta().request_id() + } +} + +impl RequestId for operation::Response { + fn request_id(&self) -> Option<&str> { + extract_request_id(self.http().headers()) + } +} + +impl RequestId for http::Response { + fn request_id(&self) -> Option<&str> { + extract_request_id(self.headers()) + } +} + +impl RequestId for Result +where + O: RequestId, + E: RequestId, +{ + fn request_id(&self) -> Option<&str> { + match self { + Ok(ok) => ok.request_id(), + Err(err) => err.request_id(), + } + } +} + +/// Applies a request ID to a generic error builder +#[doc(hidden)] +pub fn apply_request_id( + builder: ErrorMetadataBuilder, + headers: &HeaderMap, +) -> ErrorMetadataBuilder { + if let Some(request_id) = extract_request_id(headers) { + builder.custom(AWS_REQUEST_ID, request_id) + } else { + builder + } +} + +/// Extracts a request ID from HTTP response headers +fn extract_request_id(headers: &HeaderMap) -> Option<&str> { + headers + .get("x-amzn-requestid") + .or_else(|| headers.get("x-amz-request-id")) + .and_then(|value| value.to_str().ok()) +} + +#[cfg(test)] +mod tests { + use super::*; + use aws_smithy_http::body::SdkBody; + use http::Response; + + #[test] + fn test_request_id_sdk_error() { + let without_request_id = + || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap()); + let with_request_id = || { + operation::Response::new( + Response::builder() + .header( + "x-amzn-requestid", + HeaderValue::from_static("some-request-id"), + ) + .body(SdkBody::empty()) + .unwrap(), + ) + }; + assert_eq!( + None, + SdkError::<(), _>::response_error("test", without_request_id()).request_id() + ); + assert_eq!( + Some("some-request-id"), + SdkError::<(), _>::response_error("test", with_request_id()).request_id() + ); + assert_eq!( + None, + SdkError::service_error((), without_request_id()).request_id() + ); + assert_eq!( + Some("some-request-id"), + SdkError::service_error((), with_request_id()).request_id() + ); + } + + #[test] + fn test_extract_request_id() { + let mut headers = HeaderMap::new(); + assert_eq!(None, extract_request_id(&headers)); + + headers.append( + "x-amzn-requestid", + HeaderValue::from_static("some-request-id"), + ); + assert_eq!(Some("some-request-id"), extract_request_id(&headers)); + + headers.append( + "x-amz-request-id", + HeaderValue::from_static("other-request-id"), + ); + assert_eq!(Some("some-request-id"), extract_request_id(&headers)); + + headers.remove("x-amzn-requestid"); + assert_eq!(Some("other-request-id"), extract_request_id(&headers)); + } + + #[test] + fn test_apply_request_id() { + let mut headers = HeaderMap::new(); + assert_eq!( + ErrorMetadata::builder().build(), + apply_request_id(ErrorMetadata::builder(), &headers).build(), + ); + + headers.append( + "x-amzn-requestid", + HeaderValue::from_static("some-request-id"), + ); + assert_eq!( + ErrorMetadata::builder() + .custom(AWS_REQUEST_ID, "some-request-id") + .build(), + apply_request_id(ErrorMetadata::builder(), &headers).build(), + ); + } + + #[test] + fn test_error_metadata_request_id_impl() { + let err = ErrorMetadata::builder() + .custom(AWS_REQUEST_ID, "some-request-id") + .build(); + assert_eq!(Some("some-request-id"), err.request_id()); + } +} diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs index 97acb89983..ed582f0e54 100644 --- a/aws/rust-runtime/aws-inlineable/src/lib.rs +++ b/aws/rust-runtime/aws-inlineable/src/lib.rs @@ -25,8 +25,8 @@ pub mod no_credentials; /// Support types required for adding presigning to an operation in a generated service. pub mod presigning; -/// Special logic for handling S3's error responses. -pub mod s3_errors; +/// Special logic for extracting request IDs from S3's responses. +pub mod s3_request_id; /// Glacier-specific checksumming behavior pub mod glacier_checksums; diff --git a/aws/rust-runtime/aws-inlineable/src/s3_errors.rs b/aws/rust-runtime/aws-inlineable/src/s3_errors.rs deleted file mode 100644 index ca15ddc42b..0000000000 --- a/aws/rust-runtime/aws-inlineable/src/s3_errors.rs +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use http::{HeaderMap, HeaderValue}; - -const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id"; - -/// S3-specific service error additions. -pub trait ErrorExt { - /// Returns the S3 Extended Request ID necessary when contacting AWS Support. - /// Read more at . - fn extended_request_id(&self) -> Option<&str>; -} - -impl ErrorExt for aws_smithy_types::Error { - fn extended_request_id(&self) -> Option<&str> { - self.extra(EXTENDED_REQUEST_ID) - } -} - -/// Parses the S3 Extended Request ID out of S3 error response headers. -pub fn parse_extended_error( - error: aws_smithy_types::Error, - headers: &HeaderMap, -) -> aws_smithy_types::Error { - let mut builder = error.into_builder(); - let host_id = headers - .get("x-amz-id-2") - .and_then(|header_value| header_value.to_str().ok()); - if let Some(host_id) = host_id { - builder.custom(EXTENDED_REQUEST_ID, host_id); - } - builder.build() -} - -#[cfg(test)] -mod test { - use crate::s3_errors::{parse_extended_error, ErrorExt}; - - #[test] - fn add_error_fields() { - let resp = http::Response::builder() - .header( - "x-amz-id-2", - "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran", - ) - .status(400) - .body("") - .unwrap(); - let error = aws_smithy_types::Error::builder() - .message("123") - .request_id("456") - .build(); - - let error = parse_extended_error(error, resp.headers()); - assert_eq!( - error - .extended_request_id() - .expect("extended request id should be set"), - "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran" - ); - } - - #[test] - fn handle_missing_header() { - let resp = http::Response::builder().status(400).body("").unwrap(); - let error = aws_smithy_types::Error::builder() - .message("123") - .request_id("456") - .build(); - - let error = parse_extended_error(error, resp.headers()); - assert_eq!(error.extended_request_id(), None); - } -} diff --git a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs new file mode 100644 index 0000000000..909dcbcd7a --- /dev/null +++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs @@ -0,0 +1,178 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_client::SdkError; +use aws_smithy_http::http::HttpHeaders; +use aws_smithy_http::operation; +use aws_smithy_types::error::metadata::{ + Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata, +}; +use aws_smithy_types::error::Unhandled; +use http::{HeaderMap, HeaderValue}; + +const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id"; + +/// Trait to retrieve the S3-specific extended request ID +/// +/// Read more at . +pub trait RequestIdExt { + /// Returns the S3 Extended Request ID necessary when contacting AWS Support. + fn extended_request_id(&self) -> Option<&str>; +} + +impl RequestIdExt for SdkError +where + R: HttpHeaders, +{ + fn extended_request_id(&self) -> Option<&str> { + match self { + Self::ResponseError(err) => extract_extended_request_id(err.raw().http_headers()), + Self::ServiceError(err) => extract_extended_request_id(err.raw().http_headers()), + _ => None, + } + } +} + +impl RequestIdExt for ErrorMetadata { + fn extended_request_id(&self) -> Option<&str> { + self.extra(EXTENDED_REQUEST_ID) + } +} + +impl RequestIdExt for Unhandled { + fn extended_request_id(&self) -> Option<&str> { + self.meta().extended_request_id() + } +} + +impl RequestIdExt for operation::Response { + fn extended_request_id(&self) -> Option<&str> { + extract_extended_request_id(self.http().headers()) + } +} + +impl RequestIdExt for http::Response { + fn extended_request_id(&self) -> Option<&str> { + extract_extended_request_id(self.headers()) + } +} + +impl RequestIdExt for Result +where + O: RequestIdExt, + E: RequestIdExt, +{ + fn extended_request_id(&self) -> Option<&str> { + match self { + Ok(ok) => ok.extended_request_id(), + Err(err) => err.extended_request_id(), + } + } +} + +/// Applies the extended request ID to a generic error builder +#[doc(hidden)] +pub fn apply_extended_request_id( + builder: ErrorMetadataBuilder, + headers: &HeaderMap, +) -> ErrorMetadataBuilder { + if let Some(extended_request_id) = extract_extended_request_id(headers) { + builder.custom(EXTENDED_REQUEST_ID, extended_request_id) + } else { + builder + } +} + +/// Extracts the S3 Extended Request ID from HTTP response headers +fn extract_extended_request_id(headers: &HeaderMap) -> Option<&str> { + headers + .get("x-amz-id-2") + .and_then(|value| value.to_str().ok()) +} + +#[cfg(test)] +mod test { + use super::*; + use aws_smithy_client::SdkError; + use aws_smithy_http::body::SdkBody; + use http::Response; + + #[test] + fn handle_missing_header() { + let resp = http::Response::builder().status(400).body("").unwrap(); + let mut builder = aws_smithy_types::Error::builder().message("123"); + builder = apply_extended_request_id(builder, resp.headers()); + assert_eq!(builder.build().extended_request_id(), None); + } + + #[test] + fn test_extended_request_id_sdk_error() { + let without_extended_request_id = + || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap()); + let with_extended_request_id = || { + operation::Response::new( + Response::builder() + .header("x-amz-id-2", HeaderValue::from_static("some-request-id")) + .body(SdkBody::empty()) + .unwrap(), + ) + }; + assert_eq!( + None, + SdkError::<(), _>::response_error("test", without_extended_request_id()) + .extended_request_id() + ); + assert_eq!( + Some("some-request-id"), + SdkError::<(), _>::response_error("test", with_extended_request_id()) + .extended_request_id() + ); + assert_eq!( + None, + SdkError::service_error((), without_extended_request_id()).extended_request_id() + ); + assert_eq!( + Some("some-request-id"), + SdkError::service_error((), with_extended_request_id()).extended_request_id() + ); + } + + #[test] + fn test_extract_extended_request_id() { + let mut headers = HeaderMap::new(); + assert_eq!(None, extract_extended_request_id(&headers)); + + headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id")); + assert_eq!( + Some("some-request-id"), + extract_extended_request_id(&headers) + ); + } + + #[test] + fn test_apply_extended_request_id() { + let mut headers = HeaderMap::new(); + assert_eq!( + ErrorMetadata::builder().build(), + apply_extended_request_id(ErrorMetadata::builder(), &headers).build(), + ); + + headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id")); + assert_eq!( + ErrorMetadata::builder() + .custom(EXTENDED_REQUEST_ID, "some-request-id") + .build(), + apply_extended_request_id(ErrorMetadata::builder(), &headers).build(), + ); + } + + #[test] + fn test_error_metadata_extended_request_id_impl() { + let err = ErrorMetadata::builder() + .custom(EXTENDED_REQUEST_ID, "some-request-id") + .build(); + assert_eq!(Some("some-request-id"), err.extended_request_id()); + } +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 75d6fd7cbd..3e55fd9ce2 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -9,12 +9,15 @@ import software.amazon.smithy.rust.codegen.client.smithy.customizations.DocsRsMe import software.amazon.smithy.rust.codegen.client.smithy.customizations.DocsRsMetadataSettings import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator +import software.amazon.smithy.rustsdk.customize.DisabledAuthDecorator import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator -import software.amazon.smithy.rustsdk.customize.auth.DisabledAuthDecorator +import software.amazon.smithy.rustsdk.customize.applyDecorators import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator import software.amazon.smithy.rustsdk.customize.glacier.GlacierDecorator +import software.amazon.smithy.rustsdk.customize.onlyApplyTo import software.amazon.smithy.rustsdk.customize.route53.Route53Decorator import software.amazon.smithy.rustsdk.customize.s3.S3Decorator +import software.amazon.smithy.rustsdk.customize.s3.S3ExtendedRequestIdDecorator import software.amazon.smithy.rustsdk.customize.s3control.S3ControlDecorator import software.amazon.smithy.rustsdk.customize.sts.STSDecorator import software.amazon.smithy.rustsdk.endpoints.AwsEndpointDecorator @@ -23,41 +26,49 @@ import software.amazon.smithy.rustsdk.endpoints.OperationInputTestDecorator val DECORATORS: List = listOf( // General AWS Decorators - CredentialsCacheDecorator(), - CredentialsProviderDecorator(), - RegionDecorator(), - AwsEndpointDecorator(), - UserAgentDecorator(), - SigV4SigningDecorator(), - HttpRequestChecksumDecorator(), - HttpResponseChecksumDecorator(), - RetryClassifierDecorator(), - IntegrationTestDecorator(), - AwsFluentClientDecorator(), - CrateLicenseDecorator(), - SdkConfigDecorator(), - ServiceConfigDecorator(), - AwsPresigningDecorator(), - AwsReadmeDecorator(), - HttpConnectorDecorator(), - AwsEndpointsStdLib(), - *PromotedBuiltInsDecorators, - GenericSmithySdkConfigSettings(), - OperationInputTestDecorator(), + listOf( + CredentialsCacheDecorator(), + CredentialsProviderDecorator(), + RegionDecorator(), + AwsEndpointDecorator(), + UserAgentDecorator(), + SigV4SigningDecorator(), + HttpRequestChecksumDecorator(), + HttpResponseChecksumDecorator(), + RetryClassifierDecorator(), + IntegrationTestDecorator(), + AwsFluentClientDecorator(), + CrateLicenseDecorator(), + SdkConfigDecorator(), + ServiceConfigDecorator(), + AwsPresigningDecorator(), + AwsReadmeDecorator(), + HttpConnectorDecorator(), + AwsEndpointsStdLib(), + *PromotedBuiltInsDecorators, + GenericSmithySdkConfigSettings(), + OperationInputTestDecorator(), + AwsRequestIdDecorator(), + DisabledAuthDecorator(), + ), // Service specific decorators - ApiGatewayDecorator(), - DisabledAuthDecorator(), - Ec2Decorator(), - GlacierDecorator(), - Route53Decorator(), - S3Decorator(), - S3ControlDecorator(), - STSDecorator(), + ApiGatewayDecorator().onlyApplyTo("com.amazonaws.apigateway#BackplaneControlService"), + Ec2Decorator().onlyApplyTo("com.amazonaws.ec2#AmazonEC2"), + GlacierDecorator().onlyApplyTo("com.amazonaws.glacier#Glacier"), + Route53Decorator().onlyApplyTo("com.amazonaws.route53#AWSDnsV20130401"), + "com.amazonaws.s3#AmazonS3".applyDecorators( + S3Decorator(), + S3ExtendedRequestIdDecorator(), + ), + S3ControlDecorator().onlyApplyTo("com.amazonaws.s3control#AWSS3ControlServiceV20180820"), + STSDecorator().onlyApplyTo("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"), // Only build docs-rs for linux to reduce load on docs.rs - DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)), -) + listOf( + DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)), + ), +).flatten() class AwsCodegenDecorator : CombinedClientCodegenDecorator(DECORATORS) { override val name: String = "AwsSdkCodegenDecorator" diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt new file mode 100644 index 0000000000..0b496ce2c9 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType + +/** + * Customizes response parsing logic to add AWS request IDs to error metadata and outputs + */ +class AwsRequestIdDecorator : BaseRequestIdDecorator() { + override val name: String = "AwsRequestIdDecorator" + override val order: Byte = 0 + + override val fieldName: String = "request_id" + override val accessorFunctionName: String = "request_id" + + private fun requestIdModule(codegenContext: ClientCodegenContext): RuntimeType = + AwsRuntimeType.awsHttp(codegenContext.runtimeConfig).resolve("request_id") + + override fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType = + requestIdModule(codegenContext).resolve("RequestId") + + override fun applyToError(codegenContext: ClientCodegenContext): RuntimeType = + requestIdModule(codegenContext).resolve("apply_request_id") +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt index 9fdbc93eda..c423dfd783 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt @@ -41,7 +41,6 @@ fun RuntimeConfig.awsRoot(): RuntimeCrateLocation { } object AwsRuntimeType { - val S3Errors by lazy { RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("s3_errors")) } val Presigning by lazy { RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("presigning", visibility = Visibility.PUBLIC)) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt new file mode 100644 index 0000000000..a3a991bedf --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt @@ -0,0 +1,221 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorSection +import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplSection +import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait +import software.amazon.smithy.rust.codegen.core.util.hasTrait + +/** + * Base customization for adding a request ID (or extended request ID) to outputs and errors. + */ +abstract class BaseRequestIdDecorator : ClientCodegenDecorator { + abstract val accessorFunctionName: String + abstract val fieldName: String + abstract fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType + abstract fun applyToError(codegenContext: ClientCodegenContext): RuntimeType + + override fun operationCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = baseCustomizations + listOf(RequestIdOperationCustomization(codegenContext)) + + override fun errorCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = + baseCustomizations + listOf(RequestIdErrorCustomization(codegenContext)) + + override fun errorImplCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + listOf(RequestIdErrorImplCustomization(codegenContext)) + + override fun structureCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + listOf(RequestIdStructureCustomization(codegenContext)) + + override fun builderCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + listOf(RequestIdBuilderCustomization()) + + override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { + rustCrate.withModule(RustModule.Types) { + // Re-export RequestId in generated crate + rust("pub use #T;", accessorTrait(codegenContext)) + } + } + + private inner class RequestIdOperationCustomization(private val codegenContext: ClientCodegenContext) : + OperationCustomization() { + override fun section(section: OperationSection): Writable = writable { + when (section) { + is OperationSection.PopulateErrorMetadataExtras -> { + rustTemplate( + "${section.builderName} = #{apply_to_error}(${section.builderName}, ${section.responseName}.headers());", + "apply_to_error" to applyToError(codegenContext), + ) + } + is OperationSection.MutateOutput -> { + rust( + "output._set_$fieldName(#T::$accessorFunctionName(response).map(str::to_string));", + accessorTrait(codegenContext), + ) + } + is OperationSection.BeforeParseResponse -> { + rustTemplate( + "#{tracing}::debug!($fieldName = ?#{trait}::$accessorFunctionName(${section.responseName}));", + "tracing" to RuntimeType.Tracing, + "trait" to accessorTrait(codegenContext), + ) + } + else -> {} + } + } + } + + private inner class RequestIdErrorCustomization(private val codegenContext: ClientCodegenContext) : + ErrorCustomization() { + override fun section(section: ErrorSection): Writable = writable { + when (section) { + is ErrorSection.OperationErrorAdditionalTraitImpls -> { + rustTemplate( + """ + impl #{AccessorTrait} for #{error} { + fn $accessorFunctionName(&self) -> Option<&str> { + self.meta().$accessorFunctionName() + } + } + """, + "AccessorTrait" to accessorTrait(codegenContext), + "error" to section.errorSymbol, + ) + } + + is ErrorSection.ServiceErrorAdditionalTraitImpls -> { + rustBlock("impl #T for Error", accessorTrait(codegenContext)) { + rustBlock("fn $accessorFunctionName(&self) -> Option<&str>") { + rustBlock("match self") { + section.allErrors.forEach { error -> + val sym = codegenContext.symbolProvider.toSymbol(error) + rust("Self::${sym.name}(e) => e.$accessorFunctionName(),") + } + rust("Self::Unhandled(e) => e.$accessorFunctionName(),") + } + } + } + } + } + } + } + + private inner class RequestIdErrorImplCustomization(private val codegenContext: ClientCodegenContext) : + ErrorImplCustomization() { + override fun section(section: ErrorImplSection): Writable = writable { + when (section) { + is ErrorImplSection.ErrorAdditionalTraitImpls -> { + rustBlock("impl #1T for #2T", accessorTrait(codegenContext), section.errorType) { + rustBlock("fn $accessorFunctionName(&self) -> Option<&str>") { + rust("use #T;", RuntimeType.provideErrorMetadataTrait(codegenContext.runtimeConfig)) + rust("self.meta().$accessorFunctionName()") + } + } + } + + else -> {} + } + } + } + + private inner class RequestIdStructureCustomization(private val codegenContext: ClientCodegenContext) : + StructureCustomization() { + override fun section(section: StructureSection): Writable = writable { + if (section.shape.hasTrait()) { + when (section) { + is StructureSection.AdditionalFields -> { + rust("_$fieldName: Option,") + } + + is StructureSection.AdditionalTraitImpls -> { + rustTemplate( + """ + impl #{AccessorTrait} for ${section.structName} { + fn $accessorFunctionName(&self) -> Option<&str> { + self._$fieldName.as_deref() + } + } + """, + "AccessorTrait" to accessorTrait(codegenContext), + ) + } + + is StructureSection.AdditionalDebugFields -> { + rust("""${section.formatterName}.field("_$fieldName", &self._$fieldName);""") + } + } + } + } + } + + private inner class RequestIdBuilderCustomization : BuilderCustomization() { + override fun section(section: BuilderSection): Writable = writable { + if (section.shape.hasTrait()) { + when (section) { + is BuilderSection.AdditionalFields -> { + rust("_$fieldName: Option,") + } + + is BuilderSection.AdditionalMethods -> { + rust( + """ + pub(crate) fn _$fieldName(mut self, $fieldName: impl Into) -> Self { + self._$fieldName = Some($fieldName.into()); + self + } + + pub(crate) fn _set_$fieldName(&mut self, $fieldName: Option) -> &mut Self { + self._$fieldName = $fieldName; + self + } + """, + ) + } + + is BuilderSection.AdditionalDebugFields -> { + rust("""${section.formatterName}.field("_$fieldName", &self._$fieldName);""") + } + + is BuilderSection.AdditionalFieldsInBuild -> { + rust("_$fieldName: self._$fieldName,") + } + } + } + } + } +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt similarity index 91% rename from aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt rename to aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt index 2c65f95bd3..dfbd2ca597 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt @@ -3,17 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rustsdk.customize.auth +package software.amazon.smithy.rustsdk.customize import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.traits.AuthTrait import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator - -private fun String.shapeId() = ShapeId.from(this) +import software.amazon.smithy.rust.codegen.core.util.shapeId // / STS (and possibly other services) need to have auth manually set to [] class DisabledAuthDecorator : ClientCodegenDecorator { diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt new file mode 100644 index 0000000000..8e957b3f59 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk.customize + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.ToShapeId +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientProtocolMap +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization +import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.ManifestCustomizations +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization + +/** Only apply this decorator to the given service ID */ +fun ClientCodegenDecorator.onlyApplyTo(serviceId: String): List = + listOf(ServiceSpecificDecorator(ShapeId.from(serviceId), this)) + +/** Apply the given decorators only to this service ID */ +fun String.applyDecorators(vararg decorators: ClientCodegenDecorator): List = + decorators.map { it.onlyApplyTo(this) }.flatten() + +/** + * Delegating decorator that only applies to a configured service ID + */ +class ServiceSpecificDecorator( + /** Service ID this decorator is active for */ + private val appliesToServiceId: ShapeId, + /** Decorator to delegate to */ + private val delegateTo: ClientCodegenDecorator, + /** Decorator name */ + override val name: String = "${appliesToServiceId.namespace}.${appliesToServiceId.name}", + /** Decorator order */ + override val order: Byte = 0, +) : ClientCodegenDecorator { + private fun T.maybeApply(serviceId: ToShapeId, delegatedValue: () -> T): T = + if (appliesToServiceId == serviceId.toShapeId()) { + delegatedValue() + } else { + this + } + + // This kind of decorator gets explicitly added to the root sdk-codegen decorator + override fun classpathDiscoverable(): Boolean = false + + override fun builderCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.builderCustomizations(codegenContext, baseCustomizations) + } + + override fun configCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.configCustomizations(codegenContext, baseCustomizations) + } + + override fun crateManifestCustomizations(codegenContext: ClientCodegenContext): ManifestCustomizations = + emptyMap().maybeApply(codegenContext.serviceShape) { + delegateTo.crateManifestCustomizations(codegenContext) + } + + override fun endpointCustomizations(codegenContext: ClientCodegenContext): List = + emptyList().maybeApply(codegenContext.serviceShape) { + delegateTo.endpointCustomizations(codegenContext) + } + + override fun errorCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.errorCustomizations(codegenContext, baseCustomizations) + } + + override fun errorImplCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.errorImplCustomizations(codegenContext, baseCustomizations) + } + + override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { + maybeApply(codegenContext.serviceShape) { + delegateTo.extras(codegenContext, rustCrate) + } + } + + override fun libRsCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.libRsCustomizations(codegenContext, baseCustomizations) + } + + override fun operationCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.operationCustomizations(codegenContext, operation, baseCustomizations) + } + + override fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap = + currentProtocols.maybeApply(serviceId) { + delegateTo.protocols(serviceId, currentProtocols) + } + + override fun structureCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.structureCustomizations(codegenContext, baseCustomizations) + } + + override fun transformModel(service: ServiceShape, model: Model): Model = + model.maybeApply(service) { + delegateTo.transformModel(service, model) + } +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt index 9fc0f3e4c7..5959918ef7 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt @@ -6,34 +6,24 @@ package software.amazon.smithy.rustsdk.customize.apigateway import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.writable -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.customize.OperationCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection -import software.amazon.smithy.rust.codegen.core.util.letIf class ApiGatewayDecorator : ClientCodegenDecorator { override val name: String = "ApiGateway" override val order: Byte = 0 - private fun applies(codegenContext: CodegenContext) = - codegenContext.serviceShape.id == ShapeId.from("com.amazonaws.apigateway#BackplaneControlService") - override fun operationCustomizations( codegenContext: ClientCodegenContext, operation: OperationShape, baseCustomizations: List, - ): List { - return baseCustomizations.letIf(applies(codegenContext)) { - it + ApiGatewayAddAcceptHeader() - } - } + ): List = baseCustomizations + ApiGatewayAddAcceptHeader() } class ApiGatewayAddAcceptHeader : OperationCustomization() { diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt index ee005da318..e788920e1d 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt @@ -7,24 +7,14 @@ package software.amazon.smithy.rustsdk.customize.ec2 import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator -import software.amazon.smithy.rust.codegen.core.util.letIf class Ec2Decorator : ClientCodegenDecorator { override val name: String = "Ec2" override val order: Byte = 0 - private val ec2 = ShapeId.from("com.amazonaws.ec2#AmazonEC2") - private fun applies(serviceShape: ServiceShape) = - serviceShape.id == ec2 - - override fun transformModel(service: ServiceShape, model: Model): Model { - // EC2 incorrectly models primitive shapes as unboxed when they actually - // need to be boxed for the API to work properly - return model.letIf( - applies(service), - EC2MakePrimitivesOptional::processModel, - ) - } + // EC2 incorrectly models primitive shapes as unboxed when they actually + // need to be boxed for the API to work properly + override fun transformModel(service: ServiceShape, model: Model): Model = + EC2MakePrimitivesOptional.processModel(model) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt index 7bfc3c4e42..5ba71e2f20 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt @@ -6,35 +6,21 @@ package software.amazon.smithy.rustsdk.customize.glacier import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization -val Glacier: ShapeId = ShapeId.from("com.amazonaws.glacier#Glacier") - class GlacierDecorator : ClientCodegenDecorator { override val name: String = "Glacier" override val order: Byte = 0 - private fun applies(codegenContext: CodegenContext) = codegenContext.serviceShape.id == Glacier - override fun operationCustomizations( codegenContext: ClientCodegenContext, operation: OperationShape, baseCustomizations: List, - ): List { - val extras = if (applies(codegenContext)) { - val apiVersion = codegenContext.serviceShape.version - listOfNotNull( - ApiVersionHeader(apiVersion), - TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig), - AccountIdAutofill.forOperation(operation, codegenContext.model), - ) - } else { - emptyList() - } - return baseCustomizations + extras - } + ): List = baseCustomizations + listOfNotNull( + ApiVersionHeader(codegenContext.serviceShape.version), + TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig), + AccountIdAutofill.forOperation(operation, codegenContext.model), + ) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt index e1adcf46af..c8b8cb9813 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt @@ -26,26 +26,19 @@ import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rustsdk.InlineAwsDependency import java.util.logging.Logger -val Route53: ShapeId = ShapeId.from("com.amazonaws.route53#AWSDnsV20130401") - class Route53Decorator : ClientCodegenDecorator { override val name: String = "Route53" override val order: Byte = 0 private val logger: Logger = Logger.getLogger(javaClass.name) private val resourceShapes = setOf(ShapeId.from("com.amazonaws.route53#ResourceId"), ShapeId.from("com.amazonaws.route53#ChangeId")) - private fun applies(service: ServiceShape) = service.id == Route53 - - override fun transformModel(service: ServiceShape, model: Model): Model { - return model.letIf(applies(service)) { - ModelTransformer.create().mapShapes(model) { shape -> - shape.letIf(isResourceId(shape)) { - logger.info("Adding TrimResourceId trait to $shape") - (shape as MemberShape).toBuilder().addTrait(TrimResourceId()).build() - } + override fun transformModel(service: ServiceShape, model: Model): Model = + ModelTransformer.create().mapShapes(model) { shape -> + shape.letIf(isResourceId(shape)) { + logger.info("Adding TrimResourceId trait to $shape") + (shape as MemberShape).toBuilder().addTrait(TrimResourceId()).build() } } - } override fun operationCustomizations( codegenContext: ClientCodegenContext, diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt index d6c0f8257f..bd0c521008 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt @@ -22,19 +22,15 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.Cli import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientRestXmlFactory import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.Writable -import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable 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.generators.LibRsCustomization -import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestXml import software.amazon.smithy.rust.codegen.core.smithy.traits.AllowInvalidXmlRoot import software.amazon.smithy.rust.codegen.core.util.letIf -import software.amazon.smithy.rustsdk.AwsRuntimeType import software.amazon.smithy.rustsdk.endpoints.stripEndpointTrait import software.amazon.smithy.rustsdk.getBuiltIn import software.amazon.smithy.rustsdk.toWritable @@ -52,38 +48,22 @@ class S3Decorator : ClientCodegenDecorator { ShapeId.from("com.amazonaws.s3#GetObjectAttributesOutput"), ) - private fun applies(serviceId: ShapeId) = - serviceId == ShapeId.from("com.amazonaws.s3#AmazonS3") - override fun protocols( serviceId: ShapeId, currentProtocols: ProtocolMap, - ): ProtocolMap = - currentProtocols.letIf(applies(serviceId)) { - it + mapOf( - RestXmlTrait.ID to ClientRestXmlFactory { protocolConfig -> - S3(protocolConfig) - }, - ) - } - - override fun transformModel(service: ServiceShape, model: Model): Model { - return model.letIf(applies(service.id)) { - ModelTransformer.create().mapShapes(model) { shape -> - shape.letIf(isInInvalidXmlRootAllowList(shape)) { - logger.info("Adding AllowInvalidXmlRoot trait to $it") - (it as StructureShape).toBuilder().addTrait(AllowInvalidXmlRoot()).build() - } - }.let(StripBucketFromHttpPath()::transform).let(stripEndpointTrait("RequestRoute")) - } - } + ): ProtocolMap = currentProtocols + mapOf( + RestXmlTrait.ID to ClientRestXmlFactory { protocolConfig -> + S3ProtocolOverride(protocolConfig) + }, + ) - override fun libRsCustomizations( - codegenContext: ClientCodegenContext, - baseCustomizations: List, - ): List = baseCustomizations.letIf(applies(codegenContext.serviceShape.id)) { - it + S3PubUse() - } + override fun transformModel(service: ServiceShape, model: Model): Model = + ModelTransformer.create().mapShapes(model) { shape -> + shape.letIf(isInInvalidXmlRootAllowList(shape)) { + logger.info("Adding AllowInvalidXmlRoot trait to $it") + (it as StructureShape).toBuilder().addTrait(AllowInvalidXmlRoot()).build() + } + }.let(StripBucketFromHttpPath()::transform).let(stripEndpointTrait("RequestRoute")) override fun endpointCustomizations(codegenContext: ClientCodegenContext): List { return listOf(object : EndpointCustomization { @@ -108,35 +88,36 @@ class S3Decorator : ClientCodegenDecorator { } } -class S3(codegenContext: CodegenContext) : RestXml(codegenContext) { +class S3ProtocolOverride(codegenContext: CodegenContext) : RestXml(codegenContext) { private val runtimeConfig = codegenContext.runtimeConfig private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadata" to RuntimeType.errorMetadata(runtimeConfig), + "ErrorBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.HttpHeaderMap, "Response" to RuntimeType.HttpResponse, "XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"), "base_errors" to restXmlErrors, - "s3_errors" to AwsRuntimeType.S3Errors, ) - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType { - return RuntimeType.forInlineFun("parse_http_generic_error", RustModule.private("xml_deser")) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType { + return RuntimeType.forInlineFun("parse_http_error_metadata", RustModule.private("xml_deser")) { rustBlockTemplate( - "pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorBuilder}, #{XmlDecodeError}>", *errorScope, ) { rustTemplate( """ + // S3 HEAD responses have no response body to for an error code. Therefore, + // check the HTTP response status and populate an error code for 404s. if response.body().is_empty() { - let mut err = #{Error}::builder(); + let mut builder = #{ErrorMetadata}::builder(); if response.status().as_u16() == 404 { - err.code("NotFound"); + builder = builder.code("NotFound"); } - Ok(err.build()) + Ok(builder) } else { - let base_err = #{base_errors}::parse_generic_error(response.body().as_ref())?; - Ok(#{s3_errors}::parse_extended_error(base_err, response.headers())) + #{base_errors}::parse_error_metadata(response.body().as_ref()) } """, *errorScope, @@ -145,16 +126,3 @@ class S3(codegenContext: CodegenContext) : RestXml(codegenContext) { } } } - -class S3PubUse : LibRsCustomization() { - override fun section(section: LibRsSection): Writable = when (section) { - is LibRsSection.Body -> writable { - rust( - "pub use #T::ErrorExt;", - AwsRuntimeType.S3Errors, - ) - } - - else -> emptySection - } -} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt new file mode 100644 index 0000000000..6b117b60da --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk.customize.s3 + +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rustsdk.BaseRequestIdDecorator +import software.amazon.smithy.rustsdk.InlineAwsDependency + +class S3ExtendedRequestIdDecorator : BaseRequestIdDecorator() { + override val name: String = "S3ExtendedRequestIdDecorator" + override val order: Byte = 0 + + override val fieldName: String = "extended_request_id" + override val accessorFunctionName: String = "extended_request_id" + + private val requestIdModule: RuntimeType = + RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("s3_request_id")) + + override fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType = + requestIdModule.resolve("RequestIdExt") + + override fun applyToError(codegenContext: ClientCodegenContext): RuntimeType = + requestIdModule.resolve("apply_extended_request_id") +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt index 39e85d7898..3534258b18 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt @@ -6,21 +6,41 @@ package software.amazon.smithy.rustsdk.customize.s3control import software.amazon.smithy.model.Model +import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rustsdk.endpoints.stripEndpointTrait +import software.amazon.smithy.rustsdk.getBuiltIn +import software.amazon.smithy.rustsdk.toWritable class S3ControlDecorator : ClientCodegenDecorator { override val name: String = "S3Control" override val order: Byte = 0 - private fun applies(service: ServiceShape) = - service.id == ShapeId.from("com.amazonaws.s3control#AWSS3ControlServiceV20180820") - override fun transformModel(service: ServiceShape, model: Model): Model { - if (!applies(service)) { - return model - } - return stripEndpointTrait("AccountId")(model) + override fun transformModel(service: ServiceShape, model: Model): Model = + stripEndpointTrait("AccountId")(model) + + override fun endpointCustomizations(codegenContext: ClientCodegenContext): List { + return listOf(object : EndpointCustomization { + override fun setBuiltInOnServiceConfig(name: String, value: Node, configBuilderRef: String): Writable? { + if (!name.startsWith("AWS::S3Control")) { + return null + } + val builtIn = codegenContext.getBuiltIn(name) ?: return null + return writable { + rustTemplate( + "let $configBuilderRef = $configBuilderRef.${builtIn.name.rustName()}(#{value});", + "value" to value.toWritable(), + ) + } + } + }, + ) } } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt index 75d0555c8f..a332dd3035 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt @@ -7,7 +7,6 @@ package software.amazon.smithy.rustsdk.customize.sts import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.RetryableTrait @@ -22,24 +21,18 @@ class STSDecorator : ClientCodegenDecorator { override val order: Byte = 0 private val logger: Logger = Logger.getLogger(javaClass.name) - private fun applies(serviceId: ShapeId) = - serviceId == ShapeId.from("com.amazonaws.sts#AWSSecurityTokenServiceV20110615") - private fun isIdpCommunicationError(shape: Shape): Boolean = shape is StructureShape && shape.hasTrait() && shape.id.namespace == "com.amazonaws.sts" && shape.id.name == "IDPCommunicationErrorException" - override fun transformModel(service: ServiceShape, model: Model): Model { - return model.letIf(applies(service.id)) { - ModelTransformer.create().mapShapes(model) { shape -> - shape.letIf(isIdpCommunicationError(shape)) { - logger.info("Adding @retryable trait to $shape and setting its error type to 'server'") - (shape as StructureShape).toBuilder() - .removeTrait(ErrorTrait.ID) - .addTrait(ErrorTrait("server")) - .addTrait(RetryableTrait.builder().build()).build() - } + override fun transformModel(service: ServiceShape, model: Model): Model = + ModelTransformer.create().mapShapes(model) { shape -> + shape.letIf(isIdpCommunicationError(shape)) { + logger.info("Adding @retryable trait to $shape and setting its error type to 'server'") + (shape as StructureShape).toBuilder() + .removeTrait(ErrorTrait.ID) + .addTrait(ErrorTrait("server")) + .addTrait(RetryableTrait.builder().build()).build() } } - } } diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs index baa39ef6a4..8c9bd1e105 100644 --- a/aws/sdk/integration-tests/kms/tests/integration.rs +++ b/aws/sdk/integration-tests/kms/tests/integration.rs @@ -6,6 +6,7 @@ use aws_http::user_agent::AwsUserAgent; use aws_sdk_kms as kms; use aws_sdk_kms::middleware::DefaultMiddleware; +use aws_sdk_kms::types::RequestId; use aws_smithy_client::test_connection::TestConnection; use aws_smithy_client::{Client as CoreClient, SdkError}; use aws_smithy_http::body::SdkBody; diff --git a/aws/sdk/integration-tests/kms/tests/sensitive-it.rs b/aws/sdk/integration-tests/kms/tests/sensitive-it.rs index 5a97651d83..00f3c8d95e 100644 --- a/aws/sdk/integration-tests/kms/tests/sensitive-it.rs +++ b/aws/sdk/integration-tests/kms/tests/sensitive-it.rs @@ -19,12 +19,17 @@ use kms::types::Blob; #[test] fn validate_sensitive_trait() { + let builder = GenerateRandomOutput::builder().plaintext(Blob::new("some output")); + assert_eq!( + format!("{:?}", builder), + "Builder { plaintext: \"*** Sensitive Data Redacted ***\", _request_id: None }" + ); let output = GenerateRandomOutput::builder() .plaintext(Blob::new("some output")) .build(); assert_eq!( format!("{:?}", output), - "GenerateRandomOutput { plaintext: \"*** Sensitive Data Redacted ***\" }" + "GenerateRandomOutput { plaintext: \"*** Sensitive Data Redacted ***\", _request_id: None }" ); } diff --git a/aws/sdk/integration-tests/lambda/tests/request_id.rs b/aws/sdk/integration-tests/lambda/tests/request_id.rs new file mode 100644 index 0000000000..ab3ede5f0a --- /dev/null +++ b/aws/sdk/integration-tests/lambda/tests/request_id.rs @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_lambda::error::ListFunctionsError; +use aws_sdk_lambda::operation::ListFunctions; +use aws_sdk_lambda::types::RequestId; +use aws_smithy_http::response::ParseHttpResponse; +use bytes::Bytes; + +#[test] +fn get_request_id_from_unmodeled_error() { + let resp = http::Response::builder() + .header("x-amzn-RequestId", "correct-request-id") + .header("X-Amzn-Errortype", "ListFunctions") + .status(500) + .body("{}") + .unwrap(); + let err = ListFunctions::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect_err("status was 500, this is an error"); + assert!(matches!(err, ListFunctionsError::Unhandled(_))); + assert_eq!(Some("correct-request-id"), err.request_id()); + assert_eq!(Some("correct-request-id"), err.meta().request_id()); +} + +#[test] +fn get_request_id_from_successful_response() { + let resp = http::Response::builder() + .header("x-amzn-RequestId", "correct-request-id") + .status(200) + .body(r#"{"Functions":[],"NextMarker":null}"#) + .unwrap(); + let output = ListFunctions::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect("valid successful response"); + assert_eq!(Some("correct-request-id"), output.request_id()); +} diff --git a/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs b/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs deleted file mode 100644 index 46b1fc50f7..0000000000 --- a/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use aws_sdk_s3::operation::GetObject; -use aws_sdk_s3::ErrorExt; -use aws_smithy_http::response::ParseHttpResponse; -use bytes::Bytes; - -#[test] -fn deserialize_extended_errors() { - let resp = http::Response::builder() - .header( - "x-amz-id-2", - "gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP", - ) - .header("x-amz-request-id", "3B3C7C725673C630") - .status(404) - .body( - r#" - - NoSuchKey - The resource you requested does not exist - /mybucket/myfoto.jpg - 4442587FB7D0A2F9 -"#, - ) - .unwrap(); - let err = GetObject::new() - .parse_loaded(&resp.map(Bytes::from)) - .expect_err("status was 404, this is an error"); - assert_eq!( - err.meta().extended_request_id(), - Some("gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP") - ); - assert_eq!(err.meta().request_id(), Some("4442587FB7D0A2F9")); -} diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs index 858d013841..7714becfe5 100644 --- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs +++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs @@ -71,7 +71,7 @@ async fn test_s3_signer_query_string_with_all_valid_chars() { #[tokio::test] #[ignore] async fn test_query_strings_are_correctly_encoded() { - use aws_sdk_s3::error::{ListObjectsV2Error, ListObjectsV2ErrorKind}; + use aws_sdk_s3::error::ListObjectsV2Error; use aws_smithy_http::result::SdkError; tracing_subscriber::fmt::init(); @@ -92,22 +92,19 @@ async fn test_query_strings_are_correctly_encoded() { .send() .await; if let Err(SdkError::ServiceError(context)) = res { - let ListObjectsV2Error { kind, .. } = context.err(); - match kind { - ListObjectsV2ErrorKind::Unhandled(e) + match context.err() { + ListObjectsV2Error::Unhandled(e) if e.to_string().contains("SignatureDoesNotMatch") => { chars_that_break_signing.push(byte); } - ListObjectsV2ErrorKind::Unhandled(e) if e.to_string().contains("InvalidUri") => { + ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidUri") => { chars_that_break_uri_parsing.push(byte); } - ListObjectsV2ErrorKind::Unhandled(e) - if e.to_string().contains("InvalidArgument") => - { + ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidArgument") => { chars_that_are_invalid_arguments.push(byte); } - ListObjectsV2ErrorKind::Unhandled(e) if e.to_string().contains("InvalidToken") => { + ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidToken") => { panic!("refresh your credentials and run this test again"); } e => todo!("unexpected error: {:?}", e), diff --git a/aws/sdk/integration-tests/s3/tests/request_id.rs b/aws/sdk/integration-tests/s3/tests/request_id.rs new file mode 100644 index 0000000000..957dd8cb28 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/request_id.rs @@ -0,0 +1,148 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_s3::error::GetObjectError; +use aws_sdk_s3::operation::{GetObject, ListBuckets}; +use aws_sdk_s3::types::{RequestId, RequestIdExt}; +use aws_smithy_http::body::SdkBody; +use aws_smithy_http::operation; +use aws_smithy_http::response::ParseHttpResponse; +use bytes::Bytes; + +#[test] +fn get_request_id_from_modeled_error() { + let resp = http::Response::builder() + .header("x-amz-request-id", "correct-request-id") + .header("x-amz-id-2", "correct-extended-request-id") + .status(404) + .body( + r#" + + NoSuchKey + The resource you requested does not exist + /mybucket/myfoto.jpg + incorrect-request-id + "#, + ) + .unwrap(); + let err = GetObject::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect_err("status was 404, this is an error"); + assert!(matches!(err, GetObjectError::NoSuchKey(_))); + assert_eq!(Some("correct-request-id"), err.request_id()); + assert_eq!(Some("correct-request-id"), err.meta().request_id()); + assert_eq!( + Some("correct-extended-request-id"), + err.extended_request_id() + ); + assert_eq!( + Some("correct-extended-request-id"), + err.meta().extended_request_id() + ); +} + +#[test] +fn get_request_id_from_unmodeled_error() { + let resp = http::Response::builder() + .header("x-amz-request-id", "correct-request-id") + .header("x-amz-id-2", "correct-extended-request-id") + .status(500) + .body( + r#" + + SomeUnmodeledError + Something bad happened + /mybucket/myfoto.jpg + incorrect-request-id + "#, + ) + .unwrap(); + let err = GetObject::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect_err("status 500"); + assert!(matches!(err, GetObjectError::Unhandled(_))); + assert_eq!(Some("correct-request-id"), err.request_id()); + assert_eq!(Some("correct-request-id"), err.meta().request_id()); + assert_eq!( + Some("correct-extended-request-id"), + err.extended_request_id() + ); + assert_eq!( + Some("correct-extended-request-id"), + err.meta().extended_request_id() + ); +} + +#[test] +fn get_request_id_from_successful_nonstreaming_response() { + let resp = http::Response::builder() + .header("x-amz-request-id", "correct-request-id") + .header("x-amz-id-2", "correct-extended-request-id") + .status(200) + .body( + r#" + + some-idsome-display-name + + "#, + ) + .unwrap(); + let output = ListBuckets::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect("valid successful response"); + assert_eq!(Some("correct-request-id"), output.request_id()); + assert_eq!( + Some("correct-extended-request-id"), + output.extended_request_id() + ); +} + +#[test] +fn get_request_id_from_successful_streaming_response() { + let resp = http::Response::builder() + .header("x-amz-request-id", "correct-request-id") + .header("x-amz-id-2", "correct-extended-request-id") + .status(200) + .body(SdkBody::from("some streaming file data")) + .unwrap(); + let mut resp = operation::Response::new(resp); + let output = GetObject::new() + .parse_unloaded(&mut resp) + .expect("valid successful response"); + assert_eq!(Some("correct-request-id"), output.request_id()); + assert_eq!( + Some("correct-extended-request-id"), + output.extended_request_id() + ); +} + +// Verify that the conversion from operation error to the top-level service error maintains the request ID +#[test] +fn conversion_to_service_error_maintains_request_id() { + let resp = http::Response::builder() + .header("x-amz-request-id", "correct-request-id") + .header("x-amz-id-2", "correct-extended-request-id") + .status(404) + .body( + r#" + + NoSuchKey + The resource you requested does not exist + /mybucket/myfoto.jpg + incorrect-request-id + "#, + ) + .unwrap(); + let err = GetObject::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect_err("status was 404, this is an error"); + + let service_error: aws_sdk_s3::Error = err.into(); + assert_eq!(Some("correct-request-id"), service_error.request_id()); + assert_eq!( + Some("correct-extended-request-id"), + service_error.extended_request_id() + ); +} diff --git a/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs b/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs index 6fe9895cd3..3b546bbb0b 100644 --- a/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs +++ b/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs @@ -4,24 +4,21 @@ */ use aws_sdk_sts as sts; -use aws_smithy_types::error::Error as ErrorMeta; +use aws_smithy_types::error::ErrorMetadata; use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind}; -use sts::error::{ - AssumeRoleWithWebIdentityError, AssumeRoleWithWebIdentityErrorKind, - IdpCommunicationErrorException, -}; +use sts::error::{AssumeRoleWithWebIdentityError, IdpCommunicationErrorException}; #[tokio::test] async fn idp_comms_err_retryable() { - let error = AssumeRoleWithWebIdentityError::new( - AssumeRoleWithWebIdentityErrorKind::IdpCommunicationErrorException( - IdpCommunicationErrorException::builder() - .message("test") - .build(), - ), - ErrorMeta::builder() - .code("IDPCommunicationError") + let error = AssumeRoleWithWebIdentityError::IdpCommunicationErrorException( + IdpCommunicationErrorException::builder() .message("test") + .meta( + ErrorMetadata::builder() + .code("IDPCommunicationError") + .message("test") + .build(), + ) .build(), ); assert_eq!( diff --git a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs index f48515038a..fe6b028820 100644 --- a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs +++ b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs @@ -4,9 +4,7 @@ */ use async_stream::stream; -use aws_sdk_transcribestreaming::error::{ - AudioStreamError, TranscriptResultStreamError, TranscriptResultStreamErrorKind, -}; +use aws_sdk_transcribestreaming::error::{AudioStreamError, TranscriptResultStreamError}; use aws_sdk_transcribestreaming::model::{ AudioEvent, AudioStream, LanguageCode, MediaEncoding, TranscriptResultStream, }; @@ -76,10 +74,7 @@ async fn test_error() { match output.transcript_result_stream.recv().await { Err(SdkError::ServiceError(context)) => match context.err() { - TranscriptResultStreamError { - kind: TranscriptResultStreamErrorKind::BadRequestException(err), - .. - } => { + TranscriptResultStreamError::BadRequestException(err) => { assert_eq!( Some("A complete signal was sent without the preceding empty frame."), err.message() diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt index 3694cc992d..6a20ba0073 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt @@ -16,14 +16,18 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.ClientEnumGenerator import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.OperationErrorGenerator import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientProtocolLoader import software.amazon.smithy.rust.codegen.client.smithy.transformers.AddErrorMessage import software.amazon.smithy.rust.codegen.client.smithy.transformers.RemoveEventStreamOperations +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider @@ -31,14 +35,13 @@ import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.core.util.CommandFailed +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.isEventStream import software.amazon.smithy.rust.codegen.core.util.letIf @@ -181,14 +184,40 @@ class ClientCodegenVisitor( * This function _does not_ generate any serializers */ override fun structureShape(shape: StructureShape) { - logger.fine("generating a structure...") rustCrate.useShapeWriter(shape) { - StructureGenerator(model, symbolProvider, this, shape).render() - if (!shape.hasTrait()) { - val builderGenerator = BuilderGenerator(codegenContext.model, codegenContext.symbolProvider, shape) - builderGenerator.render(this) - this.implBlock(shape, symbolProvider) { - builderGenerator.renderConvenienceMethod(this) + when (val errorTrait = shape.getTrait()) { + null -> { + StructureGenerator( + model, + symbolProvider, + this, + shape, + codegenDecorator.structureCustomizations(codegenContext, emptyList()), + ).render() + + if (!shape.hasTrait()) { + val builderGenerator = + BuilderGenerator( + codegenContext.model, + codegenContext.symbolProvider, + shape, + codegenDecorator.builderCustomizations(codegenContext, emptyList()), + ) + builderGenerator.render(this) + implBlock(symbolProvider.toSymbol(shape)) { + builderGenerator.renderConvenienceMethod(this) + } + } + } + else -> { + ErrorGenerator( + model, + symbolProvider, + this, + shape, + errorTrait, + codegenDecorator.errorImplCustomizations(codegenContext, emptyList()), + ).render() } } } @@ -220,7 +249,12 @@ class ClientCodegenVisitor( } if (shape.isEventStream()) { rustCrate.withModule(ClientRustModule.Error) { - OperationErrorGenerator(model, symbolProvider, shape).render(this) + OperationErrorGenerator( + model, + symbolProvider, + shape, + codegenDecorator.errorCustomizations(codegenContext, emptyList()), + ).render(this) } } } @@ -230,7 +264,12 @@ class ClientCodegenVisitor( */ override fun operationShape(shape: OperationShape) { rustCrate.withModule(ClientRustModule.Error) { - OperationErrorGenerator(model, symbolProvider, shape).render(this) + OperationErrorGenerator( + model, + symbolProvider, + shape, + codegenDecorator.errorCustomizations(codegenContext, emptyList()), + ).render(this) } } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt index f4034c1b9b..c893ff078f 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.core.smithy.customize.CombinedCoreCodegenDecorator import software.amazon.smithy.rust.codegen.core.smithy.customize.CoreCodegenDecorator @@ -40,6 +41,14 @@ interface ClientCodegenDecorator : CoreCodegenDecorator { baseCustomizations: List, ): List = baseCustomizations + /** + * Hook to customize generated errors. + */ + fun errorCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap = currentProtocols fun endpointCustomizations(codegenContext: ClientCodegenContext): List = listOf() @@ -72,6 +81,13 @@ open class CombinedClientCodegenDecorator(decorators: List, + ): List = combineCustomizations(baseCustomizations) { decorator, customizations -> + decorator.errorCustomizations(codegenContext, customizations) + } + override fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap = combineCustomizations(currentProtocols) { decorator, protocolMap -> decorator.protocols(serviceId, protocolMap) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt index 0f51ba4d58..77f5041a90 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt @@ -67,7 +67,7 @@ class RequiredCustomizations : ClientCodegenDecorator { ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(rustCrate) rustCrate.withModule(ClientRustModule.Types) { - pubUseSmithyTypes(codegenContext.runtimeConfig, codegenContext.model)(this) + pubUseSmithyTypes(codegenContext, codegenContext.model)(this) } } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextConfigCustomization.kt similarity index 100% rename from codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt rename to codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextConfigCustomization.kt diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt index 2a9adeac90..c495c623f5 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt @@ -10,11 +10,11 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfigGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ServiceErrorGenerator import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ProtocolTestGenerator import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.smithy.RustCrate -import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ServiceErrorGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport import software.amazon.smithy.rust.codegen.core.util.inputShape @@ -56,7 +56,11 @@ class ServiceGenerator( } } - ServiceErrorGenerator(clientCodegenContext, operations).render(rustCrate) + ServiceErrorGenerator( + clientCodegenContext, + operations, + decorator.errorCustomizations(clientCodegenContext, emptyList()), + ).render(rustCrate) rustCrate.withModule(ClientRustModule.Config) { ServiceConfigGenerator.withBaseBehavior( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt new file mode 100644 index 0000000000..d275c9b17d --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.generators.error + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.Section + +/** Error customization sections */ +sealed class ErrorSection(name: String) : Section(name) { + /** Use this section to add additional trait implementations to the generated operation errors */ + data class OperationErrorAdditionalTraitImpls(val errorSymbol: Symbol, val allErrors: List) : + ErrorSection("OperationErrorAdditionalTraitImpls") + + /** Use this section to add additional trait implementations to the generated service error */ + class ServiceErrorAdditionalTraitImpls(val allErrors: List) : + ErrorSection("ServiceErrorAdditionalTraitImpls") +} + +/** Customizations for generated errors */ +abstract class ErrorCustomization : NamedCustomization() diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt new file mode 100644 index 0000000000..ffd4c9a287 --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt @@ -0,0 +1,110 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.generators.error + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.ErrorTrait +import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.errorMetadata +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplGenerator + +class ErrorGenerator( + private val model: Model, + private val symbolProvider: RustSymbolProvider, + private val writer: RustWriter, + private val shape: StructureShape, + private val error: ErrorTrait, + private val implCustomizations: List, +) { + private val runtimeConfig = symbolProvider.config().runtimeConfig + + fun render() { + val symbol = symbolProvider.toSymbol(shape) + + StructureGenerator( + model, symbolProvider, writer, shape, + listOf(object : StructureCustomization() { + override fun section(section: StructureSection): Writable = writable { + when (section) { + is StructureSection.AdditionalFields -> { + rust("pub(crate) meta: #T,", errorMetadata(runtimeConfig)) + } + is StructureSection.AdditionalDebugFields -> { + rust("""${section.formatterName}.field("meta", &self.meta);""") + } + } + } + }, + ), + ).render() + + BuilderGenerator( + model, symbolProvider, shape, + listOf( + object : BuilderCustomization() { + override fun section(section: BuilderSection): Writable = writable { + when (section) { + is BuilderSection.AdditionalFields -> { + rust("meta: Option<#T>,", errorMetadata(runtimeConfig)) + } + + is BuilderSection.AdditionalMethods -> { + rustTemplate( + """ + /// Sets error metadata + pub fn meta(mut self, meta: #{error_metadata}) -> Self { + self.meta = Some(meta); + self + } + + /// Sets error metadata + pub fn set_meta(&mut self, meta: Option<#{error_metadata}>) -> &mut Self { + self.meta = meta; + self + } + """, + "error_metadata" to errorMetadata(runtimeConfig), + ) + } + + is BuilderSection.AdditionalFieldsInBuild -> { + rust("meta: self.meta.unwrap_or_default(),") + } + } + } + }, + ), + ).let { builderGen -> + writer.implBlock(symbol) { + builderGen.renderConvenienceMethod(this) + } + builderGen.render(writer) + } + + ErrorImplGenerator(model, symbolProvider, writer, shape, error, implCustomizations).render(CodegenTarget.CLIENT) + + writer.rustBlock("impl #T for ${symbol.name}", RuntimeType.provideErrorMetadataTrait(runtimeConfig)) { + rust("fn meta(&self) -> &#T { &self.meta }", errorMetadata(runtimeConfig)) + } + } +} diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt similarity index 60% rename from codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt rename to codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt index a86c9fce19..5d44738a2c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.core.smithy.generators.error +package software.amazon.smithy.rust.codegen.client.smithy.generators.error import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.Model @@ -25,11 +25,15 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.errorMetadata +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.customize.Section +import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE +import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.toSnakeCase @@ -43,10 +47,11 @@ class OperationErrorGenerator( private val model: Model, private val symbolProvider: RustSymbolProvider, private val operationOrEventStream: Shape, + private val customizations: List, ) { private val runtimeConfig = symbolProvider.config().runtimeConfig private val symbol = symbolProvider.toSymbol(operationOrEventStream) - private val genericError = RuntimeType.genericError(symbolProvider.config().runtimeConfig) + private val errorMetadata = errorMetadata(symbolProvider.config().runtimeConfig) private val createUnhandledError = RuntimeType.smithyHttp(runtimeConfig).resolve("result::CreateUnhandledError") @@ -69,71 +74,98 @@ class OperationErrorGenerator( visibility = Visibility.PUBLIC, ) - writer.rust("/// Error type for the `${symbol.name}` operation.") - meta.render(writer) - writer.rustBlock("struct ${errorSymbol.name}") { - rust( - """ - /// Kind of error that occurred. - pub kind: ${errorSymbol.name}Kind, - /// Additional metadata about the error, including error code, message, and request ID. - pub (crate) meta: #T - """, - RuntimeType.genericError(runtimeConfig), - ) - } - writer.rustBlock("impl #T for ${errorSymbol.name}", createUnhandledError) { - rustBlock("fn create_unhandled_error(source: Box) -> Self") { - rustBlock("Self") { - rust("kind: ${errorSymbol.name}Kind::Unhandled(#T::new(source)),", unhandledError(symbolProvider)) - rust("meta: Default::default()") - } - } - } + // TODO(deprecated): Remove this temporary alias. This was added so that the compiler + // points customers in the right direction when they are upgrading. Unfortunately there's no + // way to provide better backwards compatibility on this change. + val kindDeprecationMessage = "Operation `*Error/*ErrorKind` types were combined into a single `*Error` enum. " + + "The `.kind` field on `*Error` no longer exists and isn't needed anymore (you can just match on the " + + "error directly since it's an enum now)." + writer.rust( + """ + /// Do not use this. + /// + /// $kindDeprecationMessage + ##[deprecated(note = ${kindDeprecationMessage.dq()})] + pub type ${errorSymbol.name}Kind = ${errorSymbol.name}; + """, + ) - writer.rust("/// Types of errors that can occur for the `${symbol.name}` operation.") + writer.rust("/// Error type for the `${errorSymbol.name}` operation.") meta.render(writer) - writer.rustBlock("enum ${errorSymbol.name}Kind") { + writer.rustBlock("enum ${errorSymbol.name}") { errors.forEach { errorVariant -> documentShape(errorVariant, model) deprecatedShape(errorVariant) val errorVariantSymbol = symbolProvider.toSymbol(errorVariant) write("${errorVariantSymbol.name}(#T),", errorVariantSymbol) } - docs(UNHANDLED_ERROR_DOCS) rust( """ + /// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code). Unhandled(#T), """, - unhandledError(symbolProvider), + unhandledError(runtimeConfig), ) } + writer.rustBlock("impl #T for ${errorSymbol.name}", createUnhandledError) { + rustBlock( + """ + fn create_unhandled_error( + source: Box, + meta: Option<#T> + ) -> Self + """, + errorMetadata, + ) { + rust( + """ + Self::Unhandled({ + let mut builder = #T::builder().source(source); + builder.set_meta(meta); + builder.build() + }) + """, + unhandledError(runtimeConfig), + ) + } + } writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.Display) { rustBlock("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result") { - delegateToVariants(errors, errorSymbol) { + delegateToVariants(errors) { writable { rust("_inner.fmt(f)") } } } } - val errorKindT = RuntimeType.errorKind(symbolProvider.config().runtimeConfig) + val errorMetadataTrait = RuntimeType.provideErrorMetadataTrait(runtimeConfig) + writer.rustBlock("impl #T for ${errorSymbol.name}", errorMetadataTrait) { + rustBlock("fn meta(&self) -> &#T", errorMetadata(runtimeConfig)) { + delegateToVariants(errors) { + writable { rust("#T::meta(_inner)", errorMetadataTrait) } + } + } + } + + writer.writeCustomizations(customizations, ErrorSection.OperationErrorAdditionalTraitImpls(errorSymbol, errors)) + + val retryErrorKindT = RuntimeType.retryErrorKind(symbolProvider.config().runtimeConfig) writer.rustBlock( "impl #T for ${errorSymbol.name}", RuntimeType.provideErrorKind(symbolProvider.config().runtimeConfig), ) { rustBlock("fn code(&self) -> Option<&str>") { - rust("${errorSymbol.name}::code(self)") + rust("#T::code(self)", RuntimeType.provideErrorMetadataTrait(runtimeConfig)) } - rustBlock("fn retryable_error_kind(&self) -> Option<#T>", errorKindT) { + rustBlock("fn retryable_error_kind(&self) -> Option<#T>", retryErrorKindT) { val retryableVariants = errors.filter { it.hasTrait() } if (retryableVariants.isEmpty()) { rust("None") } else { - rustBlock("match &self.kind") { + rustBlock("match self") { retryableVariants.forEach { val errorVariantSymbol = symbolProvider.toSymbol(it) - rust("${errorSymbol.name}Kind::${errorVariantSymbol.name}(inner) => Some(inner.retryable_error_kind()),") + rust("Self::${errorVariantSymbol.name}(inner) => Some(inner.retryable_error_kind()),") } rust("_ => None") } @@ -144,65 +176,49 @@ class OperationErrorGenerator( writer.rustBlock("impl ${errorSymbol.name}") { writer.rustTemplate( """ - /// Creates a new `${errorSymbol.name}`. - pub fn new(kind: ${errorSymbol.name}Kind, meta: #{generic_error}) -> Self { - Self { kind, meta } - } - /// Creates the `${errorSymbol.name}::Unhandled` variant from any error type. pub fn unhandled(err: impl Into>) -> Self { - Self { - kind: ${errorSymbol.name}Kind::Unhandled(#{Unhandled}::new(err.into())), - meta: Default::default() - } - } - - /// Creates the `${errorSymbol.name}::Unhandled` variant from a `#{generic_error}`. - pub fn generic(err: #{generic_error}) -> Self { - Self { - meta: err.clone(), - kind: ${errorSymbol.name}Kind::Unhandled(#{Unhandled}::new(err.into())), - } + Self::Unhandled(#{Unhandled}::builder().source(err).build()) } - /// Returns the error message if one is available. - pub fn message(&self) -> Option<&str> { - self.meta.message() - } - - /// Returns error metadata, which includes the error code, message, - /// request ID, and potentially additional information. - pub fn meta(&self) -> &#{generic_error} { - &self.meta - } - - /// Returns the request ID if it's available. - pub fn request_id(&self) -> Option<&str> { - self.meta.request_id() - } - - /// Returns the error code if it's available. - pub fn code(&self) -> Option<&str> { - self.meta.code() + /// Creates the `${errorSymbol.name}::Unhandled` variant from a `#{error_metadata}`. + pub fn generic(err: #{error_metadata}) -> Self { + Self::Unhandled(#{Unhandled}::builder().source(err.clone()).meta(err).build()) } """, - "generic_error" to genericError, + "error_metadata" to errorMetadata, "std_error" to RuntimeType.StdError, - "Unhandled" to unhandledError(symbolProvider), + "Unhandled" to unhandledError(runtimeConfig), ) + writer.docs( + """ + Returns error metadata, which includes the error code, message, + request ID, and potentially additional information. + """, + ) + writer.rustBlock("pub fn meta(&self) -> &#T", errorMetadata) { + rust("use #T;", RuntimeType.provideErrorMetadataTrait(runtimeConfig)) + rustBlock("match self") { + errors.forEach { error -> + val errorVariantSymbol = symbolProvider.toSymbol(error) + rust("Self::${errorVariantSymbol.name}(e) => e.meta(),") + } + rust("Self::Unhandled(e) => e.meta(),") + } + } errors.forEach { error -> val errorVariantSymbol = symbolProvider.toSymbol(error) val fnName = errorVariantSymbol.name.toSnakeCase() - writer.rust("/// Returns `true` if the error kind is `${errorSymbol.name}Kind::${errorVariantSymbol.name}`.") + writer.rust("/// Returns `true` if the error kind is `${errorSymbol.name}::${errorVariantSymbol.name}`.") writer.rustBlock("pub fn is_$fnName(&self) -> bool") { - rust("matches!(&self.kind, ${errorSymbol.name}Kind::${errorVariantSymbol.name}(_))") + rust("matches!(self, Self::${errorVariantSymbol.name}(_))") } } } writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.StdError) { rustBlock("fn source(&self) -> Option<&(dyn #T + 'static)>", RuntimeType.StdError) { - delegateToVariants(errors, errorSymbol) { + delegateToVariants(errors) { writable { rust("Some(_inner)") } @@ -220,11 +236,11 @@ class OperationErrorGenerator( * Generates code to delegate behavior to the variants, for example: * * ```rust - * match &self.kind { - * GreetingWithErrorsError::InvalidGreeting(_inner) => inner.fmt(f), - * GreetingWithErrorsError::ComplexError(_inner) => inner.fmt(f), - * GreetingWithErrorsError::FooError(_inner) => inner.fmt(f), - * GreetingWithErrorsError::Unhandled(_inner) => _inner.fmt(f), + * match self { + * Self::InvalidGreeting(_inner) => inner.fmt(f), + * Self::ComplexError(_inner) => inner.fmt(f), + * Self::FooError(_inner) => inner.fmt(f), + * Self::Unhandled(_inner) => _inner.fmt(f), * } * ``` * @@ -233,20 +249,19 @@ class OperationErrorGenerator( * * The field will always be bound as `_inner`. */ - private fun RustWriter.delegateToVariants( + fun RustWriter.delegateToVariants( errors: List, - symbol: Symbol, handler: (VariantMatch) -> Writable, ) { - rustBlock("match &self.kind") { + rustBlock("match self") { errors.forEach { val errorSymbol = symbolProvider.toSymbol(it) - rust("""${symbol.name}Kind::${errorSymbol.name}(_inner) => """) + rust("""Self::${errorSymbol.name}(_inner) => """) handler(VariantMatch.Modeled(errorSymbol, it))(this) write(",") } val unhandledHandler = handler(VariantMatch.Unhandled) - rustBlock("${symbol.name}Kind::Unhandled(_inner) =>") { + rustBlock("Self::Unhandled(_inner) =>") { unhandledHandler(this) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt similarity index 77% rename from codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt rename to codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt index 606d375c74..ca71dd9033 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.core.smithy.generators.error +package software.amazon.smithy.rust.codegen.client.smithy.generators.error import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.OperationShape @@ -24,7 +24,9 @@ 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.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.transformers.allErrors import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors @@ -43,7 +45,11 @@ import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamE * } * ``` */ -class ServiceErrorGenerator(private val codegenContext: CodegenContext, private val operations: List) { +class ServiceErrorGenerator( + private val codegenContext: CodegenContext, + private val operations: List, + private val customizations: List, +) { private val symbolProvider = codegenContext.symbolProvider private val model = codegenContext.model @@ -73,6 +79,7 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private ) } rust("impl #T for Error {}", RuntimeType.StdError) + writeCustomizations(customizations, ErrorSection.ServiceErrorAdditionalTraitImpls(allErrors)) } crate.lib { rust("pub use error_meta::Error;") } } @@ -105,25 +112,36 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private ) { rustBlock("match err") { rust("#T::ServiceError(context) => Self::from(context.into_err()),", sdkError) - rust("_ => Error::Unhandled(#T::new(err.into())),", unhandledError(symbolProvider)) + rustTemplate( + """ + _ => Error::Unhandled( + #{Unhandled}::builder() + .meta(#{ProvideErrorMetadata}::meta(&err).clone()) + .source(err) + .build() + ), + """, + "Unhandled" to unhandledError(codegenContext.runtimeConfig), + "ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(codegenContext.runtimeConfig), + ) } } } rustBlock("impl From<#T> for Error", errorSymbol) { rustBlock("fn from(err: #T) -> Self", errorSymbol) { - rustBlock("match err.kind") { + rustBlock("match err") { operationErrors.forEach { errorShape -> val errSymbol = symbolProvider.toSymbol(errorShape) rust( - "#TKind::${errSymbol.name}(inner) => Error::${errSymbol.name}(inner),", + "#T::${errSymbol.name}(inner) => Error::${errSymbol.name}(inner),", errorSymbol, ) } rustTemplate( - "#{errorSymbol}Kind::Unhandled(inner) => Error::Unhandled(#{unhandled}::new(inner.into())),", + "#{errorSymbol}::Unhandled(inner) => Error::Unhandled(inner),", "errorSymbol" to errorSymbol, - "unhandled" to unhandledError(symbolProvider), + "unhandled" to unhandledError(codegenContext.runtimeConfig), ) } } @@ -144,8 +162,8 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private val sym = symbolProvider.toSymbol(error) rust("${sym.name}(#T),", sym) } - docs(UNHANDLED_ERROR_DOCS) - rust("Unhandled(#T)", unhandledError(symbolProvider)) + docs("An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).") + rust("Unhandled(#T)", unhandledError(codegenContext.runtimeConfig)) } } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt index b8d6a652e8..d011039b19 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.docLink +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext @@ -19,7 +20,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustom import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolTraitImplGenerator import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol @@ -50,12 +50,12 @@ open class ClientProtocolGenerator( customizations: List, ) { val inputShape = operationShape.inputShape(model) - val builderGenerator = BuilderGenerator(model, symbolProvider, operationShape.inputShape(model)) + val builderGenerator = BuilderGenerator(model, symbolProvider, operationShape.inputShape(model), emptyList()) builderGenerator.render(inputWriter) // impl OperationInputShape { ... } val operationName = symbolProvider.toSymbol(operationShape).name - inputWriter.implBlock(inputShape, symbolProvider) { + inputWriter.implBlock(symbolProvider.toSymbol(inputShape)) { writeCustomizations( customizations, OperationSection.InputImpl(customizations, operationShape, inputShape, protocol), @@ -82,7 +82,7 @@ open class ClientProtocolGenerator( operationWriter.rustBlock("pub struct $operationName") { write("_private: ()") } - operationWriter.implBlock(operationShape, symbolProvider) { + operationWriter.implBlock(symbolProvider.toSymbol(operationShape)) { builderGenerator.renderConvenienceMethod(this) rust("/// Creates a new `$operationName` operation.") diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt index c1db56fa4c..5aa6f2ee56 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt @@ -292,46 +292,50 @@ class ProtocolTestGenerator( val errorSymbol = codegenContext.symbolProvider.symbolForOperationError(operationShape) val errorVariant = codegenContext.symbolProvider.toSymbol(expectedShape).name rust("""let parsed = parsed.expect_err("should be error response");""") - rustBlock("if let #TKind::$errorVariant(actual_error) = parsed.kind", errorSymbol) { - rustTemplate("#{AssertEq}(expected_output, actual_error);", *codegenScope) + rustBlock("if let #T::$errorVariant(parsed) = parsed", errorSymbol) { + compareMembers(expectedShape) } rustBlock("else") { rust("panic!(\"wrong variant: Got: {:?}. Expected: {:?}\", parsed, expected_output);") } } else { rust("let parsed = parsed.unwrap();") - outputShape.members().forEach { member -> - val memberName = codegenContext.symbolProvider.toMemberName(member) - if (member.isStreaming(codegenContext.model)) { - rustTemplate( - """ - #{AssertEq}( - parsed.$memberName.collect().await.unwrap().into_bytes(), - expected_output.$memberName.collect().await.unwrap().into_bytes() - ); - """, - *codegenScope, - ) - } else { - when (codegenContext.model.expectShape(member.target)) { - is DoubleShape, is FloatShape -> { - addUseImports( - RuntimeType.protocolTest(codegenContext.runtimeConfig, "FloatEquals").toSymbol(), - ) - rust( - """ - assert!(parsed.$memberName.float_equals(&expected_output.$memberName), - "Unexpected value for `$memberName` {:?} vs. {:?}", expected_output.$memberName, parsed.$memberName); - """, - ) - } - - else -> - rustTemplate( - """#{AssertEq}(parsed.$memberName, expected_output.$memberName, "Unexpected value for `$memberName`");""", - *codegenScope, - ) + compareMembers(outputShape) + } + } + + private fun RustWriter.compareMembers(shape: StructureShape) { + shape.members().forEach { member -> + val memberName = codegenContext.symbolProvider.toMemberName(member) + if (member.isStreaming(codegenContext.model)) { + rustTemplate( + """ + #{AssertEq}( + parsed.$memberName.collect().await.unwrap().into_bytes(), + expected_output.$memberName.collect().await.unwrap().into_bytes() + ); + """, + *codegenScope, + ) + } else { + when (codegenContext.model.expectShape(member.target)) { + is DoubleShape, is FloatShape -> { + addUseImports( + RuntimeType.protocolTest(codegenContext.runtimeConfig, "FloatEquals").toSymbol(), + ) + rust( + """ + assert!(parsed.$memberName.float_equals(&expected_output.$memberName), + "Unexpected value for `$memberName` {:?} vs. {:?}", expected_output.$memberName, parsed.$memberName); + """, + ) } + + else -> + rustTemplate( + """#{AssertEq}(parsed.$memberName, expected_output.$memberName, "Unexpected value for `$memberName`");""", + *codegenScope, + ) } } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt index f1cc486575..2bddfb00e4 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt @@ -115,6 +115,7 @@ class HttpBoundProtocolTraitImplGenerator( impl #{ParseStrict} for $operationName { type Output = std::result::Result<#{O}, #{E}>; fn parse(&self, response: &#{http}::Response<#{Bytes}>) -> Self::Output { + #{BeforeParseResponse} if !response.status().is_success() && response.status().as_u16() != $successCode { #{parse_error}(response) } else { @@ -125,8 +126,11 @@ class HttpBoundProtocolTraitImplGenerator( *codegenScope, "O" to outputSymbol, "E" to symbolProvider.symbolForOperationError(operationShape), - "parse_error" to parseError(operationShape), + "parse_error" to parseError(operationShape, customizations), "parse_response" to parseResponse(operationShape, customizations), + "BeforeParseResponse" to writable { + writeCustomizations(customizations, OperationSection.BeforeParseResponse(customizations, "response")) + }, ) } @@ -157,12 +161,12 @@ class HttpBoundProtocolTraitImplGenerator( "O" to outputSymbol, "E" to symbolProvider.symbolForOperationError(operationShape), "parse_streaming_response" to parseStreamingResponse(operationShape, customizations), - "parse_error" to parseError(operationShape), + "parse_error" to parseError(operationShape, customizations), *codegenScope, ) } - private fun parseError(operationShape: OperationShape): RuntimeType { + private fun parseError(operationShape: OperationShape, customizations: List): RuntimeType { val fnName = "parse_${operationShape.id.name.toSnakeCase()}_error" val outputShape = operationShape.outputShape(model) val outputSymbol = symbolProvider.toSymbol(outputShape) @@ -175,11 +179,17 @@ class HttpBoundProtocolTraitImplGenerator( "O" to outputSymbol, "E" to errorSymbol, ) { + Attribute.AllowUnusedMut.render(this) rust( - "let generic = #T(response).map_err(#T::unhandled)?;", - protocol.parseHttpGenericError(operationShape), + "let mut generic_builder = #T(response).map_err(#T::unhandled)?;", + protocol.parseHttpErrorMetadata(operationShape), errorSymbol, ) + writeCustomizations( + customizations, + OperationSection.PopulateErrorMetadataExtras(customizations, "generic_builder", "response"), + ) + rust("let generic = generic_builder.build();") if (operationShape.operationErrors(model).isNotEmpty()) { rustTemplate( """ @@ -199,8 +209,8 @@ class HttpBoundProtocolTraitImplGenerator( val variantName = symbolProvider.toSymbol(model.expectShape(error.id)).name val errorCode = httpBindingResolver.errorCode(errorShape).dq() withBlock( - "$errorCode => #1T { meta: generic, kind: #1TKind::$variantName({", - "})},", + "$errorCode => #1T::$variantName({", + "}),", errorSymbol, ) { Attribute.AllowUnusedMut.render(this) @@ -211,7 +221,14 @@ class HttpBoundProtocolTraitImplGenerator( errorShape, httpBindingResolver.errorResponseBindings(errorShape), errorSymbol, - listOf(), + listOf(object : OperationCustomization() { + override fun section(section: OperationSection): Writable = writable { + if (section is OperationSection.MutateOutput) { + rust("let output = output.meta(generic);") + } + } + }, + ), ) } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt index 7e69ce6995..02d61d9905 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt @@ -19,7 +19,7 @@ import java.util.logging.Logger * * Not all errors are modeled with an error message field. However, in many cases, the server can still send an error. * If an error, specifically, a structure shape with the error trait does not have a member `message` or `Message`, - * this transformer will add a `message` member targeting a string. + * this transformer will add a `Message` member targeting a string. * * This ensures that we always generate a modeled error message field enabling end users to easily extract the error * message when present. @@ -37,7 +37,7 @@ object AddErrorMessage { val addMessageField = shape.hasTrait() && shape is StructureShape && shape.errorMessageMember() == null if (addMessageField && shape is StructureShape) { logger.info("Adding message field to ${shape.id}") - shape.toBuilder().addMember("message", ShapeId.from("smithy.api#String")).build() + shape.toBuilder().addMember("Message", ShapeId.from("smithy.api#String")).build() } else { shape } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt similarity index 97% rename from codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt rename to codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt index cb96490209..78d77d01db 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt @@ -14,7 +14,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.unitTest -class ClientContextParamsDecoratorTest { +class ClientContextConfigCustomizationTest { val model = """ namespace test use smithy.rules#clientContextParams diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt index 2a9787f69d..12285e4364 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt @@ -13,10 +13,10 @@ import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace @@ -50,10 +50,10 @@ internal class EndpointTraitBindingsTest { } """.asSmithyModel() val operationShape: OperationShape = model.lookup("test#GetStatus") - val sym = testSymbolProvider(model) + val symbolProvider = testSymbolProvider(model) val endpointBindingGenerator = EndpointTraitBindings( model, - sym, + symbolProvider, TestRuntimeConfig, operationShape, operationShape.expectTrait(EndpointTrait::class.java), @@ -67,7 +67,7 @@ internal class EndpointTraitBindingsTest { } """, ) - implBlock(model.lookup("test#GetStatusInput"), sym) { + implBlock(symbolProvider.toSymbol(model.lookup("test#GetStatusInput"))) { rustBlock( "fn endpoint_prefix(&self) -> std::result::Result<#T::endpoint::EndpointPrefix, #T>", RuntimeType.smithyHttp(TestRuntimeConfig), diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt new file mode 100644 index 0000000000..cca1dcb3d5 --- /dev/null +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.generators.error + +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 + +class ErrorGeneratorTest { + val model = + """ + namespace com.test + use aws.protocols#awsJson1_1 + + @awsJson1_1 + service TestService { + operations: [TestOp] + } + + operation TestOp { + errors: [MyError] + } + + @error("server") + @retryable + structure MyError { + message: String + } + """.asSmithyModel() + + @Test + fun `generate error structure and builder`() { + clientIntegrationTest(model) { _, rustCrate -> + rustCrate.withFile("src/error.rs") { + rust( + """ + ##[test] + fn test_error_generator() { + use aws_smithy_types::error::metadata::{ErrorMetadata, ProvideErrorMetadata}; + use aws_smithy_types::retry::ErrorKind; + + let err = MyError::builder() + .meta(ErrorMetadata::builder().code("test").message("testmsg").build()) + .message("testmsg") + .build(); + assert_eq!(err.retryable_error_kind(), ErrorKind::ServerError); + assert_eq!("test", err.meta().code().unwrap()); + assert_eq!("testmsg", err.meta().message().unwrap()); + assert_eq!("testmsg", err.message().unwrap()); + } + """, + ) + } + } + } +} diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt similarity index 94% rename from codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGeneratorTest.kt rename to codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt index f6f815a443..f9faae87f2 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt @@ -3,16 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.core.smithy.generators.error +package software.amazon.smithy.rust.codegen.client.smithy.generators.error import org.junit.jupiter.api.Test import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.renderWithModelBuilder -import software.amazon.smithy.rust.codegen.core.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.lookup @@ -53,7 +53,7 @@ class OperationErrorGeneratorTest { listOf("FooException", "ComplexError", "InvalidGreeting", "Deprecated").forEach { model.lookup("error#$it").renderWithModelBuilder(model, symbolProvider, this) } - OperationErrorGenerator(model, symbolProvider, model.lookup("error#Greeting")).render(this) + OperationErrorGenerator(model, symbolProvider, model.lookup("error#Greeting"), emptyList()).render(this) unitTest( name = "generates_combined_error_enums", diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt new file mode 100644 index 0000000000..1cbb274cfb --- /dev/null +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.generators.error + +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 + +internal class ServiceErrorGeneratorTest { + @Test + fun `top level errors are send + sync`() { + val model = """ + namespace com.example + + use aws.protocols#restJson1 + + @restJson1 + service HelloService { + operations: [SayHello], + version: "1" + } + + @http(uri: "/", method: "POST") + operation SayHello { + input: EmptyStruct, + output: EmptyStruct, + errors: [SorryBusy, CanYouRepeatThat, MeDeprecated] + } + + structure EmptyStruct { } + + @error("server") + structure SorryBusy { } + + @error("client") + structure CanYouRepeatThat { } + + @error("client") + @deprecated + structure MeDeprecated { } + """.asSmithyModel() + + clientIntegrationTest(model) { codegenContext, rustCrate -> + rustCrate.integrationTest("validate_errors") { + rust( + """ + fn check_send_sync() {} + + ##[test] + fn service_errors_are_send_sync() { + check_send_sync::<${codegenContext.moduleUseName()}::Error>() + } + """, + ) + } + } + } +} diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt index b29e23bc53..36341b4cdd 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt @@ -61,7 +61,8 @@ private class TestProtocolTraitImplGenerator( fn parse(&self, _response: &#{Response}<#{Bytes}>) -> Self::Output { ${operationWriter.escape(correctResponse)} } - }""", + } + """, "parse_strict" to RuntimeType.parseStrictResponse(codegenContext.runtimeConfig), "Output" to symbolProvider.toSymbol(operationShape.outputShape(codegenContext.model)), "Error" to symbolProvider.symbolForOperationError(operationShape), diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt index 8d07242211..34efa20475 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt @@ -13,18 +13,21 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.error.OperationErrorGenerator import software.amazon.smithy.rust.codegen.client.testutil.clientTestRustSettings import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestRequirements +import software.amazon.smithy.rust.codegen.core.util.expectTrait import java.util.stream.Stream class TestCasesProvider : ArgumentsProvider { @@ -52,9 +55,9 @@ abstract class ClientEventStreamBaseRequirements : EventStreamTestRequirements() + ErrorGenerator( + codegenContext.model, + codegenContext.symbolProvider, + writer, + shape, + errorTrait, + emptyList(), + ).render() } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt index efe9ae7cc8..34a4382302 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt @@ -40,6 +40,8 @@ class RustReservedWordSymbolProvider(private val base: RustSymbolProvider, priva "make_operation" -> "make_operation_value" "presigned" -> "presigned_value" "customize" -> "customize_value" + // To avoid conflicts with the error metadata `meta` field + "meta" -> "meta_value" else -> baseName } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index 25fb313b27..67eb7f4b7a 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -380,6 +380,13 @@ private fun Element.changeInto(tagName: String) { replaceWith(Element(tagName).also { elem -> elem.appendChildren(childNodesCopy()) }) } +/** Write an `impl` block for the given symbol */ +fun RustWriter.implBlock(symbol: Symbol, block: Writable) { + rustBlock("impl ${symbol.name}") { + block() + } +} + /** * Write _exactly_ the text as written into the code writer without newlines or formatting */ diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/EventStreamSymbolProvider.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/EventStreamSymbolProvider.kt index d1c3afd9f4..9a75974d24 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/EventStreamSymbolProvider.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/EventStreamSymbolProvider.kt @@ -10,7 +10,9 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.UnionShape 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.RustType import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter @@ -22,6 +24,11 @@ import software.amazon.smithy.rust.codegen.core.util.isEventStream import software.amazon.smithy.rust.codegen.core.util.isInputEventStream import software.amazon.smithy.rust.codegen.core.util.isOutputEventStream +fun UnionShape.eventStreamErrorSymbol(symbolProvider: RustSymbolProvider): RuntimeType { + val unionSymbol = symbolProvider.toSymbol(this) + return RustModule.Error.toType().resolve("${unionSymbol.name}Error") +} + /** * Wrapping symbol provider to wrap modeled types with the aws-smithy-http Event Stream send/receive types. */ 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 b0752430c2..50887da953 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 @@ -270,11 +270,12 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun classifyRetry(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("retry::ClassifyRetry") fun dateTime(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("DateTime") fun document(runtimeConfig: RuntimeConfig): RuntimeType = smithyTypes(runtimeConfig).resolve("Document") - fun errorKind(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("retry::ErrorKind") - fun eventStreamReceiver(runtimeConfig: RuntimeConfig): RuntimeType = - smithyHttp(runtimeConfig).resolve("event_stream::Receiver") - - fun genericError(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("Error") + fun retryErrorKind(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("retry::ErrorKind") + fun eventStreamReceiver(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("event_stream::Receiver") + fun errorMetadata(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("error::ErrorMetadata") + fun errorMetadataBuilder(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("error::metadata::Builder") + 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 labelFormat(runtimeConfig: RuntimeConfig, func: String) = smithyHttp(runtimeConfig).resolve("label::$func") fun operation(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation::Operation") diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtra.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtra.kt index ce4b7085a5..0683182b51 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtra.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtra.kt @@ -11,10 +11,12 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.writable -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.util.hasEventStreamMember import software.amazon.smithy.rust.codegen.core.util.hasStreamingMember +import software.amazon.smithy.rust.codegen.core.util.letIf private data class PubUseType( val type: RuntimeType, @@ -46,13 +48,18 @@ private fun hasBlobs(model: Model): Boolean = structUnionMembersMatchPredicate(m private fun hasDateTimes(model: Model): Boolean = structUnionMembersMatchPredicate(model, Shape::isTimestampShape) /** Returns a list of types that should be re-exported for the given model */ -internal fun pubUseTypes(runtimeConfig: RuntimeConfig, model: Model): List { +internal fun pubUseTypes(codegenContext: CodegenContext, model: Model): List { + val runtimeConfig = codegenContext.runtimeConfig return ( listOf( PubUseType(RuntimeType.blob(runtimeConfig), ::hasBlobs), PubUseType(RuntimeType.dateTime(runtimeConfig), ::hasDateTimes), ) + RuntimeType.smithyTypes(runtimeConfig).let { types -> listOf(PubUseType(types.resolve("error::display::DisplayErrorContext")) { true }) + // Only re-export `ProvideErrorMetadata` for clients + .letIf(codegenContext.target == CodegenTarget.CLIENT) { list -> + list + listOf(PubUseType(types.resolve("error::metadata::ProvideErrorMetadata")) { true }) + } } + RuntimeType.smithyHttp(runtimeConfig).let { http -> listOf( PubUseType(http.resolve("result::SdkError")) { true }, @@ -64,8 +71,8 @@ internal fun pubUseTypes(runtimeConfig: RuntimeConfig, model: Model): List rust("pub use #T;", type) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/CoreCodegenDecorator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/CoreCodegenDecorator.kt index c886c4ed97..756f0d132c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/CoreCodegenDecorator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/CoreCodegenDecorator.kt @@ -9,8 +9,11 @@ import software.amazon.smithy.build.PluginContext import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.rust.codegen.core.smithy.RustCrate +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.ManifestCustomizations +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization import software.amazon.smithy.rust.codegen.core.util.deepMergeWith import java.util.ServiceLoader import java.util.logging.Logger @@ -62,6 +65,31 @@ interface CoreCodegenDecorator { baseCustomizations: List, ): List = baseCustomizations + /** + * Hook to customize structures generated by `StructureGenerator`. + */ + fun structureCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + + // TODO(https://github.com/awslabs/smithy-rs/issues/1401): Move builder customizations into `ClientCodegenDecorator` + /** + * Hook to customize generated builders. + */ + fun builderCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + + /** + * Hook to customize error struct `impl` blocks. + */ + fun errorImplCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + /** * Extra sections allow one decorator to influence another. This is intended to be used by querying the `rootDecorator` */ @@ -76,14 +104,6 @@ abstract class CombinedCoreCodegenDecorator { private val orderedDecorators = decorators.sortedBy { it.order } - final override fun libRsCustomizations( - codegenContext: CodegenContext, - baseCustomizations: List, - ): List = - combineCustomizations(baseCustomizations) { decorator, customizations -> - decorator.libRsCustomizations(codegenContext, customizations) - } - final override fun crateManifestCustomizations(codegenContext: CodegenContext): ManifestCustomizations = combineCustomizations(emptyMap()) { decorator, customizations -> customizations.deepMergeWith(decorator.crateManifestCustomizations(codegenContext)) @@ -98,6 +118,35 @@ abstract class CombinedCoreCodegenDecorator, + ): List = + combineCustomizations(baseCustomizations) { decorator, customizations -> + decorator.libRsCustomizations(codegenContext, customizations) + } + + override fun structureCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = combineCustomizations(baseCustomizations) { decorator, customizations -> + decorator.structureCustomizations(codegenContext, customizations) + } + + override fun builderCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = combineCustomizations(baseCustomizations) { decorator, customizations -> + decorator.builderCustomizations(codegenContext, customizations) + } + + override fun errorImplCustomizations( + codegenContext: CodegenContext, + baseCustomizations: List, + ): List = combineCustomizations(baseCustomizations) { decorator, customizations -> + decorator.errorImplCustomizations(codegenContext, customizations) + } + final override fun extraSections(codegenContext: CodegenContext): List = addCustomizations { decorator -> decorator.extraSections(codegenContext) } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/OperationCustomization.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/OperationCustomization.kt index d11f98a5c7..7ab4586c31 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/OperationCustomization.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customize/OperationCustomization.kt @@ -53,6 +53,26 @@ sealed class OperationSection(name: String) : Section(name) { override val customizations: List, val operationShape: OperationShape, ) : OperationSection("MutateOutput") + + /** + * Allows for adding additional properties to the `extras` field on the + * `aws_smithy_types::error::ErrorMetadata`. + */ + data class PopulateErrorMetadataExtras( + override val customizations: List, + /** Name of the generic error builder (for referring to it in Rust code) */ + val builderName: String, + /** Name of the response (for referring to it in Rust code) */ + val responseName: String, + ) : OperationSection("PopulateErrorMetadataExtras") + + /** + * Hook to add custom code right before the response is parsed. + */ + data class BeforeParseResponse( + override val customizations: List, + val responseName: String, + ) : OperationSection("BeforeParseResponse") } abstract class OperationCustomization : NamedCustomization() { diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt index e6289d8984..deff9c1fb6 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt @@ -36,6 +36,9 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.canUseDefault +import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.Section +import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.defaultValue import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.core.smithy.isOptional @@ -52,6 +55,27 @@ import software.amazon.smithy.rust.codegen.core.util.toSnakeCase // TODO(https://github.com/awslabs/smithy-rs/issues/1401) This builder generator is only used by the client. // Move this entire file, and its tests, to `codegen-client`. +/** BuilderGenerator customization sections */ +sealed class BuilderSection(name: String) : Section(name) { + abstract val shape: StructureShape + + /** Hook to add additional fields to the builder */ + data class AdditionalFields(override val shape: StructureShape) : BuilderSection("AdditionalFields") + + /** Hook to add additional methods to the builder */ + data class AdditionalMethods(override val shape: StructureShape) : BuilderSection("AdditionalMethods") + + /** Hook to add additional fields to the `build()` method */ + data class AdditionalFieldsInBuild(override val shape: StructureShape) : BuilderSection("AdditionalFieldsInBuild") + + /** Hook to add additional fields to the `Debug` impl */ + data class AdditionalDebugFields(override val shape: StructureShape, val formatterName: String) : + BuilderSection("AdditionalDebugFields") +} + +/** Customizations for BuilderGenerator */ +abstract class BuilderCustomization : NamedCustomization() + fun builderSymbolFn(symbolProvider: RustSymbolProvider): (StructureShape) -> Symbol = { structureShape -> structureShape.builderSymbol(symbolProvider) } @@ -92,6 +116,7 @@ class BuilderGenerator( private val model: Model, private val symbolProvider: RustSymbolProvider, private val shape: StructureShape, + private val customizations: List, ) { companion object { /** @@ -216,6 +241,7 @@ class BuilderGenerator( val memberSymbol = symbolProvider.toSymbol(member).makeOptional() renderBuilderMember(this, memberName, memberSymbol) } + writeCustomizations(customizations, BuilderSection.AdditionalFields(shape)) } writer.rustBlock("impl $builderName") { @@ -235,6 +261,7 @@ class BuilderGenerator( renderBuilderMemberSetterFn(this, outerType, member, memberName) } + writeCustomizations(customizations, BuilderSection.AdditionalMethods(shape)) renderBuildFn(this) } } @@ -251,6 +278,7 @@ class BuilderGenerator( "formatter.field(${memberName.dq()}, &$fieldValue);", ) } + writeCustomizations(customizations, BuilderSection.AdditionalDebugFields(shape, "formatter")) rust("formatter.finish()") } } @@ -329,6 +357,7 @@ class BuilderGenerator( } } } + writeCustomizations(customizations, BuilderSection.AdditionalFieldsInBuild(shape)) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt index 7e88d3d1fa..46f0c31544 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt @@ -6,16 +6,13 @@ package software.amazon.smithy.rust.codegen.core.smithy.generators import software.amazon.smithy.codegen.core.Symbol -import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.MemberShape -import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.SensitiveTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.asDeref import software.amazon.smithy.rust.codegen.core.rustlang.asRef import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape @@ -25,43 +22,55 @@ import software.amazon.smithy.rust.codegen.core.rustlang.isDeref import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.Section +import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata -import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorGenerator import software.amazon.smithy.rust.codegen.core.smithy.renamedFrom import software.amazon.smithy.rust.codegen.core.smithy.rustType import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary -fun RustWriter.implBlock(structureShape: Shape, symbolProvider: SymbolProvider, block: Writable) { - rustBlock("impl ${symbolProvider.toSymbol(structureShape).name}") { - block() - } +/** StructureGenerator customization sections */ +sealed class StructureSection(name: String) : Section(name) { + abstract val shape: StructureShape + + /** Hook to add additional fields to the structure */ + data class AdditionalFields(override val shape: StructureShape) : StructureSection("AdditionalFields") + + /** Hook to add additional fields to the `Debug` impl */ + data class AdditionalDebugFields(override val shape: StructureShape, val formatterName: String) : + StructureSection("AdditionalDebugFields") + + /** Hook to add additional trait impls to the structure */ + data class AdditionalTraitImpls(override val shape: StructureShape, val structName: String) : + StructureSection("AdditionalTraitImpls") } +/** Customizations for StructureGenerator */ +abstract class StructureCustomization : NamedCustomization() + open class StructureGenerator( val model: Model, private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StructureShape, + private val customizations: List, ) { private val errorTrait = shape.getTrait() protected val members: List = shape.allMembers.values.toList() - protected val accessorMembers: List = when (errorTrait) { + private val accessorMembers: List = when (errorTrait) { null -> members // Let the ErrorGenerator render the error message accessor if this is an error struct else -> members.filter { "message" != symbolProvider.toMemberName(it) } } - protected val name = symbolProvider.toSymbol(shape).name + protected val name: String = symbolProvider.toSymbol(shape).name - fun render(forWhom: CodegenTarget = CodegenTarget.CLIENT) { + fun render() { renderStructure() - errorTrait?.also { errorTrait -> - ErrorGenerator(model, symbolProvider, writer, shape, errorTrait).render(forWhom) - } } /** @@ -98,6 +107,7 @@ open class StructureGenerator( "formatter.field(${memberName.dq()}, &$fieldValue);", ) } + writeCustomizations(customizations, StructureSection.AdditionalDebugFields(shape, "formatter")) rust("formatter.finish()") } } @@ -150,12 +160,15 @@ open class StructureGenerator( writer.forEachMember(members) { member, memberName, memberSymbol -> renderStructureMember(writer, member, memberName, memberSymbol) } + writeCustomizations(customizations, StructureSection.AdditionalFields(shape)) } renderStructureImpl() if (!containerMeta.hasDebugDerive()) { renderDebugImpl() } + + writer.writeCustomizations(customizations, StructureSection.AdditionalTraitImpls(shape, name)) } protected fun RustWriter.forEachMember( diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGenerator.kt similarity index 83% rename from codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorGenerator.kt rename to codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGenerator.kt index 071a5bd89a..93bfb3d9b2 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGenerator.kt @@ -5,6 +5,7 @@ package software.amazon.smithy.rust.codegen.core.smithy.generators.error +import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.traits.ErrorTrait @@ -23,6 +24,9 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.StdError import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization +import software.amazon.smithy.rust.codegen.core.smithy.customize.Section +import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.smithy.mapRustType import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.ValueExpression @@ -34,22 +38,31 @@ import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rust.codegen.core.util.shouldRedact +/** Error customization sections */ +sealed class ErrorImplSection(name: String) : Section(name) { + /** Use this section to add additional trait implementations to the generated error structures */ + class ErrorAdditionalTraitImpls(val errorType: Symbol) : ErrorImplSection("ErrorAdditionalTraitImpls") +} + +/** Customizations for generated errors */ +abstract class ErrorImplCustomization : NamedCustomization() + sealed class ErrorKind { abstract fun writable(runtimeConfig: RuntimeConfig): Writable object Throttling : ErrorKind() { override fun writable(runtimeConfig: RuntimeConfig) = - writable { rust("#T::ThrottlingError", RuntimeType.errorKind(runtimeConfig)) } + writable { rust("#T::ThrottlingError", RuntimeType.retryErrorKind(runtimeConfig)) } } object Client : ErrorKind() { override fun writable(runtimeConfig: RuntimeConfig) = - writable { rust("#T::ClientError", RuntimeType.errorKind(runtimeConfig)) } + writable { rust("#T::ClientError", RuntimeType.retryErrorKind(runtimeConfig)) } } object Server : ErrorKind() { override fun writable(runtimeConfig: RuntimeConfig) = - writable { rust("#T::ServerError", RuntimeType.errorKind(runtimeConfig)) } + writable { rust("#T::ServerError", RuntimeType.retryErrorKind(runtimeConfig)) } } } @@ -69,19 +82,22 @@ fun StructureShape.modeledRetryKind(errorTrait: ErrorTrait): ErrorKind? { } } -class ErrorGenerator( +class ErrorImplGenerator( private val model: Model, private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StructureShape, private val error: ErrorTrait, + private val customizations: List, ) { + private val runtimeConfig = symbolProvider.config().runtimeConfig + fun render(forWhom: CodegenTarget = CodegenTarget.CLIENT) { val symbol = symbolProvider.toSymbol(shape) val messageShape = shape.errorMessageMember() - val errorKindT = RuntimeType.errorKind(symbolProvider.config().runtimeConfig) + val errorKindT = RuntimeType.retryErrorKind(runtimeConfig) writer.rustBlock("impl ${symbol.name}") { - val retryKindWriteable = shape.modeledRetryKind(error)?.writable(symbolProvider.config().runtimeConfig) + val retryKindWriteable = shape.modeledRetryKind(error)?.writable(runtimeConfig) if (retryKindWriteable != null) { rust("/// Returns `Some(${errorKindT.name})` if the error is retryable. Otherwise, returns `None`.") rustBlock("pub fn retryable_error_kind(&self) -> #T", errorKindT) { @@ -153,6 +169,9 @@ class ErrorGenerator( write("Ok(())") } } + writer.write("impl #T for ${symbol.name} {}", StdError) + + writer.writeCustomizations(customizations, ErrorImplSection.ErrorAdditionalTraitImpls(symbol)) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt deleted file mode 100644 index 8080377c62..0000000000 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.core.smithy.generators.error - -import software.amazon.smithy.rust.codegen.core.rustlang.RustModule -import software.amazon.smithy.rust.codegen.core.rustlang.docs -import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider - -internal const val UNHANDLED_ERROR_DOCS = - """ - An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code). - - When logging an error from the SDK, it is recommended that you either wrap the error in - [`DisplayErrorContext`](crate::types::DisplayErrorContext), use another - error reporter library that visits the error's cause/source chain, or call - [`Error::source`](std::error::Error::source) for more details about the underlying cause. - """ - -internal fun unhandledError(symbolProvider: RustSymbolProvider): RuntimeType = - RuntimeType.forInlineFun("Unhandled", RustModule.Error) { - docs(UNHANDLED_ERROR_DOCS) - rustTemplate( - """ - ##[derive(Debug)] - pub struct Unhandled { - source: Box, - } - impl Unhandled { - ##[allow(unused)] - pub(crate) fn new(source: Box) -> Self { - Self { source } - } - } - impl std::fmt::Display for Unhandled { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "unhandled error") - } - } - impl #{StdError} for Unhandled { - fn source(&self) -> Option<&(dyn #{StdError} + 'static)> { - Some(self.source.as_ref() as _) - } - } - """, - "StdError" to RuntimeType.StdError, - ) - } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt index ff60da3ebe..6ed98c4de3 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt @@ -128,7 +128,7 @@ open class AwsJson( private val runtimeConfig = codegenContext.runtimeConfig private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.Http.resolve("HeaderMap"), "JsonError" to CargoDependency.smithyJson(runtimeConfig).toType() .resolve("deserialize::error::DeserializeError"), @@ -159,25 +159,25 @@ open class AwsJson( override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator = AwsJsonSerializerGenerator(codegenContext, httpBindingResolver) - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_http_generic_error", jsonDeserModule) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", jsonDeserModule) { rustTemplate( """ - pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{JsonError}> { - #{json_errors}::parse_generic_error(response.body(), response.headers()) + pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> { + #{json_errors}::parse_error_metadata(response.body(), response.headers()) } """, *errorScope, ) } - override fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_event_stream_generic_error", jsonDeserModule) { + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_event_stream_error_metadata", jsonDeserModule) { rustTemplate( """ - pub fn parse_event_stream_generic_error(payload: &#{Bytes}) -> Result<#{Error}, #{JsonError}> { + pub fn parse_event_stream_error_metadata(payload: &#{Bytes}) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> { // Note: HeaderMap::new() doesn't allocate - #{json_errors}::parse_generic_error(payload, &#{HeaderMap}::new()) + #{json_errors}::parse_error_metadata(payload, &#{HeaderMap}::new()) } """, *errorScope, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQuery.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQuery.kt index 6bb7a62a58..f5728510e0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQuery.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsQuery.kt @@ -45,7 +45,7 @@ class AwsQueryProtocol(private val codegenContext: CodegenContext) : Protocol { private val awsQueryErrors: RuntimeType = RuntimeType.wrappedXmlErrors(runtimeConfig) private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.HttpHeaderMap, "Response" to RuntimeType.HttpResponse, "XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"), @@ -65,23 +65,23 @@ class AwsQueryProtocol(private val codegenContext: CodegenContext) : Protocol { override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator = AwsQuerySerializerGenerator(codegenContext) - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_http_generic_error", xmlDeserModule) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(response.body().as_ref())", awsQueryErrors) + rust("#T::parse_error_metadata(response.body().as_ref())", awsQueryErrors) } } - override fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_event_stream_generic_error", xmlDeserModule) { + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_event_stream_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_event_stream_generic_error(payload: &#{Bytes}) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_event_stream_error_metadata(payload: &#{Bytes}) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(payload.as_ref())", awsQueryErrors) + rust("#T::parse_error_metadata(payload.as_ref())", awsQueryErrors) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Ec2Query.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Ec2Query.kt index c388b8e85b..473ec06f52 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Ec2Query.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Ec2Query.kt @@ -27,7 +27,7 @@ class Ec2QueryProtocol(private val codegenContext: CodegenContext) : Protocol { private val ec2QueryErrors: RuntimeType = RuntimeType.ec2QueryErrors(runtimeConfig) private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.HttpHeaderMap, "Response" to RuntimeType.HttpResponse, "XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"), @@ -56,23 +56,23 @@ class Ec2QueryProtocol(private val codegenContext: CodegenContext) : Protocol { override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator = Ec2QuerySerializerGenerator(codegenContext) - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_http_generic_error", xmlDeserModule) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(response.body().as_ref())", ec2QueryErrors) + rust("#T::parse_error_metadata(response.body().as_ref())", ec2QueryErrors) } } - override fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_event_stream_generic_error", xmlDeserModule) { + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_event_stream_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_event_stream_generic_error(payload: &#{Bytes}) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_event_stream_error_metadata(payload: &#{Bytes}) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(payload.as_ref())", ec2QueryErrors) + rust("#T::parse_error_metadata(payload.as_ref())", ec2QueryErrors) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Protocol.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Protocol.kt index c5d93ae3b0..1b3e4b4a9a 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Protocol.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/Protocol.kt @@ -46,21 +46,21 @@ interface Protocol { /** * Generates a function signature like the following: * ```rust - * fn parse_http_generic_error(response: &Response) -> aws_smithy_types::error::Error + * fn parse_http_error_metadata(response: &Response) -> aws_smithy_types::error::Builder * ``` */ - fun parseHttpGenericError(operationShape: OperationShape): RuntimeType + fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType /** * Generates a function signature like the following: * ```rust - * fn parse_event_stream_generic_error(payload: &Bytes) -> aws_smithy_types::error::Error + * fn parse_event_stream_error_metadata(payload: &Bytes) -> aws_smithy_types::error::Error * ``` * * Event Stream generic errors are almost identical to HTTP generic errors, except that * there are no response headers or statuses available to further inform the error parsing. */ - fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType + fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType } typealias ProtocolMap = Map> diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt index cbcd2d511b..665d9dee85 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt @@ -66,7 +66,7 @@ open class RestJson(val codegenContext: CodegenContext) : Protocol { private val runtimeConfig = codegenContext.runtimeConfig private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.Http.resolve("HeaderMap"), "JsonError" to CargoDependency.smithyJson(runtimeConfig).toType() .resolve("deserialize::error::DeserializeError"), @@ -102,25 +102,25 @@ open class RestJson(val codegenContext: CodegenContext) : Protocol { override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator = JsonSerializerGenerator(codegenContext, httpBindingResolver, ::restJsonFieldName) - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_http_generic_error", jsonDeserModule) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", jsonDeserModule) { rustTemplate( """ - pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{JsonError}> { - #{json_errors}::parse_generic_error(response.body(), response.headers()) + pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> { + #{json_errors}::parse_error_metadata(response.body(), response.headers()) } """, *errorScope, ) } - override fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_event_stream_generic_error", jsonDeserModule) { + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_event_stream_error_metadata", jsonDeserModule) { rustTemplate( """ - pub fn parse_event_stream_generic_error(payload: &#{Bytes}) -> Result<#{Error}, #{JsonError}> { + pub fn parse_event_stream_error_metadata(payload: &#{Bytes}) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> { // Note: HeaderMap::new() doesn't allocate - #{json_errors}::parse_generic_error(payload, &#{HeaderMap}::new()) + #{json_errors}::parse_error_metadata(payload, &#{HeaderMap}::new()) } """, *errorScope, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestXml.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestXml.kt index 44a9631ef7..3b4d10ddbf 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestXml.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestXml.kt @@ -27,7 +27,7 @@ open class RestXml(val codegenContext: CodegenContext) : Protocol { private val runtimeConfig = codegenContext.runtimeConfig private val errorScope = arrayOf( "Bytes" to RuntimeType.Bytes, - "Error" to RuntimeType.genericError(runtimeConfig), + "ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig), "HeaderMap" to RuntimeType.HttpHeaderMap, "Response" to RuntimeType.HttpResponse, "XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"), @@ -55,23 +55,23 @@ open class RestXml(val codegenContext: CodegenContext) : Protocol { return XmlBindingTraitSerializerGenerator(codegenContext, httpBindingResolver) } - override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_http_generic_error", xmlDeserModule) { + override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_http_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_http_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_http_error_metadata(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(response.body().as_ref())", restXmlErrors) + rust("#T::parse_error_metadata(response.body().as_ref())", restXmlErrors) } } - override fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType = - RuntimeType.forInlineFun("parse_event_stream_generic_error", xmlDeserModule) { + override fun parseEventStreamErrorMetadata(operationShape: OperationShape): RuntimeType = + RuntimeType.forInlineFun("parse_event_stream_error_metadata", xmlDeserModule) { rustBlockTemplate( - "pub fn parse_event_stream_generic_error(payload: &#{Bytes}) -> Result<#{Error}, #{XmlDecodeError}>", + "pub fn parse_event_stream_error_metadata(payload: &#{Bytes}) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>", *errorScope, ) { - rust("#T::parse_generic_error(payload.as_ref())", restXmlErrors) + rust("#T::parse_error_metadata(payload.as_ref())", restXmlErrors) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/EventStreamUnmarshallerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/EventStreamUnmarshallerGenerator.kt index 2743d1f246..253a5d2410 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/EventStreamUnmarshallerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/EventStreamUnmarshallerGenerator.kt @@ -305,12 +305,12 @@ class EventStreamUnmarshallerGenerator( CodegenTarget.CLIENT -> { rustTemplate( """ - let generic = match #{parse_generic_error}(message.payload()) { - Ok(generic) => generic, + let generic = match #{parse_error_metadata}(message.payload()) { + Ok(builder) => builder.build(), Err(err) => return Ok(#{UnmarshalledMessage}::Error(#{OpError}::unhandled(err))), }; """, - "parse_generic_error" to protocol.parseEventStreamGenericError(operationShape), + "parse_error_metadata" to protocol.parseEventStreamErrorMetadata(operationShape), *codegenScope, ) } @@ -342,11 +342,9 @@ class EventStreamUnmarshallerGenerator( .map_err(|err| { #{Error}::unmarshalling(format!("failed to unmarshall ${member.memberName}: {}", err)) })?; + builder.set_meta(Some(generic)); return Ok(#{UnmarshalledMessage}::Error( - #{OpError}::new( - #{OpError}Kind::${member.target.name}(builder.build()), - generic, - ) + #{OpError}::${member.target.name}(builder.build()) )) """, "parser" to parser, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/EventStreamErrorMarshallerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/EventStreamErrorMarshallerGenerator.kt index 579781996a..1324b91a11 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/EventStreamErrorMarshallerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/EventStreamErrorMarshallerGenerator.kt @@ -95,25 +95,15 @@ class EventStreamErrorMarshallerGenerator( ) { rust("let mut headers = Vec::new();") addStringHeader(":message-type", """"exception".into()""") - val kind = when (target) { - CodegenTarget.CLIENT -> ".kind" - CodegenTarget.SERVER -> "" - } if (errorsShape.errorMembers.isEmpty()) { rust("let payload = Vec::new();") } else { - rustBlock("let payload = match _input$kind") { - val symbol = operationErrorSymbol - val errorName = when (target) { - CodegenTarget.CLIENT -> "${symbol}Kind" - CodegenTarget.SERVER -> "$symbol" - } - + rustBlock("let payload = match _input") { errorsShape.errorMembers.forEach { error -> val errorSymbol = symbolProvider.toSymbol(error) val errorString = error.memberName val target = model.expectShape(error.target, StructureShape::class.java) - rustBlock("$errorName::${errorSymbol.name}(inner) => ") { + rustBlock("#T::${errorSymbol.name}(inner) => ", operationErrorSymbol) { addStringHeader(":exception-type", "${errorString.dq()}.into()") renderMarshallEvent(error, target) } @@ -121,11 +111,12 @@ class EventStreamErrorMarshallerGenerator( if (target.renderUnknownVariant()) { rustTemplate( """ - $errorName::Unhandled(_inner) => return Err( + #{OperationError}::Unhandled(_inner) => return Err( #{Error}::marshalling(${unknownVariantError(unionSymbol.rustType().name).dq()}.to_owned()) ), """, *codegenScope, + "OperationError" to operationErrorSymbol, ) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt index d11811ae36..51a354c0a2 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamTestTools.kt @@ -19,7 +19,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider -import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.renderUnknownVariant import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol @@ -75,6 +74,9 @@ interface EventStreamTestRequirements { symbolProvider: RustSymbolProvider, operationOrEventStream: Shape, ) + + /** Render an error struct and builder */ + fun renderError(writer: RustWriter, codegenContext: C, shape: StructureShape) } object EventStreamTestTools { @@ -126,8 +128,7 @@ object EventStreamTestTools { requirements.renderOperationError(this, model, symbolProvider, operationShape) requirements.renderOperationError(this, model, symbolProvider, unionShape) for (shape in errors) { - StructureGenerator(model, symbolProvider, this, shape).render(codegenTarget) - requirements.renderBuilderForShape(this, codegenContext, shape) + requirements.renderError(this, codegenContext, shape) } } val inputOutput = model.lookup("test#TestStreamInputOutput") diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamUnmarshallTestCases.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamUnmarshallTestCases.kt index bb27c724e0..36b3efafd0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamUnmarshallTestCases.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/EventStreamUnmarshallTestCases.kt @@ -20,7 +20,7 @@ internal object EventStreamUnmarshallTestCases { """ use aws_smithy_eventstream::frame::{Header, HeaderValue, Message, UnmarshallMessage, UnmarshalledMessage}; use aws_smithy_types::{Blob, DateTime}; - use crate::error::*; + use crate::error::TestStreamError; use crate::model::*; fn msg( @@ -210,10 +210,6 @@ internal object EventStreamUnmarshallTestCases { """, ) - val (someError, kindSuffix) = when (codegenTarget) { - CodegenTarget.CLIENT -> "TestStreamErrorKind::SomeError" to ".kind" - CodegenTarget.SERVER -> "TestStreamError::SomeError" to "" - } unitTest( "some_error", """ @@ -225,8 +221,8 @@ internal object EventStreamUnmarshallTestCases { ); let result = ${format(generator)}().unmarshall(&message); assert!(result.is_ok(), "expected ok, got: {:?}", result); - match expect_error(result.unwrap())$kindSuffix { - $someError(err) => assert_eq!(Some("some error"), err.message()), + match expect_error(result.unwrap()) { + TestStreamError::SomeError(err) => assert_eq!(Some("some error"), err.message()), kind => panic!("expected SomeError, but got {:?}", kind), } """, @@ -234,7 +230,7 @@ internal object EventStreamUnmarshallTestCases { if (codegenTarget == CodegenTarget.CLIENT) { unitTest( - "generic_error", + "error_metadata", """ let message = msg( "exception", @@ -244,13 +240,13 @@ internal object EventStreamUnmarshallTestCases { ); let result = ${format(generator)}().unmarshall(&message); assert!(result.is_ok(), "expected ok, got: {:?}", result); - match expect_error(result.unwrap())$kindSuffix { - TestStreamErrorKind::Unhandled(err) => { + match expect_error(result.unwrap()) { + TestStreamError::Unhandled(err) => { let message = format!("{}", aws_smithy_types::error::display::DisplayErrorContext(&err)); let expected = "message: \"unmodeled error\""; assert!(message.contains(expected), "Expected '{message}' to contain '{expected}'"); } - kind => panic!("expected generic error, but got {:?}", kind), + kind => panic!("expected error metadata, but got {:?}", kind), } """, ) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt index 05ef9bf216..2e80865640 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget @@ -31,7 +32,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait import software.amazon.smithy.rust.codegen.core.util.dq @@ -144,12 +144,11 @@ fun StructureShape.renderWithModelBuilder( model: Model, symbolProvider: RustSymbolProvider, writer: RustWriter, - forWhom: CodegenTarget = CodegenTarget.CLIENT, ) { - StructureGenerator(model, symbolProvider, writer, this).render(forWhom) - BuilderGenerator(model, symbolProvider, this).also { builderGen -> + StructureGenerator(model, symbolProvider, writer, this, emptyList()).render() + BuilderGenerator(model, symbolProvider, this, emptyList()).also { builderGen -> builderGen.render(writer) - writer.implBlock(this, symbolProvider) { + writer.implBlock(symbolProvider.toSymbol(this)) { builderGen.renderConvenienceMethod(this) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/util/Smithy.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/util/Smithy.kt index 267327c35e..06c344337f 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/util/Smithy.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/util/Smithy.kt @@ -137,3 +137,6 @@ fun Shape.isPrimitive(): Boolean { else -> false } } + +/** Convert a string to a ShapeId */ +fun String.shapeId() = ShapeId.from(this) diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtraTest.kt similarity index 93% rename from codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseGeneratorTest.kt rename to codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtraTest.kt index c147567d71..d8629c3b1d 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/SmithyTypesPubUseExtraTest.kt @@ -8,10 +8,11 @@ package software.amazon.smithy.rust.codegen.core.smithy.customizations import org.junit.jupiter.api.Test import software.amazon.smithy.model.Model import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGeneratorTest.Companion.model import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.testCodegenContext -class SmithyTypesPubUseGeneratorTest { +class SmithyTypesPubUseExtraTest { private fun modelWithMember( inputMember: String = "", outputMember: String = "", @@ -48,7 +49,7 @@ class SmithyTypesPubUseGeneratorTest { outputMember: String = "", unionMember: String = "", additionalShape: String = "", - ) = pubUseTypes(TestRuntimeConfig, modelWithMember(inputMember, outputMember, unionMember, additionalShape)) + ) = pubUseTypes(testCodegenContext(model), modelWithMember(inputMember, outputMember, unionMember, additionalShape)) private fun assertDoesntHaveTypes(types: List, expectedTypes: List) = expectedTypes.forEach { assertDoesntHaveType(types, it) } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt index b735d16806..e1ae567571 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt @@ -13,6 +13,7 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.AllowDeprecated +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.smithy.Default import software.amazon.smithy.rust.codegen.core.smithy.MaybeRenamed @@ -38,11 +39,11 @@ internal class BuilderGeneratorTest { val project = TestWorkspace.testProject(provider) project.moduleFor(inner) { rust("##![allow(deprecated)]") - StructureGenerator(model, provider, this, inner).render() - StructureGenerator(model, provider, this, struct).render() - BuilderGenerator(model, provider, struct).also { builderGen -> + StructureGenerator(model, provider, this, inner, emptyList()).render() + StructureGenerator(model, provider, this, struct, emptyList()).render() + BuilderGenerator(model, provider, struct, emptyList()).also { builderGen -> builderGen.render(this) - implBlock(struct, provider) { + implBlock(provider.toSymbol(struct)) { builderGen.renderConvenienceMethod(this) } } @@ -87,11 +88,11 @@ internal class BuilderGeneratorTest { val project = TestWorkspace.testProject(provider) project.moduleFor(StructureGeneratorTest.struct) { AllowDeprecated.render(this) - StructureGenerator(model, provider, this, inner).render() - StructureGenerator(model, provider, this, struct).render() - BuilderGenerator(model, provider, struct).also { builderGenerator -> + StructureGenerator(model, provider, this, inner, emptyList()).render() + StructureGenerator(model, provider, this, struct, emptyList()).render() + BuilderGenerator(model, provider, struct, emptyList()).also { builderGenerator -> builderGenerator.render(this) - implBlock(struct, provider) { + implBlock(provider.toSymbol(struct)) { builderGenerator.renderConvenienceMethod(this) } } @@ -113,10 +114,10 @@ internal class BuilderGeneratorTest { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.moduleFor(credentials) { - StructureGenerator(model, provider, this, credentials).render() - BuilderGenerator(model, provider, credentials).also { builderGen -> + StructureGenerator(model, provider, this, credentials, emptyList()).render() + BuilderGenerator(model, provider, credentials, emptyList()).also { builderGen -> builderGen.render(this) - implBlock(credentials, provider) { + implBlock(provider.toSymbol(credentials)) { builderGen.renderConvenienceMethod(this) } } @@ -140,10 +141,10 @@ internal class BuilderGeneratorTest { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.moduleFor(secretStructure) { - StructureGenerator(model, provider, this, secretStructure).render() - BuilderGenerator(model, provider, secretStructure).also { builderGen -> + StructureGenerator(model, provider, this, secretStructure, emptyList()).render() + BuilderGenerator(model, provider, secretStructure, emptyList()).also { builderGen -> builderGen.render(this) - implBlock(secretStructure, provider) { + implBlock(provider.toSymbol(secretStructure)) { builderGen.renderConvenienceMethod(this) } } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt index 6959b1a3b0..72a2dce315 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt @@ -92,8 +92,8 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - StructureGenerator(model, provider, this, inner).render() - StructureGenerator(model, provider, this, struct).render() + StructureGenerator(model, provider, this, inner, emptyList()).render() + StructureGenerator(model, provider, this, struct, emptyList()).render() unitTest( "struct_fields_optional", """ @@ -115,11 +115,11 @@ class StructureGeneratorTest { project.lib { Attribute.AllowDeprecated.render(this) } project.moduleFor(inner) { - val innerGenerator = StructureGenerator(model, provider, this, inner) + val innerGenerator = StructureGenerator(model, provider, this, inner, emptyList()) innerGenerator.render() } project.withModule(RustModule.public("structs")) { - val generator = StructureGenerator(model, provider, this, struct) + val generator = StructureGenerator(model, provider, this, struct, emptyList()) generator.render() } // By putting the test in another module, it can't access the struct @@ -138,25 +138,11 @@ class StructureGeneratorTest { project.compileAndTest() } - @Test - fun `generate error structures`() { - val provider = testSymbolProvider(model) - val writer = RustWriter.forModule("error") - val generator = StructureGenerator(model, provider, writer, error) - generator.render() - writer.compileAndTest( - """ - let err = MyError { message: None }; - assert_eq!(err.retryable_error_kind(), aws_smithy_types::retry::ErrorKind::ServerError); - """, - ) - } - @Test fun `generate a custom debug implementation when the sensitive trait is applied to some members`() { val provider = testSymbolProvider(model) val writer = RustWriter.forModule("lib") - val generator = StructureGenerator(model, provider, writer, credentials) + val generator = StructureGenerator(model, provider, writer, credentials, emptyList()) generator.render() writer.unitTest( "sensitive_fields_redacted", @@ -176,7 +162,7 @@ class StructureGeneratorTest { fun `generate a custom debug implementation when the sensitive trait is applied to the struct`() { val provider = testSymbolProvider(model) val writer = RustWriter.forModule("lib") - val generator = StructureGenerator(model, provider, writer, secretStructure) + val generator = StructureGenerator(model, provider, writer, secretStructure, emptyList()) generator.render() writer.unitTest( "sensitive_structure_redacted", @@ -195,8 +181,8 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - val secretGenerator = StructureGenerator(model, provider, this, secretStructure) - val generator = StructureGenerator(model, provider, this, structWithInnerSecretStructure) + val secretGenerator = StructureGenerator(model, provider, this, secretStructure, emptyList()) + val generator = StructureGenerator(model, provider, this, structWithInnerSecretStructure, emptyList()) secretGenerator.render() generator.render() unitTest( @@ -239,8 +225,8 @@ class StructureGeneratorTest { Attribute.DenyMissingDocs.render(this) } project.moduleFor(model.lookup("com.test#Inner")) { - StructureGenerator(model, provider, this, model.lookup("com.test#Inner")).render() - StructureGenerator(model, provider, this, model.lookup("com.test#MyStruct")).render() + StructureGenerator(model, provider, this, model.lookup("com.test#Inner"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("com.test#MyStruct"), emptyList()).render() } project.compileAndTest() @@ -250,7 +236,7 @@ class StructureGeneratorTest { fun `documents are optional in structs`() { val provider = testSymbolProvider(model) val writer = RustWriter.forModule("lib") - StructureGenerator(model, provider, writer, structWithDoc).render() + StructureGenerator(model, provider, writer, structWithDoc, emptyList()).render() writer.compileAndTest( """ @@ -283,10 +269,10 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.lib { rust("##![allow(deprecated)]") } project.moduleFor(model.lookup("test#Foo")) { - StructureGenerator(model, provider, this, model.lookup("test#Foo")).render() - StructureGenerator(model, provider, this, model.lookup("test#Bar")).render() - StructureGenerator(model, provider, this, model.lookup("test#Baz")).render() - StructureGenerator(model, provider, this, model.lookup("test#Qux")).render() + StructureGenerator(model, provider, this, model.lookup("test#Foo"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("test#Bar"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("test#Baz"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("test#Qux"), emptyList()).render() } // turn on clippy to check the semver-compliant version of `since`. @@ -316,9 +302,9 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.lib { rust("##![allow(deprecated)]") } project.moduleFor(model.lookup("test#Nested")) { - StructureGenerator(model, provider, this, model.lookup("test#Nested")).render() - StructureGenerator(model, provider, this, model.lookup("test#Foo")).render() - StructureGenerator(model, provider, this, model.lookup("test#Bar")).render() + StructureGenerator(model, provider, this, model.lookup("test#Nested"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("test#Foo"), emptyList()).render() + StructureGenerator(model, provider, this, model.lookup("test#Bar"), emptyList()).render() } project.compileAndTest() @@ -365,8 +351,8 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - StructureGenerator(testModel, provider, this, testModel.lookup("test#One")).render() - StructureGenerator(testModel, provider, this, testModel.lookup("test#Two")).render() + StructureGenerator(testModel, provider, this, testModel.lookup("test#One"), emptyList()).render() + StructureGenerator(testModel, provider, this, testModel.lookup("test#Two"), emptyList()).render() rustBlock("fn compile_test_one(one: &crate::test_model::One)") { rust( @@ -423,7 +409,7 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model) RustWriter.forModule("test").let { - StructureGenerator(model, provider, it, struct).render() + StructureGenerator(model, provider, it, struct, emptyList()).render() assertEquals(6, it.toString().split("#[doc(hidden)]").size, "there should be 5 doc-hiddens") } } @@ -439,7 +425,7 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model) RustWriter.forModule("test").let { writer -> - StructureGenerator(model, provider, writer, struct).render() + StructureGenerator(model, provider, writer, struct, emptyList()).render() writer.toString().shouldNotContain("#[doc(hidden)]") } } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGeneratorTest.kt new file mode 100644 index 0000000000..7f3772ed95 --- /dev/null +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ErrorImplGeneratorTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.core.smithy.generators.error + +import org.junit.jupiter.api.Test +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.ErrorTrait +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock +import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator +import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest +import software.amazon.smithy.rust.codegen.core.testutil.testSymbolProvider +import software.amazon.smithy.rust.codegen.core.util.getTrait + +class ErrorImplGeneratorTest { + val model = + """ + namespace com.test + + @error("server") + @retryable + structure MyError { + message: String + } + """.asSmithyModel() + + @Test + fun `generate error structures`() { + val provider = testSymbolProvider(model) + val project = TestWorkspace.testProject(provider) + val errorShape = model.expectShape(ShapeId.from("com.test#MyError")) as StructureShape + project.moduleFor(errorShape) { + val errorTrait = errorShape.getTrait()!! + StructureGenerator(model, provider, this, errorShape, emptyList()).render() + BuilderGenerator(model, provider, errorShape, emptyList()).let { builderGen -> + implBlock(provider.toSymbol(errorShape)) { + builderGen.renderConvenienceMethod(this) + } + builderGen.render(this) + } + ErrorImplGenerator(model, provider, this, errorShape, errorTrait, emptyList()).render(CodegenTarget.CLIENT) + compileAndTest( + """ + let err = MyError::builder().build(); + assert_eq!(err.retryable_error_kind(), aws_smithy_types::retry::ErrorKind::ServerError); + """, + ) + } + } +} diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt deleted file mode 100644 index d1f1cc9a57..0000000000 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.core.smithy.generators.error - -import org.junit.jupiter.api.Test -import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.rust.codegen.core.rustlang.Attribute -import software.amazon.smithy.rust.codegen.core.rustlang.AttributeKind -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget -import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings -import software.amazon.smithy.rust.codegen.core.smithy.RustCrate -import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator -import software.amazon.smithy.rust.codegen.core.smithy.module -import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel -import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext -import software.amazon.smithy.rust.codegen.core.testutil.testSymbolProvider -import software.amazon.smithy.rust.codegen.core.util.runCommand -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.createDirectory -import kotlin.io.path.writeText - -internal class ServiceErrorGeneratorTest { - @ExperimentalPathApi - @Test - fun `top level errors are send + sync`() { - val model = """ - namespace com.example - - use aws.protocols#restJson1 - - @restJson1 - service HelloService { - operations: [SayHello], - version: "1" - } - - @http(uri: "/", method: "POST") - operation SayHello { - input: EmptyStruct, - output: EmptyStruct, - errors: [SorryBusy, CanYouRepeatThat, MeDeprecated] - } - - structure EmptyStruct { } - - @error("server") - structure SorryBusy { } - - @error("client") - structure CanYouRepeatThat { } - - @error("client") - @deprecated - structure MeDeprecated { } - """.asSmithyModel() - - val (pluginContext, testDir) = generatePluginContext(model) - val moduleName = pluginContext.settings.expectStringMember("module").value.replace('-', '_') - val symbolProvider = testSymbolProvider(model) - val settings = CoreRustSettings.from(model, pluginContext.settings) - val codegenContext = CodegenContext( - model, - symbolProvider, - model.expectShape(ShapeId.from("com.example#HelloService")) as ServiceShape, - ShapeId.from("aws.protocols#restJson1"), - settings, - CodegenTarget.CLIENT, - ) - - val rustCrate = RustCrate( - pluginContext.fileManifest, - symbolProvider, - codegenContext.settings.codegenConfig, - ) - - rustCrate.lib { - Attribute.AllowDeprecated.render(this, AttributeKind.Inner) - } - for (operation in model.operationShapes) { - if (operation.id.namespace == "com.example") { - rustCrate.withModule(symbolProvider.symbolForOperationError(operation).module()) { - OperationErrorGenerator(model, symbolProvider, operation).render(this) - } - } - } - for (shape in model.structureShapes) { - if (shape.id.namespace == "com.example") { - rustCrate.moduleFor(shape) { - StructureGenerator(model, symbolProvider, this, shape).render(CodegenTarget.CLIENT) - } - } - } - ServiceErrorGenerator(codegenContext, model.operationShapes.toList()).render(rustCrate) - - testDir.resolve("tests").createDirectory() - testDir.resolve("tests/validate_errors.rs").writeText( - """ - fn check_send_sync() {} - #[test] - fn tl_errors_are_send_sync() { - check_send_sync::<$moduleName::Error>() - } - """, - ) - rustCrate.finalize(settings, model, emptyMap(), emptyList(), false) - - "cargo test".runCommand(testDir) - } -} diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt index 3bb485ebd1..56993015b9 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt @@ -50,7 +50,7 @@ class RecursiveShapesIntegrationTest { val structures = listOf("Expr", "SecondTree").map { input.lookup("com.example#$it") } structures.forEach { struct -> project.moduleFor(struct) { - StructureGenerator(input, symbolProvider, this, struct).render() + StructureGenerator(input, symbolProvider, this, struct, emptyList()).render() } } input.lookup("com.example#Atom").also { atom -> diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt index 48e7082cc7..445ef1ccf2 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt @@ -16,10 +16,13 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplGenerator +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerEnumGenerator import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerOperationHandlerGenerator import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerServiceGenerator @@ -41,7 +44,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerProtoco */ class PythonServerCodegenVisitor( context: PluginContext, - codegenDecorator: ServerCodegenDecorator, + private val codegenDecorator: ServerCodegenDecorator, ) : ServerCodegenVisitor(context, codegenDecorator) { init { @@ -120,7 +123,18 @@ class PythonServerCodegenVisitor( rustCrate.useShapeWriter(shape) { // Use Python specific structure generator that adds the #[pyclass] attribute // and #[pymethods] implementation. - PythonServerStructureGenerator(model, codegenContext.symbolProvider, this, shape).render(CodegenTarget.SERVER) + PythonServerStructureGenerator(model, codegenContext.symbolProvider, this, shape).render() + + shape.getTrait()?.also { errorTrait -> + ErrorImplGenerator( + model, + codegenContext.symbolProvider, + this, + shape, + errorTrait, + codegenDecorator.errorImplCustomizations(codegenContext, emptyList()), + ).render(CodegenTarget.SERVER) + } renderStructureShapeBuilder(shape, this) } diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt index 8b103a2821..496660e28c 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt @@ -37,7 +37,7 @@ class PythonServerStructureGenerator( private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StructureShape, -) : StructureGenerator(model, symbolProvider, writer, shape) { +) : StructureGenerator(model, symbolProvider, writer, shape, emptyList()) { private val pyO3 = PythonServerCargoDependency.PyO3.toType() diff --git a/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt b/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt index 3291e2ed05..1473edcffe 100644 --- a/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt +++ b/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt @@ -9,7 +9,6 @@ import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext @@ -31,7 +30,7 @@ internal class PythonTypeInformationGenerationTest { val codegenContext = serverTestCodegenContext(model) val symbolProvider = codegenContext.symbolProvider val writer = RustWriter.forModule("model") - PythonServerStructureGenerator(model, symbolProvider, writer, foo).render(CodegenTarget.SERVER) + PythonServerStructureGenerator(model, symbolProvider, writer, foo).render() val result = writer.toString() diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index 728dd3728d..faaf25514b 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -27,9 +27,11 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker @@ -38,12 +40,13 @@ import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplGenerator import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.core.util.CommandFailed +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasEventStreamMember import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.isEventStream @@ -251,7 +254,24 @@ open class ServerCodegenVisitor( override fun structureShape(shape: StructureShape) { logger.info("[rust-server-codegen] Generating a structure $shape") rustCrate.useShapeWriter(shape) { - StructureGenerator(model, codegenContext.symbolProvider, this, shape).render(CodegenTarget.SERVER) + StructureGenerator( + model, + codegenContext.symbolProvider, + this, + shape, + codegenDecorator.structureCustomizations(codegenContext, emptyList()), + ).render() + + shape.getTrait()?.also { errorTrait -> + ErrorImplGenerator( + model, + codegenContext.symbolProvider, + this, + shape, + errorTrait, + codegenDecorator.errorImplCustomizations(codegenContext, emptyList()), + ).render(CodegenTarget.SERVER) + } renderStructureShapeBuilder(shape, this) } @@ -266,7 +286,7 @@ open class ServerCodegenVisitor( serverBuilderGenerator.render(writer) if (codegenContext.settings.codegenConfig.publicConstrainedTypes) { - writer.implBlock(shape, codegenContext.symbolProvider) { + writer.implBlock(codegenContext.symbolProvider.toSymbol(shape)) { serverBuilderGenerator.renderConvenienceMethod(this) } } @@ -286,7 +306,7 @@ open class ServerCodegenVisitor( ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, shape, validationExceptionConversionGenerator) serverBuilderGeneratorWithoutPublicConstrainedTypes.render(writer) - writer.implBlock(shape, codegenContext.symbolProvider) { + writer.implBlock(codegenContext.symbolProvider.toSymbol(shape)) { serverBuilderGeneratorWithoutPublicConstrainedTypes.renderConvenienceMethod(this) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerRequiredCustomizations.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerRequiredCustomizations.kt index d7f06864bf..7104cb9451 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerRequiredCustomizations.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customizations/ServerRequiredCustomizations.kt @@ -37,7 +37,7 @@ class ServerRequiredCustomizations : ServerCodegenDecorator { rustCrate.mergeFeature(Feature("rt-tokio", true, listOf("aws-smithy-http/rt-tokio"))) rustCrate.withModule(ServerRustModule.Types) { - pubUseSmithyTypes(codegenContext.runtimeConfig, codegenContext.model)(this) + pubUseSmithyTypes(codegenContext, codegenContext.model)(this) } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt index 0b1660b010..69f75f46b5 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt @@ -12,12 +12,11 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig import software.amazon.smithy.rust.codegen.server.smithy.RustServerCodegenPlugin import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig @@ -121,13 +120,13 @@ fun serverTestCodegenContext( * In tests, we frequently need to generate a struct, a builder, and an impl block to access said builder. */ fun StructureShape.serverRenderWithModelBuilder(model: Model, symbolProvider: RustSymbolProvider, writer: RustWriter) { - StructureGenerator(model, symbolProvider, writer, this).render(CodegenTarget.SERVER) + StructureGenerator(model, symbolProvider, writer, this, emptyList()).render() val serverCodegenContext = serverTestCodegenContext(model) // Note that this always uses `ServerBuilderGenerator` and _not_ `ServerBuilderGeneratorWithoutPublicConstrainedTypes`, // regardless of the `publicConstrainedTypes` setting. val modelBuilder = ServerBuilderGenerator(serverCodegenContext, this, SmithyValidationExceptionConversionGenerator(serverCodegenContext)) modelBuilder.render(writer) - writer.implBlock(this, symbolProvider) { + writer.implBlock(symbolProvider.toSymbol(this)) { modelBuilder.renderConvenienceMethod(this) } } diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt index ef29d6a6e8..751d62bc8e 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt @@ -15,13 +15,13 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlock +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest @@ -178,7 +178,7 @@ class ServerBuilderDefaultValuesTest { ) val builderGenerator = ServerBuilderGeneratorWithoutPublicConstrainedTypes(codegenContext, struct, SmithyValidationExceptionConversionGenerator(codegenContext)) - writer.implBlock(struct, symbolProvider) { + writer.implBlock(symbolProvider.toSymbol(struct)) { builderGenerator.renderConvenienceMethod(writer) } builderGenerator.render(writer) @@ -188,7 +188,7 @@ class ServerBuilderDefaultValuesTest { model.lookup("com.test#Language"), SmithyValidationExceptionConversionGenerator(codegenContext), ).render(writer) - StructureGenerator(model, symbolProvider, writer, struct).render() + StructureGenerator(model, symbolProvider, writer, struct, emptyList()).render() } private fun writeServerBuilderGenerator(writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { @@ -196,7 +196,7 @@ class ServerBuilderDefaultValuesTest { val codegenContext = serverTestCodegenContext(model) val builderGenerator = ServerBuilderGenerator(codegenContext, struct, SmithyValidationExceptionConversionGenerator(codegenContext)) - writer.implBlock(struct, symbolProvider) { + writer.implBlock(symbolProvider.toSymbol(struct)) { builderGenerator.renderConvenienceMethod(writer) } builderGenerator.render(writer) @@ -206,7 +206,7 @@ class ServerBuilderDefaultValuesTest { model.lookup("com.test#Language"), SmithyValidationExceptionConversionGenerator(codegenContext), ).render(writer) - StructureGenerator(model, symbolProvider, writer, struct).render() + StructureGenerator(model, symbolProvider, writer, struct, emptyList()).render() } private fun structSetters(values: Map, optional: Boolean) = writable { diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt index 515bbb58f8..2748c6721e 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt @@ -8,9 +8,8 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import org.junit.jupiter.api.Test import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.util.lookup @@ -38,10 +37,10 @@ class ServerBuilderGeneratorTest { val codegenContext = serverTestCodegenContext(model) val writer = RustWriter.forModule("model") val shape = model.lookup("test#Credentials") - StructureGenerator(model, codegenContext.symbolProvider, writer, shape).render(CodegenTarget.SERVER) + StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList()).render() val builderGenerator = ServerBuilderGenerator(codegenContext, shape, SmithyValidationExceptionConversionGenerator(codegenContext)) builderGenerator.render(writer) - writer.implBlock(shape, codegenContext.symbolProvider) { + writer.implBlock(codegenContext.symbolProvider.toSymbol(shape)) { builderGenerator.renderConvenienceMethod(this) } writer.compileAndTest( diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt index d9fca2df9f..d59eed6acd 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/eventstream/ServerEventStreamBaseRequirements.kt @@ -14,11 +14,14 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider -import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplGenerator import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestRequirements +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenConfig import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.customizations.SmithyValidationExceptionConversionGenerator @@ -73,14 +76,14 @@ abstract class ServerEventStreamBaseRequirements : EventStreamTestRequirements &HeaderMap; + + /// Returns a mutable reference to the associated header map. + fn http_headers_mut(&mut self) -> &mut HeaderMap; +} + +impl HttpHeaders for http::Response { + fn http_headers(&self) -> &HeaderMap { + self.headers() + } + + fn http_headers_mut(&mut self) -> &mut HeaderMap { + self.headers_mut() + } +} + +impl HttpHeaders for crate::operation::Response { + fn http_headers(&self) -> &HeaderMap { + self.http().http_headers() + } + + fn http_headers_mut(&mut self) -> &mut HeaderMap { + self.http_mut().http_headers_mut() + } +} diff --git a/rust-runtime/aws-smithy-http/src/lib.rs b/rust-runtime/aws-smithy-http/src/lib.rs index 8c577a2656..92b8b737a7 100644 --- a/rust-runtime/aws-smithy-http/src/lib.rs +++ b/rust-runtime/aws-smithy-http/src/lib.rs @@ -21,6 +21,7 @@ pub mod body; pub mod endpoint; pub mod header; +pub mod http; pub mod http_versions; pub mod label; pub mod middleware; diff --git a/rust-runtime/aws-smithy-http/src/result.rs b/rust-runtime/aws-smithy-http/src/result.rs index 6cf2fc5c78..b00667d2c9 100644 --- a/rust-runtime/aws-smithy-http/src/result.rs +++ b/rust-runtime/aws-smithy-http/src/result.rs @@ -13,6 +13,8 @@ //! `Result` wrapper types for [success](SdkSuccess) and [failure](SdkError) responses. use crate::operation; +use aws_smithy_types::error::metadata::{ProvideErrorMetadata, EMPTY_ERROR_METADATA}; +use aws_smithy_types::error::ErrorMetadata; use aws_smithy_types::retry::ErrorKind; use std::error::Error; use std::fmt; @@ -126,8 +128,11 @@ impl ServiceError { /// /// This trait exists so that [`SdkError::into_service_error`] can be infallible. pub trait CreateUnhandledError { - /// Creates an unhandled error variant with the given `source`. - fn create_unhandled_error(source: Box) -> Self; + /// Creates an unhandled error variant with the given `source` and error metadata. + fn create_unhandled_error( + source: Box, + meta: Option, + ) -> Self; } /// Failed SDK Result @@ -200,19 +205,21 @@ impl SdkError { /// /// ```no_run /// # use aws_smithy_http::result::{SdkError, CreateUnhandledError}; - /// # #[derive(Debug)] enum GetObjectErrorKind { NoSuchKey(()), Other(()) } - /// # #[derive(Debug)] struct GetObjectError { kind: GetObjectErrorKind } + /// # #[derive(Debug)] enum GetObjectError { NoSuchKey(()), Other(()) } /// # impl std::fmt::Display for GetObjectError { /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unimplemented!() } /// # } /// # impl std::error::Error for GetObjectError {} /// # impl CreateUnhandledError for GetObjectError { - /// # fn create_unhandled_error(_: Box) -> Self { unimplemented!() } + /// # fn create_unhandled_error( + /// # _: Box, + /// # _: Option, + /// # ) -> Self { unimplemented!() } /// # } /// # fn example() -> Result<(), GetObjectError> { - /// # let sdk_err = SdkError::service_error(GetObjectError { kind: GetObjectErrorKind::NoSuchKey(()) }, ()); + /// # let sdk_err = SdkError::service_error(GetObjectError::NoSuchKey(()), ()); /// match sdk_err.into_service_error() { - /// GetObjectError { kind: GetObjectErrorKind::NoSuchKey(_) } => { + /// GetObjectError::NoSuchKey(_) => { /// // handle NoSuchKey /// } /// err @ _ => return Err(err), @@ -227,7 +234,7 @@ impl SdkError { { match self { Self::ServiceError(context) => context.source, - _ => E::create_unhandled_error(self.into()), + _ => E::create_unhandled_error(self.into(), None), } } @@ -278,6 +285,21 @@ where } } +impl ProvideErrorMetadata for SdkError +where + E: ProvideErrorMetadata, +{ + fn meta(&self) -> &aws_smithy_types::Error { + match self { + Self::ConstructionFailure(_) => &EMPTY_ERROR_METADATA, + Self::TimeoutError(_) => &EMPTY_ERROR_METADATA, + Self::DispatchFailure(_) => &EMPTY_ERROR_METADATA, + Self::ResponseError(_) => &EMPTY_ERROR_METADATA, + Self::ServiceError(err) => err.source.meta(), + } + } +} + #[derive(Debug)] enum ConnectorErrorKind { /// A timeout occurred while processing the request diff --git a/rust-runtime/aws-smithy-types/src/error.rs b/rust-runtime/aws-smithy-types/src/error.rs index dc41a67d83..b83d6ffe07 100644 --- a/rust-runtime/aws-smithy-types/src/error.rs +++ b/rust-runtime/aws-smithy-types/src/error.rs @@ -3,147 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Generic errors for Smithy codegen +//! Errors for Smithy codegen -use crate::retry::{ErrorKind, ProvideErrorKind}; -use std::collections::HashMap; use std::fmt; pub mod display; +pub mod metadata; +mod unhandled; -/// Generic Error type -/// -/// For many services, Errors are modeled. However, many services only partially model errors or don't -/// model errors at all. In these cases, the SDK will return this generic error type to expose the -/// `code`, `message` and `request_id`. -#[derive(Debug, Eq, PartialEq, Default, Clone)] -pub struct Error { - code: Option, - message: Option, - request_id: Option, - extras: HashMap<&'static str, String>, -} - -/// Builder for [`Error`]. -#[derive(Debug, Default)] -pub struct Builder { - inner: Error, -} - -impl Builder { - /// Sets the error message. - pub fn message(&mut self, message: impl Into) -> &mut Self { - self.inner.message = Some(message.into()); - self - } - - /// Sets the error code. - pub fn code(&mut self, code: impl Into) -> &mut Self { - self.inner.code = Some(code.into()); - self - } - - /// Sets the request ID the error happened for. - pub fn request_id(&mut self, request_id: impl Into) -> &mut Self { - self.inner.request_id = Some(request_id.into()); - self - } - - /// Set a custom field on the error metadata - /// - /// Typically, these will be accessed with an extension trait: - /// ```rust - /// use aws_smithy_types::Error; - /// const HOST_ID: &str = "host_id"; - /// trait S3ErrorExt { - /// fn extended_request_id(&self) -> Option<&str>; - /// } - /// - /// impl S3ErrorExt for Error { - /// fn extended_request_id(&self) -> Option<&str> { - /// self.extra(HOST_ID) - /// } - /// } - /// - /// fn main() { - /// // Extension trait must be brought into scope - /// use S3ErrorExt; - /// let sdk_response: Result<(), Error> = Err(Error::builder().custom(HOST_ID, "x-1234").build()); - /// if let Err(err) = sdk_response { - /// println!("request id: {:?}, extended request id: {:?}", err.request_id(), err.extended_request_id()); - /// } - /// } - /// ``` - pub fn custom(&mut self, key: &'static str, value: impl Into) -> &mut Self { - self.inner.extras.insert(key, value.into()); - self - } - - /// Creates the error. - pub fn build(&mut self) -> Error { - std::mem::take(&mut self.inner) - } -} - -impl Error { - /// Returns the error code. - pub fn code(&self) -> Option<&str> { - self.code.as_deref() - } - /// Returns the error message. - pub fn message(&self) -> Option<&str> { - self.message.as_deref() - } - /// Returns the request ID the error occurred for, if it's available. - pub fn request_id(&self) -> Option<&str> { - self.request_id.as_deref() - } - /// Returns additional information about the error if it's present. - pub fn extra(&self, key: &'static str) -> Option<&str> { - self.extras.get(key).map(|k| k.as_str()) - } - - /// Creates an `Error` builder. - pub fn builder() -> Builder { - Builder::default() - } - - /// Converts an `Error` into a builder. - pub fn into_builder(self) -> Builder { - Builder { inner: self } - } -} - -impl ProvideErrorKind for Error { - fn retryable_error_kind(&self) -> Option { - None - } - - fn code(&self) -> Option<&str> { - Error::code(self) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut fmt = f.debug_struct("Error"); - if let Some(code) = &self.code { - fmt.field("code", code); - } - if let Some(message) = &self.message { - fmt.field("message", message); - } - if let Some(req_id) = &self.request_id { - fmt.field("request_id", req_id); - } - for (k, v) in &self.extras { - fmt.field(k, &v); - } - fmt.finish() - } -} - -impl std::error::Error for Error {} +pub use metadata::ErrorMetadata; +pub use unhandled::Unhandled; #[derive(Debug)] pub(super) enum TryFromNumberErrorKind { diff --git a/rust-runtime/aws-smithy-types/src/error/metadata.rs b/rust-runtime/aws-smithy-types/src/error/metadata.rs new file mode 100644 index 0000000000..06925e13f9 --- /dev/null +++ b/rust-runtime/aws-smithy-types/src/error/metadata.rs @@ -0,0 +1,166 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Error metadata + +use crate::retry::{ErrorKind, ProvideErrorKind}; +use std::collections::HashMap; +use std::fmt; + +/// Trait to retrieve error metadata from a result +pub trait ProvideErrorMetadata { + /// Returns error metadata, which includes the error code, message, + /// request ID, and potentially additional information. + fn meta(&self) -> &ErrorMetadata; + + /// Returns the error code if it's available. + fn code(&self) -> Option<&str> { + self.meta().code() + } + + /// Returns the error message, if there is one. + fn message(&self) -> Option<&str> { + self.meta().message() + } +} + +/// Empty error metadata +#[doc(hidden)] +pub const EMPTY_ERROR_METADATA: ErrorMetadata = ErrorMetadata { + code: None, + message: None, + extras: None, +}; + +/// Generic Error type +/// +/// For many services, Errors are modeled. However, many services only partially model errors or don't +/// model errors at all. In these cases, the SDK will return this generic error type to expose the +/// `code`, `message` and `request_id`. +#[derive(Debug, Eq, PartialEq, Default, Clone)] +pub struct ErrorMetadata { + code: Option, + message: Option, + extras: Option>, +} + +/// Builder for [`ErrorMetadata`]. +#[derive(Debug, Default)] +pub struct Builder { + inner: ErrorMetadata, +} + +impl Builder { + /// Sets the error message. + pub fn message(mut self, message: impl Into) -> Self { + self.inner.message = Some(message.into()); + self + } + + /// Sets the error code. + pub fn code(mut self, code: impl Into) -> Self { + self.inner.code = Some(code.into()); + self + } + + /// Set a custom field on the error metadata + /// + /// Typically, these will be accessed with an extension trait: + /// ```rust + /// use aws_smithy_types::Error; + /// const HOST_ID: &str = "host_id"; + /// trait S3ErrorExt { + /// fn extended_request_id(&self) -> Option<&str>; + /// } + /// + /// impl S3ErrorExt for Error { + /// fn extended_request_id(&self) -> Option<&str> { + /// self.extra(HOST_ID) + /// } + /// } + /// + /// fn main() { + /// // Extension trait must be brought into scope + /// use S3ErrorExt; + /// let sdk_response: Result<(), Error> = Err(Error::builder().custom(HOST_ID, "x-1234").build()); + /// if let Err(err) = sdk_response { + /// println!("extended request id: {:?}", err.extended_request_id()); + /// } + /// } + /// ``` + pub fn custom(mut self, key: &'static str, value: impl Into) -> Self { + if self.inner.extras.is_none() { + self.inner.extras = Some(HashMap::new()); + } + self.inner + .extras + .as_mut() + .unwrap() + .insert(key, value.into()); + self + } + + /// Creates the error. + pub fn build(self) -> ErrorMetadata { + self.inner + } +} + +impl ErrorMetadata { + /// Returns the error code. + pub fn code(&self) -> Option<&str> { + self.code.as_deref() + } + /// Returns the error message. + pub fn message(&self) -> Option<&str> { + self.message.as_deref() + } + /// Returns additional information about the error if it's present. + pub fn extra(&self, key: &'static str) -> Option<&str> { + self.extras + .as_ref() + .and_then(|extras| extras.get(key).map(|k| k.as_str())) + } + + /// Creates an `Error` builder. + pub fn builder() -> Builder { + Builder::default() + } + + /// Converts an `Error` into a builder. + pub fn into_builder(self) -> Builder { + Builder { inner: self } + } +} + +impl ProvideErrorKind for ErrorMetadata { + fn retryable_error_kind(&self) -> Option { + None + } + + fn code(&self) -> Option<&str> { + ErrorMetadata::code(self) + } +} + +impl fmt::Display for ErrorMetadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fmt = f.debug_struct("Error"); + if let Some(code) = &self.code { + fmt.field("code", code); + } + if let Some(message) = &self.message { + fmt.field("message", message); + } + if let Some(extras) = &self.extras { + for (k, v) in extras { + fmt.field(k, &v); + } + } + fmt.finish() + } +} + +impl std::error::Error for ErrorMetadata {} diff --git a/rust-runtime/aws-smithy-types/src/error/unhandled.rs b/rust-runtime/aws-smithy-types/src/error/unhandled.rs new file mode 100644 index 0000000000..2397d700ff --- /dev/null +++ b/rust-runtime/aws-smithy-types/src/error/unhandled.rs @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Unhandled error type. + +use crate::error::{metadata::ProvideErrorMetadata, ErrorMetadata}; +use std::error::Error as StdError; + +/// Builder for [`Unhandled`] +#[derive(Default, Debug)] +pub struct Builder { + source: Option>, + meta: Option, +} + +impl Builder { + /// Sets the error source + pub fn source(mut self, source: impl Into>) -> Self { + self.source = Some(source.into()); + self + } + + /// Sets the error source + pub fn set_source( + &mut self, + source: Option>, + ) -> &mut Self { + self.source = source; + self + } + + /// Sets the error metadata + pub fn meta(mut self, meta: ErrorMetadata) -> Self { + self.meta = Some(meta); + self + } + + /// Sets the error metadata + pub fn set_meta(&mut self, meta: Option) -> &mut Self { + self.meta = meta; + self + } + + /// Builds the unhandled error + pub fn build(self) -> Unhandled { + Unhandled { + source: self.source.expect("unhandled errors must have a source"), + meta: self.meta.unwrap_or_default(), + } + } +} + +/// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code). +/// +/// When logging an error from the SDK, it is recommended that you either wrap the error in +/// [`DisplayErrorContext`](crate::error::display::DisplayErrorContext), use another +/// error reporter library that visits the error's cause/source chain, or call +/// [`Error::source`](std::error::Error::source) for more details about the underlying cause. +#[derive(Debug)] +pub struct Unhandled { + source: Box, + meta: ErrorMetadata, +} + +impl Unhandled { + /// Returns a builder to construct an unhandled error. + pub fn builder() -> Builder { + Default::default() + } +} + +impl std::fmt::Display for Unhandled { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "unhandled error") + } +} + +impl StdError for Unhandled { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(self.source.as_ref() as _) + } +} + +impl ProvideErrorMetadata for Unhandled { + fn meta(&self) -> &ErrorMetadata { + &self.meta + } +} diff --git a/rust-runtime/aws-smithy-types/src/lib.rs b/rust-runtime/aws-smithy-types/src/lib.rs index 39d91c75be..cc6c25e041 100644 --- a/rust-runtime/aws-smithy-types/src/lib.rs +++ b/rust-runtime/aws-smithy-types/src/lib.rs @@ -26,7 +26,13 @@ pub mod retry; pub mod timeout; pub use crate::date_time::DateTime; -pub use error::Error; + +// TODO(deprecated): Remove deprecated re-export +/// Use [error::ErrorMetadata] instead. +#[deprecated( + note = "`aws_smithy_types::Error` has been renamed to `aws_smithy_types::error::ErrorMetadata`" +)] +pub use error::ErrorMetadata as Error; /// Binary Blob Type /// diff --git a/rust-runtime/inlineable/src/ec2_query_errors.rs b/rust-runtime/inlineable/src/ec2_query_errors.rs index a7fc1b1163..3355dbe004 100644 --- a/rust-runtime/inlineable/src/ec2_query_errors.rs +++ b/rust-runtime/inlineable/src/ec2_query_errors.rs @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata}; use aws_smithy_xml::decode::{try_data, Document, ScopedDecoder, XmlDecodeError}; use std::convert::TryFrom; @@ -13,36 +14,30 @@ pub fn body_is_error(body: &[u8]) -> Result { Ok(scoped.start_el().matches("Response")) } -pub fn parse_generic_error(body: &[u8]) -> Result { +pub fn parse_error_metadata(body: &[u8]) -> Result { let mut doc = Document::try_from(body)?; let mut root = doc.root_element()?; - let mut err_builder = aws_smithy_types::Error::builder(); + let mut err_builder = ErrorMetadata::builder(); while let Some(mut tag) = root.next_tag() { - match tag.start_el().local() { - "Errors" => { - while let Some(mut error_tag) = tag.next_tag() { - if let "Error" = error_tag.start_el().local() { - while let Some(mut error_field) = error_tag.next_tag() { - match error_field.start_el().local() { - "Code" => { - err_builder.code(try_data(&mut error_field)?); - } - "Message" => { - err_builder.message(try_data(&mut error_field)?); - } - _ => {} + if tag.start_el().local() == "Errors" { + while let Some(mut error_tag) = tag.next_tag() { + if let "Error" = error_tag.start_el().local() { + while let Some(mut error_field) = error_tag.next_tag() { + match error_field.start_el().local() { + "Code" => { + err_builder = err_builder.code(try_data(&mut error_field)?); } + "Message" => { + err_builder = err_builder.message(try_data(&mut error_field)?); + } + _ => {} } } } } - "RequestId" => { - err_builder.request_id(try_data(&mut tag)?); - } - _ => {} } } - Ok(err_builder.build()) + Ok(err_builder) } #[allow(unused)] @@ -71,7 +66,7 @@ pub fn error_scope<'a, 'b>( #[cfg(test)] mod test { - use super::{body_is_error, parse_generic_error}; + use super::{body_is_error, parse_error_metadata}; use crate::ec2_query_errors::error_scope; use aws_smithy_xml::decode::Document; use std::convert::TryFrom; @@ -92,8 +87,7 @@ mod test { "#; assert!(body_is_error(xml).unwrap()); - let parsed = parse_generic_error(xml).expect("valid xml"); - assert_eq!(parsed.request_id(), Some("foo-id")); + let parsed = parse_error_metadata(xml).expect("valid xml").build(); assert_eq!(parsed.message(), Some("Hi")); assert_eq!(parsed.code(), Some("InvalidGreeting")); } diff --git a/rust-runtime/inlineable/src/json_errors.rs b/rust-runtime/inlineable/src/json_errors.rs index ea13da3ba8..1973f59d79 100644 --- a/rust-runtime/inlineable/src/json_errors.rs +++ b/rust-runtime/inlineable/src/json_errors.rs @@ -5,7 +5,7 @@ use aws_smithy_json::deserialize::token::skip_value; use aws_smithy_json::deserialize::{error::DeserializeError, json_token_iter, Token}; -use aws_smithy_types::Error as SmithyError; +use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata}; use bytes::Bytes; use http::header::ToStrError; use http::{HeaderMap, HeaderValue}; @@ -82,56 +82,47 @@ fn error_type_from_header(headers: &HeaderMap) -> Result) -> Option<&str> { - headers - .get("X-Amzn-Requestid") - .and_then(|v| v.to_str().ok()) -} - -pub fn parse_generic_error( +pub fn parse_error_metadata( payload: &Bytes, headers: &HeaderMap, -) -> Result { +) -> Result { let ErrorBody { code, message } = parse_error_body(payload.as_ref())?; - let mut err_builder = SmithyError::builder(); + let mut err_builder = ErrorMetadata::builder(); if let Some(code) = error_type_from_header(headers) .map_err(|_| DeserializeError::custom("X-Amzn-Errortype header was not valid UTF-8"))? .or(code.as_deref()) .map(sanitize_error_code) { - err_builder.code(code); + err_builder = err_builder.code(code); } if let Some(message) = message { - err_builder.message(message); + err_builder = err_builder.message(message); } - if let Some(request_id) = request_id(headers) { - err_builder.request_id(request_id); - } - Ok(err_builder.build()) + Ok(err_builder) } #[cfg(test)] mod test { - use crate::json_errors::{parse_error_body, parse_generic_error, sanitize_error_code}; + use crate::json_errors::{parse_error_body, parse_error_metadata, sanitize_error_code}; use aws_smithy_types::Error; use bytes::Bytes; use std::borrow::Cow; #[test] - fn generic_error() { + fn error_metadata() { let response = http::Response::builder() - .header("X-Amzn-Requestid", "1234") .body(Bytes::from_static( br#"{ "__type": "FooError", "message": "Go to foo" }"#, )) .unwrap(); assert_eq!( - parse_generic_error(response.body(), response.headers()).unwrap(), + parse_error_metadata(response.body(), response.headers()) + .unwrap() + .build(), Error::builder() .code("FooError") .message("Go to foo") - .request_id("1234") .build() ) } @@ -209,7 +200,9 @@ mod test { )) .unwrap(); assert_eq!( - parse_generic_error(response.body(), response.headers()).unwrap(), + parse_error_metadata(response.body(), response.headers()) + .unwrap() + .build(), Error::builder() .code("ResourceNotFoundException") .message("Functions from 'us-west-2' are not reachable from us-east-1") diff --git a/rust-runtime/inlineable/src/rest_xml_unwrapped_errors.rs b/rust-runtime/inlineable/src/rest_xml_unwrapped_errors.rs index df0f22ef4e..def901cf7f 100644 --- a/rust-runtime/inlineable/src/rest_xml_unwrapped_errors.rs +++ b/rust-runtime/inlineable/src/rest_xml_unwrapped_errors.rs @@ -6,6 +6,7 @@ //! Error abstractions for `noErrorWrapping`. Code generators should either inline this file //! or its companion `rest_xml_wrapped_errors.rs` for code generation +use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata}; use aws_smithy_xml::decode::{try_data, Document, ScopedDecoder, XmlDecodeError}; use std::convert::TryFrom; @@ -26,30 +27,27 @@ pub fn error_scope<'a, 'b>( Ok(scoped) } -pub fn parse_generic_error(body: &[u8]) -> Result { +pub fn parse_error_metadata(body: &[u8]) -> Result { let mut doc = Document::try_from(body)?; let mut root = doc.root_element()?; - let mut err = aws_smithy_types::Error::builder(); + let mut builder = ErrorMetadata::builder(); while let Some(mut tag) = root.next_tag() { match tag.start_el().local() { "Code" => { - err.code(try_data(&mut tag)?); + builder = builder.code(try_data(&mut tag)?); } "Message" => { - err.message(try_data(&mut tag)?); - } - "RequestId" => { - err.request_id(try_data(&mut tag)?); + builder = builder.message(try_data(&mut tag)?); } _ => {} } } - Ok(err.build()) + Ok(builder) } #[cfg(test)] mod test { - use super::{body_is_error, parse_generic_error}; + use super::{body_is_error, parse_error_metadata}; #[test] fn parse_unwrapped_error() { @@ -61,8 +59,7 @@ mod test { foo-id "#; assert!(body_is_error(xml).unwrap()); - let parsed = parse_generic_error(xml).expect("valid xml"); - assert_eq!(parsed.request_id(), Some("foo-id")); + let parsed = parse_error_metadata(xml).expect("valid xml").build(); assert_eq!(parsed.message(), Some("Hi")); assert_eq!(parsed.code(), Some("InvalidGreeting")); } diff --git a/rust-runtime/inlineable/src/rest_xml_wrapped_errors.rs b/rust-runtime/inlineable/src/rest_xml_wrapped_errors.rs index c90301bf39..2511929a06 100644 --- a/rust-runtime/inlineable/src/rest_xml_wrapped_errors.rs +++ b/rust-runtime/inlineable/src/rest_xml_wrapped_errors.rs @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata}; use aws_smithy_xml::decode::{try_data, Document, ScopedDecoder, XmlDecodeError}; use std::convert::TryFrom; @@ -13,32 +14,26 @@ pub fn body_is_error(body: &[u8]) -> Result { Ok(scoped.start_el().matches("ErrorResponse")) } -pub fn parse_generic_error(body: &[u8]) -> Result { +pub fn parse_error_metadata(body: &[u8]) -> Result { let mut doc = Document::try_from(body)?; let mut root = doc.root_element()?; - let mut err_builder = aws_smithy_types::Error::builder(); + let mut err_builder = ErrorMetadata::builder(); while let Some(mut tag) = root.next_tag() { - match tag.start_el().local() { - "Error" => { - while let Some(mut error_field) = tag.next_tag() { - match error_field.start_el().local() { - "Code" => { - err_builder.code(try_data(&mut error_field)?); - } - "Message" => { - err_builder.message(try_data(&mut error_field)?); - } - _ => {} + if tag.start_el().local() == "Error" { + while let Some(mut error_field) = tag.next_tag() { + match error_field.start_el().local() { + "Code" => { + err_builder = err_builder.code(try_data(&mut error_field)?); } + "Message" => { + err_builder = err_builder.message(try_data(&mut error_field)?); + } + _ => {} } } - "RequestId" => { - err_builder.request_id(try_data(&mut tag)?); - } - _ => {} } } - Ok(err_builder.build()) + Ok(err_builder) } #[allow(unused)] @@ -65,7 +60,7 @@ pub fn error_scope<'a, 'b>( #[cfg(test)] mod test { - use super::{body_is_error, parse_generic_error}; + use super::{body_is_error, parse_error_metadata}; use crate::rest_xml_wrapped_errors::error_scope; use aws_smithy_xml::decode::Document; use std::convert::TryFrom; @@ -83,8 +78,7 @@ mod test { foo-id "#; assert!(body_is_error(xml).unwrap()); - let parsed = parse_generic_error(xml).expect("valid xml"); - assert_eq!(parsed.request_id(), Some("foo-id")); + let parsed = parse_error_metadata(xml).expect("valid xml").build(); assert_eq!(parsed.message(), Some("Hi")); assert_eq!(parsed.code(), Some("InvalidGreeting")); } diff --git a/tools/ci-cdk/canary-lambda/src/s3_canary.rs b/tools/ci-cdk/canary-lambda/src/s3_canary.rs index cb56797f01..830a760481 100644 --- a/tools/ci-cdk/canary-lambda/src/s3_canary.rs +++ b/tools/ci-cdk/canary-lambda/src/s3_canary.rs @@ -8,7 +8,7 @@ use crate::{mk_canary, CanaryEnv}; use anyhow::Context; use aws_config::SdkConfig; use aws_sdk_s3 as s3; -use s3::error::{GetObjectError, GetObjectErrorKind}; +use s3::error::GetObjectError; use s3::types::ByteStream; use uuid::Uuid; @@ -36,10 +36,7 @@ pub async fn s3_canary(client: s3::Client, s3_bucket_name: String) -> anyhow::Re ); } Err(err) => match err.into_service_error() { - GetObjectError { - kind: GetObjectErrorKind::NoSuchKey(..), - .. - } => { + GetObjectError::NoSuchKey(..) => { // good } err => Err(err).context("unexpected s3::GetObject failure")?,