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