diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index fc4c4c2578b5c5f185517bd30b00e87999da307c..9d106d4f355c8b14450c054186130e40df2fcea7 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -9,4 +9,66 @@ # message = "Fix typos in module documentation for generated crates" # references = ["smithy-rs#920"] # meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"} -# author = "rcoh" \ No newline at end of file +# author = "rcoh" + +[[aws-sdk-rust]] +message = "`aws_config::RetryConfig` no longer implements `Default`, and its `new` function has been replaced with `standard`." +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[smithy-rs]] +message = "`aws_smithy_types::RetryConfig` no longer implements `Default`, and its `new` function has been replaced with `standard`." +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[aws-sdk-rust]] +message = """ +Direct configuration of `aws_config::SdkConfig` now defaults to retries being disabled. +If you're using `aws_config::load_from_env()` or `aws_config::from_env()` to configure +the SDK, then you are NOT affected by this change. If you use `SdkConfig::builder()` to +configure the SDK, then you ARE affected by this change and should set the retry config +on that builder. +""" +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[aws-sdk-rust]] +message = """ +Client creation now panics if retries or timeouts are enabled without an async sleep +implementation set on the SDK config. +If you're using the Tokio runtime and have the `rt-tokio` feature enabled (which is enabled by default), +then you shouldn't notice this change at all. +Otherwise, if using something other than Tokio as the async runtime, the `AsyncSleep` trait must be implemented, +and that implementation given to the config builder via the `sleep_impl` method. Alternatively, retry can be +explicitly turned off by setting the retry config to `RetryConfig::disabled()`, which will result in successful +client creation without an async sleep implementation. +""" +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + +[[smithy-rs]] +message = """ +Client creation now panics if retries or timeouts are enabled without an async sleep implementation. +If you're using the Tokio runtime and have the `rt-tokio` feature enabled (which is enabled by default), +then you shouldn't notice this change at all. +Otherwise, if using something other than Tokio as the async runtime, the `AsyncSleep` trait must be implemented, +and that implementation given to the config builder via the `sleep_impl` method. Alternatively, retry can be +explicitly turned off by setting `max_attempts` to 1, which will result in successful client creation without an +async sleep implementation. +""" +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[smithy-rs]] +message = """ +The `default_async_sleep` method on the `Client` builder has been removed. The default async sleep is +wired up by default if none is provided. +""" +references = ["smithy-rs#1603", "aws-sdk-rust#586"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" diff --git a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs index 18265be6ffc6f435e1c3ee58915ae4c8c39747f3..8ebffc6b8a92e387ef3a93dcde332c7f8e59ace4 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs @@ -399,7 +399,7 @@ mod test { .retry_config() .await; - let expected_retry_config = RetryConfig::new(); + let expected_retry_config = RetryConfig::standard(); assert_eq!(actual_retry_config, expected_retry_config); // This is redundant but it's really important to make sure that @@ -420,7 +420,7 @@ mod test { .retry_config() .await; - let expected_retry_config = RetryConfig::new(); + let expected_retry_config = RetryConfig::standard(); assert_eq!(actual_retry_config, expected_retry_config) } @@ -447,9 +447,7 @@ retry_mode = standard .retry_config() .await; - let expected_retry_config = RetryConfig::new() - .with_max_attempts(1) - .with_retry_mode(RetryMode::Standard); + let expected_retry_config = RetryConfig::standard().with_max_attempts(1); assert_eq!(actual_retry_config, expected_retry_config) } @@ -480,9 +478,7 @@ retry_mode = standard .retry_config() .await; - let expected_retry_config = RetryConfig::new() - .with_max_attempts(42) - .with_retry_mode(RetryMode::Standard); + let expected_retry_config = RetryConfig::standard().with_max_attempts(42); assert_eq!(actual_retry_config, expected_retry_config) } diff --git a/aws/rust-runtime/aws-config/src/default_provider/retry_config.rs b/aws/rust-runtime/aws-config/src/default_provider/retry_config.rs index 4e8a4c00ad61acc76e5877a7741d9f77bb850a8b..3da892ebcef09d9ba38e64a3caf47f13c0993b58 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/retry_config.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/retry_config.rs @@ -76,7 +76,7 @@ impl Builder { /// Attempt to create a [RetryConfig](aws_smithy_types::retry::RetryConfig) from following sources in order: /// 1. [Environment variables](crate::environment::retry_config::EnvironmentVariableRetryConfigProvider) /// 2. [Profile file](crate::profile::retry_config::ProfileFileRetryConfigProvider) - /// 3. [RetryConfig::default()](aws_smithy_types::retry::RetryConfig::default) + /// 3. [RetryConfig::standard()](aws_smithy_types::retry::RetryConfig::standard) /// /// Precedence is considered on a per-field basis /// diff --git a/aws/rust-runtime/aws-config/src/environment/retry_config.rs b/aws/rust-runtime/aws-config/src/environment/retry_config.rs index 986d05c85ea513df231a25e786b694243421c19d..f0ed9848644d4f0ec50a2b183c0f326a4be90379 100644 --- a/aws/rust-runtime/aws-config/src/environment/retry_config.rs +++ b/aws/rust-runtime/aws-config/src/environment/retry_config.rs @@ -102,7 +102,7 @@ mod test { .retry_config_builder() .unwrap() .build(), - RetryConfig::new().with_max_attempts(88) + RetryConfig::standard().with_max_attempts(88) ); } @@ -123,7 +123,7 @@ mod test { .retry_config_builder() .unwrap() .build(), - RetryConfig::new().with_retry_mode(RetryMode::Standard) + RetryConfig::standard() ); } @@ -137,9 +137,7 @@ mod test { .retry_config_builder() .unwrap() .build(), - RetryConfig::new() - .with_max_attempts(13) - .with_retry_mode(RetryMode::Standard) + RetryConfig::standard().with_max_attempts(13) ); } diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index 67e81371b95293fd6a4743155e1c9ff4ba2ccf21..b1b9c7ab448590ba3c90c5363d1b4f30c905e082 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -744,11 +744,8 @@ impl ClassifyResponse, SdkError> for ImdsErrorPolicy { #[cfg(test)] pub(crate) mod test { - use std::collections::HashMap; - use std::error::Error; - use std::io; - use std::time::{Duration, UNIX_EPOCH}; - + use crate::imds::client::{Client, EndpointMode, ImdsErrorPolicy}; + use crate::provider_config::ProviderConfig; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::test_connection::{capture_request, TestConnection}; @@ -760,11 +757,12 @@ pub(crate) mod test { use http::header::USER_AGENT; use http::Uri; use serde::Deserialize; + use std::collections::HashMap; + use std::error::Error; + use std::io; + use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; - use crate::imds::client::{Client, EndpointMode, ImdsErrorPolicy}; - use crate::provider_config::ProviderConfig; - const TOKEN_A: &str = "AQAEAFTNrA4eEGx0AQgJ1arIq_Cc-t4tWt3fB0Hd8RKhXlKc5ccvhg=="; const TOKEN_B: &str = "alternatetoken=="; @@ -918,6 +916,7 @@ pub(crate) mod test { let client = super::Client::builder() .configure( &ProviderConfig::no_configuration() + .with_sleep(TokioSleep::new()) .with_http_connector(DynConnector::new(connection.clone())) .with_time_source(TimeSource::manual(&time_source)), ) @@ -1134,6 +1133,7 @@ pub(crate) mod test { async fn check(test_case: ImdsConfigTest) { let (server, watcher) = capture_request(None); let provider_config = ProviderConfig::no_configuration() + .with_sleep(TokioSleep::new()) .with_env(Env::from(test_case.env)) .with_fs(Fs::from_map(test_case.fs)) .with_http_connector(DynConnector::new(server)); diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 5956efa10ae5951e9bbeb58e1b548eb985fbe379..0f8fa392577428aa18c4d819848e571bd2916f04 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -227,7 +227,7 @@ mod loader { /// # use aws_smithy_types::retry::RetryConfig; /// # async fn create_config() { /// let config = aws_config::from_env() - /// .retry_config(RetryConfig::new().with_max_attempts(2)) + /// .retry_config(RetryConfig::standard().with_max_attempts(2)) /// .load().await; /// # } /// ``` @@ -449,6 +449,7 @@ mod loader { mod test { use crate::from_env; use crate::provider_config::ProviderConfig; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::never::NeverConnector; use aws_types::credentials::ProvideCredentials; @@ -465,6 +466,7 @@ mod loader { let loader = from_env() .configure( ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_env(env) .with_http_connector(DynConnector::new(NeverConnector::new())), ) 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 60b73d9a3921efb55be7a91511f023610a91b2e3..f9c98103b3c1c5987cccdd3eb0b383627dfa6e95 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -273,6 +273,7 @@ impl ProvideCredentials for AssumeRoleProvider { mod test { use crate::provider_config::ProviderConfig; use crate::sts::AssumeRoleProvider; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::test_connection::capture_request; use aws_smithy_http::body::SdkBody; @@ -286,6 +287,7 @@ mod test { async fn configures_session_length() { let (server, request) = capture_request(None); let provider_conf = ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_time_source(TimeSource::manual(&ManualTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), ))) @@ -314,6 +316,7 @@ mod test { )); let (server, _request) = capture_request(Some(resp)); let provider_conf = ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_time_source(TimeSource::manual(&ManualTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), ))) diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs index 4d963f7c66c46687e5e84b6e0575e1d458d43d52..8ad9aa8614348cb2ddaa2a6af1231212acfd6231 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -259,22 +259,22 @@ async fn load_credentials( #[cfg(test)] mod test { + use crate::provider_config::ProviderConfig; + use crate::test_case::no_traffic_connector; use crate::web_identity_token::{ Builder, ENV_VAR_ROLE_ARN, ENV_VAR_SESSION_NAME, ENV_VAR_TOKEN_FILE, }; - use aws_sdk_sts::Region; - use aws_types::os_shim_internal::{Env, Fs}; - - use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_types::credentials::CredentialsError; + use aws_types::os_shim_internal::{Env, Fs}; use std::collections::HashMap; #[tokio::test] async fn unloaded_provider() { // empty environment let conf = ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_env(Env::from_slice(&[])) .with_http_connector(no_traffic_connector()) .with_region(Some(Region::from_static("us-east-1"))); @@ -297,6 +297,7 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_region(region) .with_env(env) .with_http_connector(no_traffic_connector()), @@ -328,6 +329,7 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() + .with_sleep(TokioSleep::new()) .with_http_connector(no_traffic_connector()) .with_region(Some(Region::new("us-east-1"))) .with_env(env) diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index 6c0715c535033e3904a406d9545c7e8fc22323b9..98ce157cb2a7cf4f9e9c84758a6a7e29556b4a8e 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -35,6 +35,10 @@ pub struct SdkConfig { } /// Builder for AWS Shared Configuration +/// +/// _Important:_ Using the `aws-config` crate to configure the SDK is preferred to invoking this +/// builder directly. Using this builder directly won't pull in any AWS recommended default +/// configuration values. #[derive(Debug, Default)] pub struct Builder { app_name: Option, @@ -127,12 +131,15 @@ impl Builder { /// Set the retry_config for the builder /// + /// _Note:_ Retries require a sleep implementation in order to work. When enabling retry, make + /// sure to set one with [Self::sleep_impl] or [Self::set_sleep_impl]. + /// /// # Examples /// ```rust /// use aws_types::SdkConfig; /// use aws_smithy_types::retry::RetryConfig; /// - /// let retry_config = RetryConfig::new().with_max_attempts(5); + /// let retry_config = RetryConfig::standard().with_max_attempts(5); /// let config = SdkConfig::builder().retry_config(retry_config).build(); /// ``` pub fn retry_config(mut self, retry_config: RetryConfig) -> Self { @@ -142,13 +149,16 @@ impl Builder { /// Set the retry_config for the builder /// + /// _Note:_ Retries require a sleep implementation in order to work. When enabling retry, make + /// sure to set one with [Self::sleep_impl] or [Self::set_sleep_impl]. + /// /// # Examples /// ```rust /// use aws_types::sdk_config::{SdkConfig, Builder}; /// use aws_smithy_types::retry::RetryConfig; /// /// fn disable_retries(builder: &mut Builder) { - /// let retry_config = RetryConfig::new().with_max_attempts(1); + /// let retry_config = RetryConfig::standard().with_max_attempts(1); /// builder.set_retry_config(Some(retry_config)); /// } /// @@ -163,6 +173,10 @@ impl Builder { /// Set the [`timeout::Config`](aws_smithy_types::timeout::Config) for the builder /// + /// _Note:_ Timeouts require a sleep implementation in order to work. + /// When enabling timeouts, be sure to set one with [Self::sleep_impl] or + /// [Self::set_sleep_impl]. + /// /// # Examples /// /// ```rust @@ -184,6 +198,10 @@ impl Builder { /// Set the [`timeout::Config`](aws_smithy_types::timeout::Config) for the builder /// + /// _Note:_ Timeouts require a sleep implementation in order to work. + /// When enabling timeouts, be sure to set one with [Self::sleep_impl] or + /// [Self::set_sleep_impl]. + /// /// # Examples /// ```rust /// # use std::time::Duration; @@ -211,6 +229,9 @@ impl Builder { /// Set the sleep implementation for the builder. The sleep implementation is used to create /// timeout futures. /// + /// _Note:_ If you're using the Tokio runtime, a `TokioSleep` implementation is available in + /// the `aws-smithy-async` crate. + /// /// # Examples /// /// ```rust @@ -238,6 +259,9 @@ impl Builder { /// Set the sleep implementation for the builder. The sleep implementation is used to create /// timeout futures. /// + /// _Note:_ If you're using the Tokio runtime, a `TokioSleep` implementation is available in + /// the `aws-smithy-async` crate. + /// /// # Examples /// ```rust /// # use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep}; @@ -405,6 +429,10 @@ impl SdkConfig { } /// Config builder + /// + /// _Important:_ Using the `aws-config` crate to configure the SDK is preferred to invoking this + /// builder directly. Using this builder directly won't pull in any AWS recommended default + /// configuration values. pub fn builder() -> Builder { Builder::default() } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index d9aa69781eb9fd6ff27562bc318609635a3d2267..f51f0b3d1ebdc7864a79ebc420efb9ccd2b5c079 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -35,6 +35,7 @@ import software.amazon.smithy.rust.codegen.util.expectTrait import software.amazon.smithy.rustsdk.AwsRuntimeType.defaultMiddleware private class Types(runtimeConfig: RuntimeConfig) { + private val smithyTypesDep = CargoDependency.SmithyTypes(runtimeConfig) private val smithyClientDep = CargoDependency.SmithyClient(runtimeConfig) private val smithyHttpDep = CargoDependency.SmithyHttp(runtimeConfig) @@ -46,6 +47,7 @@ private class Types(runtimeConfig: RuntimeConfig) { val dynConnector = RuntimeType("DynConnector", smithyClientDep, "aws_smithy_client::erase") val dynMiddleware = RuntimeType("DynMiddleware", smithyClientDep, "aws_smithy_client::erase") val smithyConnector = RuntimeType("SmithyConnector", smithyClientDep, "aws_smithy_client::bounds") + val retryConfig = RuntimeType("RetryConfig", smithyTypesDep, "aws_smithy_types::retry") val connectorError = RuntimeType("ConnectorError", smithyHttpDep, "aws_smithy_http::result") } @@ -118,14 +120,15 @@ class AwsFluentClientDecorator : RustCodegenDecorator { private class AwsFluentClientExtensions(types: Types) { private val codegenScope = arrayOf( - "Middleware" to types.defaultMiddleware, - "retry" to types.smithyClientRetry, + "ConnectorError" to types.connectorError, "DynConnector" to types.dynConnector, "DynMiddleware" to types.dynMiddleware, + "Middleware" to types.defaultMiddleware, + "RetryConfig" to types.retryConfig, "SmithyConnector" to types.smithyConnector, - "ConnectorError" to types.connectorError, "aws_smithy_client" to types.awsSmithyClient, "aws_types" to types.awsTypes, + "retry" to types.smithyClientRetry, ) fun render(writer: RustWriter) { @@ -138,7 +141,7 @@ private class AwsFluentClientExtensions(types: Types) { C: #{SmithyConnector} + Send + 'static, E: Into<#{ConnectorError}>, { - let retry_config = conf.retry_config().cloned().unwrap_or_default(); + let retry_config = conf.retry_config().cloned().unwrap_or_else(#{RetryConfig}::disabled); let timeout_config = conf.timeout_config().cloned().unwrap_or_default(); let mut builder = #{aws_smithy_client}::Builder::new() .connector(#{DynConnector}::new(conn)) @@ -161,15 +164,20 @@ private class AwsFluentClientExtensions(types: Types) { /// Creates a new client from the service [`Config`](crate::Config). ##[cfg(any(feature = "rustls", feature = "native-tls"))] pub fn from_conf(conf: crate::Config) -> Self { - let retry_config = conf.retry_config().cloned().unwrap_or_default(); + let retry_config = conf.retry_config().cloned().unwrap_or_else(#{RetryConfig}::disabled); let timeout_config = conf.timeout_config().cloned().unwrap_or_default(); + let sleep_impl = conf.sleep_impl(); + if (retry_config.has_retry() || timeout_config.has_timeouts()) && sleep_impl.is_none() { + panic!("An async sleep implementation is required for retries or timeouts to work. \ + Set the `sleep_impl` on the Config passed into this function to fix this panic."); + } let mut builder = #{aws_smithy_client}::Builder::dyn_https() .middleware(#{DynMiddleware}::new(#{Middleware}::new())); builder.set_retry_config(retry_config.into()); builder.set_timeout_config(timeout_config); // the builder maintains a try-state. To avoid suppressing the warning when sleep is unset, // only set it if we actually have a sleep impl. - if let Some(sleep_impl) = conf.sleep_impl() { + if let Some(sleep_impl) = sleep_impl { builder.set_sleep_impl(Some(sleep_impl)); } let client = builder.build(); diff --git a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs index c1613d86294840a7a0892ba8b43c7ac1a613de2d..1b2740d7596b9869bf990c3af5822b536450c6c9 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs @@ -6,6 +6,7 @@ use aws_sdk_dynamodb::types::SdkError; use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep}; use aws_smithy_client::never::NeverConnector; +use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout; use aws_smithy_types::timeout::Api; use aws_smithy_types::tristate::TriState; @@ -34,6 +35,7 @@ async fn api_call_timeout_retries() { .timeout_config(timeout::Config::new().with_api_timeouts( Api::new().with_call_attempt_timeout(TriState::Set(Duration::new(123, 0))), )) + .retry_config(RetryConfig::standard()) .sleep_impl(Arc::new(InstantSleep)) .build(); let client = aws_sdk_dynamodb::Client::from_conf_conn( @@ -69,6 +71,7 @@ async fn no_retries_on_operation_timeout() { .timeout_config(timeout::Config::new().with_api_timeouts( Api::new().with_call_timeout(TriState::Set(Duration::new(123, 0))), )) + .retry_config(RetryConfig::standard()) .sleep_impl(Arc::new(InstantSleep)) .build(); let client = aws_sdk_dynamodb::Client::from_conf_conn( diff --git a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs index b2071833111746dc2ed2bcb4db6b4a38f020130b..0ff185734fe323718c9660a58ca62823cf22366a 100644 --- a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_config::RetryConfig; use aws_sdk_s3::model::{ CompressionType, CsvInput, CsvOutput, ExpressionType, FileHeaderInfo, InputSerialization, OutputSerialization, }; use aws_sdk_s3::{Client, Config, Credentials, Region}; +use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep}; use aws_smithy_client::never::NeverConnector; use aws_smithy_http::result::SdkError; use aws_smithy_types::timeout; use aws_smithy_types::tristate::TriState; - -use aws_smithy_async::assert_elapsed; use std::fmt::Debug; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -131,6 +131,7 @@ async fn retry_test(sleep_impl: Arc) -> Result<(), Box rustTemplate( + """ + retry_config: Option<#{RetryConfig}>, + sleep_impl: Option>, + timeout_config: Option<#{TimeoutConfig}>, + """, + *codegenScope, + ) + is ServiceConfig.ConfigImpl -> { + rustTemplate( + """ + /// Return a reference to the retry configuration contained in this config, if any. + pub fn retry_config(&self) -> Option<&#{RetryConfig}> { + self.retry_config.as_ref() + } + + /// Return a cloned Arc containing the async sleep implementation from this config, if any. + pub fn sleep_impl(&self) -> Option> { + self.sleep_impl.clone() + } + + /// Return a reference to the timeout configuration contained in this config, if any. + pub fn timeout_config(&self) -> Option<&#{TimeoutConfig}> { + self.timeout_config.as_ref() + } + """, + *codegenScope, + ) + } + is ServiceConfig.BuilderStruct -> + rustTemplate( + """ + retry_config: Option<#{RetryConfig}>, + sleep_impl: Option>, + timeout_config: Option<#{TimeoutConfig}>, + """, + *codegenScope, + ) + ServiceConfig.BuilderImpl -> + rustTemplate( + """ + /// Set the retry_config for the builder + /// + /// ## Examples + /// ```no_run + /// use $moduleUseName::config::Config; + /// use #{RetryConfig}; + /// + /// let retry_config = RetryConfig::standard().with_max_attempts(5); + /// let config = Config::builder().retry_config(retry_config).build(); + /// ``` + pub fn retry_config(mut self, retry_config: #{RetryConfig}) -> Self { + self.set_retry_config(Some(retry_config)); + self + } + + /// Set the retry_config for the builder + /// + /// ## Examples + /// ```no_run + /// use $moduleUseName::config::{Builder, Config}; + /// use #{RetryConfig}; + /// + /// fn disable_retries(builder: &mut Builder) { + /// let retry_config = RetryConfig::standard().with_max_attempts(1); + /// builder.set_retry_config(Some(retry_config)); + /// } + /// + /// let mut builder = Config::builder(); + /// disable_retries(&mut builder); + /// let config = builder.build(); + /// ``` + pub fn set_retry_config(&mut self, retry_config: Option<#{RetryConfig}>) -> &mut Self { + self.retry_config = retry_config; + self + } + + /// Set the sleep_impl for the builder + /// + /// ## Examples + /// + /// ```no_run + /// use $moduleUseName::config::Config; + /// use #{AsyncSleep}; + /// use #{Sleep}; + /// + /// ##[derive(Debug)] + /// pub struct ForeverSleep; + /// + /// impl AsyncSleep for ForeverSleep { + /// fn sleep(&self, duration: std::time::Duration) -> Sleep { + /// Sleep::new(std::future::pending()) + /// } + /// } + /// + /// let sleep_impl = std::sync::Arc::new(ForeverSleep); + /// let config = Config::builder().sleep_impl(sleep_impl).build(); + /// ``` + pub fn sleep_impl(mut self, sleep_impl: std::sync::Arc) -> Self { + self.set_sleep_impl(Some(sleep_impl)); + self + } + + /// Set the sleep_impl for the builder + /// + /// ## Examples + /// + /// ```no_run + /// use $moduleUseName::config::{Builder, Config}; + /// use #{AsyncSleep}; + /// use #{Sleep}; + /// + /// ##[derive(Debug)] + /// pub struct ForeverSleep; + /// + /// impl AsyncSleep for ForeverSleep { + /// fn sleep(&self, duration: std::time::Duration) -> Sleep { + /// Sleep::new(std::future::pending()) + /// } + /// } + /// + /// fn set_never_ending_sleep_impl(builder: &mut Builder) { + /// let sleep_impl = std::sync::Arc::new(ForeverSleep); + /// builder.set_sleep_impl(Some(sleep_impl)); + /// } + /// + /// let mut builder = Config::builder(); + /// set_never_ending_sleep_impl(&mut builder); + /// let config = builder.build(); + /// ``` + pub fn set_sleep_impl(&mut self, sleep_impl: Option>) -> &mut Self { + self.sleep_impl = sleep_impl; + self + } + + /// Set the timeout_config for the builder + /// + /// ## Examples + /// + /// ```no_run + /// ## use std::time::Duration; + /// use $moduleUseName::config::Config; + /// use aws_smithy_types::{timeout, tristate::TriState}; + /// + /// let api_timeouts = timeout::Api::new() + /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); + /// let timeout_config = timeout::Config::new() + /// .with_api_timeouts(api_timeouts); + /// let config = Config::builder().timeout_config(timeout_config).build(); + /// ``` + pub fn timeout_config(mut self, timeout_config: #{TimeoutConfig}) -> Self { + self.set_timeout_config(Some(timeout_config)); + self + } + + /// Set the timeout_config for the builder + /// + /// ## Examples + /// + /// ```no_run + /// ## use std::time::Duration; + /// use $moduleUseName::config::{Builder, Config}; + /// use aws_smithy_types::{timeout, tristate::TriState}; + /// + /// fn set_request_timeout(builder: &mut Builder) { + /// let api_timeouts = timeout::Api::new() + /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); + /// let timeout_config = timeout::Config::new() + /// .with_api_timeouts(api_timeouts); + /// builder.set_timeout_config(Some(timeout_config)); + /// } + /// + /// let mut builder = Config::builder(); + /// set_request_timeout(&mut builder); + /// let config = builder.build(); + /// ``` + pub fn set_timeout_config(&mut self, timeout_config: Option<#{TimeoutConfig}>) -> &mut Self { + self.timeout_config = timeout_config; + self + } + """, + *codegenScope, + ) + ServiceConfig.BuilderBuild -> rustTemplate( + """ + retry_config: self.retry_config, + sleep_impl: self.sleep_impl, + timeout_config: self.timeout_config, + """, + *codegenScope, + ) + else -> emptySection + } + } +} + +class ResiliencyReExportCustomization(private val runtimeConfig: RuntimeConfig) : LibRsCustomization() { + override fun section(section: LibRsSection): Writable { + return when (section) { + is LibRsSection.Body -> writable { + rustTemplate( + """ + pub use #{retry}::RetryConfig; + pub use #{sleep}::AsyncSleep; + pub use #{timeout}::Config as TimeoutConfig; + """, + "retry" to smithyTypesRetry(runtimeConfig), + "sleep" to smithyAsyncRtSleep(runtimeConfig), + "timeout" to smithyTypesTimeout(runtimeConfig), + ) + } + else -> emptySection + } + } +} + +// Generate path to the retry module in aws_smithy_types +private fun smithyTypesRetry(runtimeConfig: RuntimeConfig) = + RuntimeType("retry", runtimeConfig.runtimeCrate("types"), "aws_smithy_types") + +// Generate path to the root module in aws_smithy_async +private fun smithyAsyncRtSleep(runtimeConfig: RuntimeConfig) = + RuntimeType("sleep", runtimeConfig.runtimeCrate("async"), "aws_smithy_async::rt") + +// Generate path to the timeout module in aws_smithy_types +private fun smithyTypesTimeout(runtimeConfig: RuntimeConfig) = + RuntimeType("timeout", runtimeConfig.runtimeCrate("types"), "aws_smithy_types") diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/RetryConfigDecorator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/RetryConfigDecorator.kt deleted file mode 100644 index 280f6165598754d8a389b362f91ecca8963a814f..0000000000000000000000000000000000000000 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/RetryConfigDecorator.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.smithy.customizations - -import software.amazon.smithy.rust.codegen.rustlang.Writable -import software.amazon.smithy.rust.codegen.rustlang.rust -import software.amazon.smithy.rust.codegen.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.rustlang.writable -import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext -import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig -import software.amazon.smithy.rust.codegen.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.smithy.generators.LibRsCustomization -import software.amazon.smithy.rust.codegen.smithy.generators.LibRsSection -import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization -import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig - -/* Example Generated Code */ -/* -pub struct Config { - pub(crate) retry_config: Option, -} -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut config = f.debug_struct("Config"); - config.finish() - } -} -impl Config { - pub fn builder() -> Builder { - Builder::default() - } -} -#[derive(Default)] -pub struct Builder { - retry_config: Option, -} -impl Builder { - pub fn new() -> Self { - Self::default() - } - pub fn retry_config(mut self, retry_config: aws_smithy_types::retry::RetryConfig) -> Self { - self.set_retry_config(Some(retry_config)); - self - } - pub fn set_retry_config( - &mut self, - retry_config: Option, - ) -> &mut Self { - self.retry_config = retry_config; - self - } - pub fn build(self) -> Config { - Config { - retry_config: self.retry_config, - } - } -} -#[test] -fn test_1() { - fn assert_send_sync() {} - assert_send_sync::(); -} - */ - -class RetryConfigProviderCustomization(coreCodegenContext: CoreCodegenContext) : ConfigCustomization() { - private val retryConfig = smithyTypesRetry(coreCodegenContext.runtimeConfig) - private val moduleUseName = coreCodegenContext.moduleUseName() - private val codegenScope = arrayOf("RetryConfig" to retryConfig.member("RetryConfig")) - override fun section(section: ServiceConfig) = writable { - when (section) { - is ServiceConfig.ConfigStruct -> rustTemplate( - "retry_config: Option<#{RetryConfig}>,", - *codegenScope, - ) - is ServiceConfig.ConfigImpl -> { - rustTemplate( - """ - /// Return a reference to the retry configuration contained in this config, if any. - pub fn retry_config(&self) -> Option<&#{RetryConfig}> { self.retry_config.as_ref() } - """, - *codegenScope, - ) - } - is ServiceConfig.BuilderStruct -> - rustTemplate("retry_config: Option<#{RetryConfig}>,", *codegenScope) - ServiceConfig.BuilderImpl -> - rustTemplate( - """ - /// Set the retry_config for the builder - /// - /// ## Examples - /// ```no_run - /// use $moduleUseName::config::Config; - /// use #{RetryConfig}; - /// - /// let retry_config = RetryConfig::new().with_max_attempts(5); - /// let config = Config::builder().retry_config(retry_config).build(); - /// ``` - pub fn retry_config(mut self, retry_config: #{RetryConfig}) -> Self { - self.set_retry_config(Some(retry_config)); - self - } - - /// Set the retry_config for the builder - /// - /// ## Examples - /// ```no_run - /// use $moduleUseName::config::{Builder, Config}; - /// use #{RetryConfig}; - /// - /// fn disable_retries(builder: &mut Builder) { - /// let retry_config = RetryConfig::new().with_max_attempts(1); - /// builder.set_retry_config(Some(retry_config)); - /// } - /// - /// let mut builder = Config::builder(); - /// disable_retries(&mut builder); - /// let config = builder.build(); - /// ``` - pub fn set_retry_config(&mut self, retry_config: Option<#{RetryConfig}>) -> &mut Self { - self.retry_config = retry_config; - self - } - """, - *codegenScope, - ) - ServiceConfig.BuilderBuild -> rustTemplate( - """retry_config: self.retry_config,""", - *codegenScope, - ) - else -> emptySection - } - } -} - -class PubUseRetryConfigGenerator(private val runtimeConfig: RuntimeConfig) : LibRsCustomization() { - override fun section(section: LibRsSection): Writable { - return when (section) { - is LibRsSection.Body -> writable { - rust("pub use #T::RetryConfig;", smithyTypesRetry(runtimeConfig)) - } - else -> emptySection - } - } -} - -// Generate path to the retry module in aws_smithy_types -fun smithyTypesRetry(runtimeConfig: RuntimeConfig) = - RuntimeType("retry", runtimeConfig.runtimeCrate("types"), "aws_smithy_types") diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/SleepImplDecorator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/SleepImplDecorator.kt deleted file mode 100644 index a2436a3e434761194b94a404db1eeaa399593582..0000000000000000000000000000000000000000 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/SleepImplDecorator.kt +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.smithy.customizations - -import software.amazon.smithy.rust.codegen.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.rustlang.writable -import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext -import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig -import software.amazon.smithy.rust.codegen.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization -import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig - -/* Example Generated Code */ -/* -/// Service config. -/// -pub struct Config { - pub(crate) sleep_impl: Option>, -} -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut config = f.debug_struct("Config"); - config.finish() - } -} -impl Config { - /// Constructs a config builder. - pub fn builder() -> Builder { - Builder::default() - } -} -/// Builder for creating a `Config`. -#[derive(Default)] -pub struct Builder { - sleep_impl: Option>, -} -impl Builder { - /// Constructs a config builder. - pub fn new() -> Self { - Self::default() - } - /// Set the sleep_impl for the builder - /// - /// # Examples - /// - /// ```no_run - /// use test_smithy_test1832442648477221704::config::Config; - /// use aws_smithy_async::rt::sleep::AsyncSleep; - /// use aws_smithy_async::rt::sleep::Sleep; - /// - /// #[derive(Debug)] - /// pub struct ForeverSleep; - /// - /// impl AsyncSleep for ForeverSleep { - /// fn sleep(&self, duration: std::time::Duration) -> Sleep { - /// Sleep::new(std::future::pending()) - /// } - /// } - /// - /// let sleep_impl = std::sync::Arc::new(ForeverSleep); - /// let config = Config::builder().sleep_impl(sleep_impl).build(); - /// ``` - pub fn sleep_impl( - mut self, - sleep_impl: std::sync::Arc, - ) -> Self { - self.set_sleep_impl(Some(sleep_impl)); - self - } - - /// Set the sleep_impl for the builder - /// - /// # Examples - /// - /// ```no_run - /// use test_smithy_test1832442648477221704::config::{Builder, Config}; - /// use aws_smithy_async::rt::sleep::AsyncSleep; - /// use aws_smithy_async::rt::sleep::Sleep; - /// - /// #[derive(Debug)] - /// pub struct ForeverSleep; - /// - /// impl AsyncSleep for ForeverSleep { - /// fn sleep(&self, duration: std::time::Duration) -> Sleep { - /// Sleep::new(std::future::pending()) - /// } - /// } - /// - /// fn set_never_ending_sleep_impl(builder: &mut Builder) { - /// let sleep_impl = std::sync::Arc::new(ForeverSleep); - /// builder.set_sleep_impl(Some(sleep_impl)); - /// } - /// - /// let mut builder = Config::builder(); - /// set_never_ending_sleep_impl(&mut builder); - /// let config = builder.build(); - /// ``` - pub fn set_sleep_impl( - &mut self, - sleep_impl: Option>, - ) -> &mut Self { - self.sleep_impl = sleep_impl; - self - } - /// Builds a [`Config`]. - pub fn build(self) -> Config { - Config { - sleep_impl: self.sleep_impl - } - } -} - */ - -class SleepImplProviderCustomization(coreCodegenContext: CoreCodegenContext) : ConfigCustomization() { - private val sleepModule = smithyAsyncRtSleep(coreCodegenContext.runtimeConfig) - private val moduleUseName = coreCodegenContext.moduleUseName() - private val codegenScope = arrayOf( - "AsyncSleep" to sleepModule.member("AsyncSleep"), - "Sleep" to sleepModule.member("Sleep"), - ) - - override fun section(section: ServiceConfig) = writable { - when (section) { - is ServiceConfig.ConfigStruct -> rustTemplate( - "sleep_impl: Option>,", - *codegenScope, - ) - is ServiceConfig.ConfigImpl -> { - rustTemplate( - """ - /// Return a cloned Arc containing the async sleep implementation from this config, if any. - pub fn sleep_impl(&self) -> Option> { self.sleep_impl.clone() } - """, - *codegenScope, - ) - } - is ServiceConfig.BuilderStruct -> - rustTemplate("sleep_impl: Option>,", *codegenScope) - ServiceConfig.BuilderImpl -> - rustTemplate( - """ - /// Set the sleep_impl for the builder - /// - /// ## Examples - /// - /// ```no_run - /// use $moduleUseName::config::Config; - /// use #{AsyncSleep}; - /// use #{Sleep}; - /// - /// ##[derive(Debug)] - /// pub struct ForeverSleep; - /// - /// impl AsyncSleep for ForeverSleep { - /// fn sleep(&self, duration: std::time::Duration) -> Sleep { - /// Sleep::new(std::future::pending()) - /// } - /// } - /// - /// let sleep_impl = std::sync::Arc::new(ForeverSleep); - /// let config = Config::builder().sleep_impl(sleep_impl).build(); - /// ``` - pub fn sleep_impl(mut self, sleep_impl: std::sync::Arc) -> Self { - self.set_sleep_impl(Some(sleep_impl)); - self - } - - /// Set the sleep_impl for the builder - /// - /// ## Examples - /// - /// ```no_run - /// use $moduleUseName::config::{Builder, Config}; - /// use #{AsyncSleep}; - /// use #{Sleep}; - /// - /// ##[derive(Debug)] - /// pub struct ForeverSleep; - /// - /// impl AsyncSleep for ForeverSleep { - /// fn sleep(&self, duration: std::time::Duration) -> Sleep { - /// Sleep::new(std::future::pending()) - /// } - /// } - /// - /// fn set_never_ending_sleep_impl(builder: &mut Builder) { - /// let sleep_impl = std::sync::Arc::new(ForeverSleep); - /// builder.set_sleep_impl(Some(sleep_impl)); - /// } - /// - /// let mut builder = Config::builder(); - /// set_never_ending_sleep_impl(&mut builder); - /// let config = builder.build(); - /// ``` - pub fn set_sleep_impl(&mut self, sleep_impl: Option>) -> &mut Self { - self.sleep_impl = sleep_impl; - self - } - """, - *codegenScope, - ) - ServiceConfig.BuilderBuild -> rustTemplate( - """sleep_impl: self.sleep_impl,""", - *codegenScope, - ) - else -> emptySection - } - } -} - -// Generate path to the root module in aws_smithy_async -fun smithyAsyncRtSleep(runtimeConfig: RuntimeConfig) = - RuntimeType("sleep", runtimeConfig.runtimeCrate("async"), "aws_smithy_async::rt") diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/TimeoutConfigDecorator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/TimeoutConfigDecorator.kt deleted file mode 100644 index 3501079e380bf76a8b995971715975afadb153f6..0000000000000000000000000000000000000000 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customizations/TimeoutConfigDecorator.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.smithy.customizations - -import software.amazon.smithy.rust.codegen.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.rustlang.writable -import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext -import software.amazon.smithy.rust.codegen.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization -import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig - -/* Example Generated Code */ -/* -pub struct Config { - pub(crate) timeout_config: Option, -} -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut config = f.debug_struct("Config"); - config.finish() - } -} -impl Config { - /// Constructs a config builder. - pub fn builder() -> Builder { - Builder::default() - } -} -/// Builder for creating a `Config`. -#[derive(Default)] -pub struct Builder { - timeout_config: Option, -} -impl Builder { - /// Constructs a config builder. - pub fn new() -> Self { - Self::default() - } - /// Set the timeout_config for the builder - /// - /// # Examples - /// - /// ```no_run - /// # use std::time::Duration; - /// use test_smithy_test2036416049427740159::config::Config; - /// use aws_smithy_types::{timeout, tristate::TriState}; - /// - /// let api_timeouts = timeout::Api::new() - /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); - /// let timeout_config = timeout::Config::new() - /// .with_api_timeouts(api_timeouts); - /// let config = Config::builder().timeout_config(timeout_config).build(); - /// ``` - pub fn timeout_config(mut self, timeout_config: aws_smithy_types::timeout::Config) -> Self { - self.set_timeout_config(Some(timeout_config)); - self - } - - /// Set the timeout_config for the builder - /// - /// # Examples - /// - /// ```no_run - /// # use std::time::Duration; - /// use test_smithy_test2036416049427740159::config::{Builder, Config}; - /// use aws_smithy_types::{timeout, tristate::TriState}; - /// - /// fn set_request_timeout(builder: &mut Builder) { - /// let api_timeouts = timeout::Api::new() - /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); - /// let timeout_config = timeout::Config::new() - /// .with_api_timeouts(api_timeouts); - /// builder.set_timeout_config(Some(timeout_config)); - /// } - /// - /// let mut builder = Config::builder(); - /// set_request_timeout(&mut builder); - /// let config = builder.build(); - /// ``` - pub fn set_timeout_config( - &mut self, - timeout_config: Option, - ) -> &mut Self { - self.timeout_config = timeout_config; - self - } - /// Builds a [`Config`]. - pub fn build(self) -> Config { - Config { - timeout_config: self.timeout_config, - } - } -} -#[test] -fn test_1() { - fn assert_send_sync() {} - assert_send_sync::(); -} - */ - -class TimeoutConfigProviderCustomization(coreCodegenContext: CoreCodegenContext) : ConfigCustomization() { - private val smithyTypesCrate = coreCodegenContext.runtimeConfig.runtimeCrate("types") - private val timeoutModule = RuntimeType("timeout", smithyTypesCrate, "aws_smithy_types") - private val moduleUseName = coreCodegenContext.moduleUseName() - private val codegenScope = arrayOf( - "TimeoutConfig" to timeoutModule.member("Config"), - ) - override fun section(section: ServiceConfig) = writable { - when (section) { - is ServiceConfig.ConfigStruct -> rustTemplate( - "timeout_config: Option<#{TimeoutConfig}>,", - *codegenScope, - ) - is ServiceConfig.ConfigImpl -> { - rustTemplate( - """ - /// Return a reference to the timeout configuration contained in this config, if any. - pub fn timeout_config(&self) -> Option<&#{TimeoutConfig}> { self.timeout_config.as_ref() } - """, - *codegenScope, - ) - } - is ServiceConfig.BuilderStruct -> - rustTemplate("timeout_config: Option<#{TimeoutConfig}>,", *codegenScope) - ServiceConfig.BuilderImpl -> - rustTemplate( - """ - /// Set the timeout_config for the builder - /// - /// ## Examples - /// - /// ```no_run - /// ## use std::time::Duration; - /// use $moduleUseName::config::Config; - /// use aws_smithy_types::{timeout, tristate::TriState}; - /// - /// let api_timeouts = timeout::Api::new() - /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); - /// let timeout_config = timeout::Config::new() - /// .with_api_timeouts(api_timeouts); - /// let config = Config::builder().timeout_config(timeout_config).build(); - /// ``` - pub fn timeout_config(mut self, timeout_config: #{TimeoutConfig}) -> Self { - self.set_timeout_config(Some(timeout_config)); - self - } - - /// Set the timeout_config for the builder - /// - /// ## Examples - /// - /// ```no_run - /// ## use std::time::Duration; - /// use $moduleUseName::config::{Builder, Config}; - /// use aws_smithy_types::{timeout, tristate::TriState}; - /// - /// fn set_request_timeout(builder: &mut Builder) { - /// let api_timeouts = timeout::Api::new() - /// .with_call_attempt_timeout(TriState::Set(Duration::from_secs(1))); - /// let timeout_config = timeout::Config::new() - /// .with_api_timeouts(api_timeouts); - /// builder.set_timeout_config(Some(timeout_config)); - /// } - /// - /// let mut builder = Config::builder(); - /// set_request_timeout(&mut builder); - /// let config = builder.build(); - /// ``` - pub fn set_timeout_config(&mut self, timeout_config: Option<#{TimeoutConfig}>) -> &mut Self { - self.timeout_config = timeout_config; - self - } - """, - *codegenScope, - ) - ServiceConfig.BuilderBuild -> rustTemplate( - """timeout_config: self.timeout_config,""", - *codegenScope, - ) - else -> emptySection - } - } -} diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customize/RequiredCustomizations.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customize/RequiredCustomizations.kt index 5af1a3288e907390fa90e4fa0318baf22a213b83..56a56cb6b6af312cd8f127d710b3c1570bc85222 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customize/RequiredCustomizations.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/customize/RequiredCustomizations.kt @@ -16,11 +16,9 @@ import software.amazon.smithy.rust.codegen.smithy.customizations.EndpointPrefixG import software.amazon.smithy.rust.codegen.smithy.customizations.HttpChecksumRequiredGenerator import software.amazon.smithy.rust.codegen.smithy.customizations.HttpVersionListCustomization import software.amazon.smithy.rust.codegen.smithy.customizations.IdempotencyTokenGenerator -import software.amazon.smithy.rust.codegen.smithy.customizations.PubUseRetryConfigGenerator -import software.amazon.smithy.rust.codegen.smithy.customizations.RetryConfigProviderCustomization -import software.amazon.smithy.rust.codegen.smithy.customizations.SleepImplProviderCustomization +import software.amazon.smithy.rust.codegen.smithy.customizations.ResiliencyConfigCustomization +import software.amazon.smithy.rust.codegen.smithy.customizations.ResiliencyReExportCustomization import software.amazon.smithy.rust.codegen.smithy.customizations.SmithyTypesPubUseGenerator -import software.amazon.smithy.rust.codegen.smithy.customizations.TimeoutConfigProviderCustomization import software.amazon.smithy.rust.codegen.smithy.generators.LibRsCustomization import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization @@ -48,10 +46,7 @@ class RequiredCustomizations : RustCodegenDecorator { codegenContext: ClientCodegenContext, baseCustomizations: List, ): List = - baseCustomizations + - RetryConfigProviderCustomization(codegenContext) + - SleepImplProviderCustomization(codegenContext) + - TimeoutConfigProviderCustomization(codegenContext) + baseCustomizations + ResiliencyConfigCustomization(codegenContext) override fun libRsCustomizations( codegenContext: ClientCodegenContext, @@ -60,7 +55,7 @@ class RequiredCustomizations : RustCodegenDecorator { baseCustomizations + CrateVersionGenerator() + SmithyTypesPubUseGenerator(codegenContext.runtimeConfig) + AllowLintsGenerator() + - PubUseRetryConfigGenerator(codegenContext.runtimeConfig) + ResiliencyReExportCustomization(codegenContext.runtimeConfig) override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { // Add rt-tokio feature for `ByteStream::from_path` diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/HttpVersionListGeneratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/HttpVersionListGeneratorTest.kt index 45c5bae1ac4daf7a73bbed417bce17849f03e9d9..efb2927e14f6d4cf358189afae20e39ba4a1f14a 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/HttpVersionListGeneratorTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/HttpVersionListGeneratorTest.kt @@ -13,19 +13,17 @@ import software.amazon.smithy.rust.codegen.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.rustlang.writable import software.amazon.smithy.rust.codegen.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.smithy.CodegenVisitor -import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.smithy.RustCrate import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator import software.amazon.smithy.rust.codegen.smithy.customize.RequiredCustomizations -import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig import software.amazon.smithy.rust.codegen.testutil.TokioTest import software.amazon.smithy.rust.codegen.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.testutil.generatePluginContext import software.amazon.smithy.rust.codegen.util.runCommand +import software.amazon.smithy.rust.testutil.AddRustTestsDecorator // If any of these tests fail, and you want to understand why, run them with logging: // ``` @@ -60,39 +58,30 @@ internal class HttpVersionListGeneratorTest { """.asSmithyModel() val (ctx, testDir) = generatePluginContext(model) val moduleName = ctx.settings.expectStringMember("module").value.replace('-', '_') - val testWriter = object : RustCodegenDecorator { - override val name: String = "add tests" - override val order: Byte = 0 - - override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { - rustCrate.withFile("tests/validate_defaults.rs") { - TokioTest.render(it) - it.rust( - """ - async fn test_http_version_list_defaults() { - let conf = $moduleName::Config::builder().build(); - let op = $moduleName::operation::SayHello::builder() - .greeting("hello") - .build().expect("valid operation") - .make_operation(&conf).await.expect("hello is a valid prefix"); - let properties = op.properties(); - let actual_http_versions = properties.get::>() - .expect("http versions list should be in property bag"); - let expected_http_versions = &vec![http::Version::HTTP_11]; - assert_eq!(actual_http_versions, expected_http_versions); - } - """, - ) - } - } - - override fun supportsCodegenContext(clazz: Class): Boolean = - clazz.isAssignableFrom(ClientCodegenContext::class.java) - } val combinedCodegenDecorator: CombinedCodegenDecorator = - CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()).withDecorator(testWriter) - val visitor = CodegenVisitor(ctx, combinedCodegenDecorator) - visitor.execute() + CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()) + .withDecorator( + AddRustTestsDecorator("validate_defaults") { + TokioTest.render(this) + rust( + """ + async fn test_http_version_list_defaults() { + let conf = $moduleName::Config::builder().build(); + let op = $moduleName::operation::SayHello::builder() + .greeting("hello") + .build().expect("valid operation") + .make_operation(&conf).await.expect("hello is a valid prefix"); + let properties = op.properties(); + let actual_http_versions = properties.get::>() + .expect("http versions list should be in property bag"); + let expected_http_versions = &vec![http::Version::HTTP_11]; + assert_eq!(actual_http_versions, expected_http_versions); + } + """, + ) + }, + ) + CodegenVisitor(ctx, combinedCodegenDecorator).execute() "cargo test".runCommand(testDir) } @@ -125,40 +114,30 @@ internal class HttpVersionListGeneratorTest { """.asSmithyModel() val (ctx, testDir) = generatePluginContext(model) val moduleName = ctx.settings.expectStringMember("module").value.replace('-', '_') - val testWriter = object : RustCodegenDecorator { - override val name: String = "add tests" - override val order: Byte = 0 - - override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { - rustCrate.withFile("tests/validate_http.rs") { - TokioTest.render(it) - it.rust( - """ - async fn test_http_version_list_defaults() { - let conf = $moduleName::Config::builder().build(); - let op = $moduleName::operation::SayHello::builder() - .greeting("hello") - .build().expect("valid operation") - .make_operation(&conf).await.expect("hello is a valid prefix"); - let properties = op.properties(); - let actual_http_versions = properties.get::>() - .expect("http versions list should be in property bag"); - let expected_http_versions = &vec![http::Version::HTTP_11, http::Version::HTTP_2]; - assert_eq!(actual_http_versions, expected_http_versions); - } - """, - ) - } - } - - override fun supportsCodegenContext(clazz: Class): Boolean = - clazz.isAssignableFrom(ClientCodegenContext::class.java) - } - val combinedCodegenDecorator: CombinedCodegenDecorator = - CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()).withDecorator(testWriter) - val visitor = CodegenVisitor(ctx, combinedCodegenDecorator) - visitor.execute() + CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()) + .withDecorator( + AddRustTestsDecorator("validate_http") { + TokioTest.render(this) + rust( + """ + async fn test_http_version_list_defaults() { + let conf = $moduleName::Config::builder().build(); + let op = $moduleName::operation::SayHello::builder() + .greeting("hello") + .build().expect("valid operation") + .make_operation(&conf).await.expect("hello is a valid prefix"); + let properties = op.properties(); + let actual_http_versions = properties.get::>() + .expect("http versions list should be in property bag"); + let expected_http_versions = &vec![http::Version::HTTP_11, http::Version::HTTP_2]; + assert_eq!(actual_http_versions, expected_http_versions); + } + """, + ) + }, + ) + CodegenVisitor(ctx, combinedCodegenDecorator).execute() "cargo test".runCommand(testDir) } @@ -204,21 +183,12 @@ internal class HttpVersionListGeneratorTest { val (ctx, testDir) = generatePluginContext(model, addModuleToEventStreamAllowList = true) val moduleName = ctx.settings.expectStringMember("module").value.replace('-', '_') - val codegenDecorator = object : RustCodegenDecorator { - override val name: String = "add tests" - override val order: Byte = 0 - - override fun configCustomizations( - codegenContext: ClientCodegenContext, - baseCustomizations: List, - ): List { - return super.configCustomizations(codegenContext, baseCustomizations) + FakeSigningConfig(codegenContext.runtimeConfig) - } - override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) { - rustCrate.withFile("tests/validate_eventstream_http.rs") { - TokioTest.render(it) - it.rust( + val combinedCodegenDecorator: CombinedCodegenDecorator = + CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()) + .withDecorator(object : AddRustTestsDecorator("validate_eventstream_http", { + TokioTest.render(this) + rust( """ async fn test_http_version_list_defaults() { let conf = $moduleName::Config::builder().build(); @@ -233,17 +203,16 @@ internal class HttpVersionListGeneratorTest { } """, ) - } - } - - override fun supportsCodegenContext(clazz: Class): Boolean = - clazz.isAssignableFrom(ClientCodegenContext::class.java) - } - - val combinedCodegenDecorator: CombinedCodegenDecorator = - CombinedCodegenDecorator.fromClasspath(ctx, RequiredCustomizations()).withDecorator(codegenDecorator) - val visitor = CodegenVisitor(ctx, combinedCodegenDecorator) - visitor.execute() + },) { + override fun configCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List { + return super.configCustomizations(codegenContext, baseCustomizations) + FakeSigningConfig(codegenContext.runtimeConfig) + } + }, + ) + CodegenVisitor(ctx, combinedCodegenDecorator).execute() "cargo test".runCommand(testDir) } } diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/SleepImplDecoratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/ResiliencyConfigCustomizationTest.kt similarity index 88% rename from codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/SleepImplDecoratorTest.kt rename to codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/ResiliencyConfigCustomizationTest.kt index 613b2a716213139c5ed426804a69a643593cfc49..1e3e49e36f65bc1c14143108812eaccc081825f7 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/SleepImplDecoratorTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/ResiliencyConfigCustomizationTest.kt @@ -6,7 +6,7 @@ package software.amazon.smithy.rust.codegen.customizations import org.junit.jupiter.api.Test -import software.amazon.smithy.rust.codegen.smithy.customizations.SleepImplProviderCustomization +import software.amazon.smithy.rust.codegen.smithy.customizations.ResiliencyConfigCustomization import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.testutil.TestWorkspace @@ -15,7 +15,7 @@ import software.amazon.smithy.rust.codegen.testutil.rustSettings import software.amazon.smithy.rust.codegen.testutil.testCodegenContext import software.amazon.smithy.rust.codegen.testutil.validateConfigCustomizations -internal class SleepImplDecoratorTest { +internal class ResiliencyConfigCustomizationTest { private val baseModel = """ namespace test use aws.protocols#awsQuery @@ -38,6 +38,6 @@ internal class SleepImplDecoratorTest { val project = TestWorkspace.testProject() val codegenContext = testCodegenContext(model, settings = project.rustSettings()) - validateConfigCustomizations(SleepImplProviderCustomization(codegenContext), project) + validateConfigCustomizations(ResiliencyConfigCustomization(codegenContext), project) } } diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/RetryConfigDecoratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/RetryConfigDecoratorTest.kt deleted file mode 100644 index 67eb40cd1750dffc4c7e5fe742d8aefc769d3564..0000000000000000000000000000000000000000 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/RetryConfigDecoratorTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.customizations - -import org.junit.jupiter.api.Test -import software.amazon.smithy.rust.codegen.smithy.customizations.RetryConfigProviderCustomization -import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormalizer -import software.amazon.smithy.rust.codegen.smithy.transformers.RecursiveShapeBoxer -import software.amazon.smithy.rust.codegen.testutil.TestWorkspace -import software.amazon.smithy.rust.codegen.testutil.asSmithyModel -import software.amazon.smithy.rust.codegen.testutil.rustSettings -import software.amazon.smithy.rust.codegen.testutil.testCodegenContext -import software.amazon.smithy.rust.codegen.testutil.validateConfigCustomizations - -internal class RetryConfigDecoratorTest { - private val baseModel = """ - namespace test - use aws.protocols#awsQuery - - structure SomeOutput { - @xmlAttribute - someAttribute: Long, - - someVal: String - } - - operation SomeOperation { - output: SomeOutput - } - """.asSmithyModel() - - @Test - fun `generates a valid config`() { - val model = RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel)) - val project = TestWorkspace.testProject() - val codegenContext = testCodegenContext(model, settings = project.rustSettings()) - - validateConfigCustomizations(RetryConfigProviderCustomization(codegenContext), project) - } -} diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/TimeoutConfigDecoratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/TimeoutConfigDecoratorTest.kt deleted file mode 100644 index 20ff1a3cbd3bdc6b5eaf9ad859bc11452e317ec7..0000000000000000000000000000000000000000 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/customizations/TimeoutConfigDecoratorTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.customizations - -import org.junit.jupiter.api.Test -import software.amazon.smithy.rust.codegen.smithy.customizations.TimeoutConfigProviderCustomization -import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormalizer -import software.amazon.smithy.rust.codegen.smithy.transformers.RecursiveShapeBoxer -import software.amazon.smithy.rust.codegen.testutil.TestWorkspace -import software.amazon.smithy.rust.codegen.testutil.asSmithyModel -import software.amazon.smithy.rust.codegen.testutil.rustSettings -import software.amazon.smithy.rust.codegen.testutil.testCodegenContext -import software.amazon.smithy.rust.codegen.testutil.validateConfigCustomizations - -internal class TimeoutConfigDecoratorTest { - private val baseModel = """ - namespace test - use aws.protocols#awsQuery - - structure SomeOutput { - @xmlAttribute - someAttribute: Long, - - someVal: String - } - - operation SomeOperation { - output: SomeOutput - } - """.asSmithyModel() - - @Test - fun `generates a valid config`() { - val model = RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel)) - val project = TestWorkspace.testProject() - val codegenContext = testCodegenContext(model, settings = project.rustSettings()) - - validateConfigCustomizations(TimeoutConfigProviderCustomization(codegenContext), project) - } -} diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/testutil/AddRustTestsDecorator.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/testutil/AddRustTestsDecorator.kt new file mode 100644 index 0000000000000000000000000000000000000000..00c4da0ccb7d4e3e289f71146b28a993584ed0d3 --- /dev/null +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/testutil/AddRustTestsDecorator.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.rust.testutil + +import software.amazon.smithy.rust.codegen.rustlang.Writable +import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext +import software.amazon.smithy.rust.codegen.smithy.RustCrate +import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator + +open class AddRustTestsDecorator( + private val testsFileName: String, + private val testWritable: Writable, +) : RustCodegenDecorator { + override val name: String = "add tests" + override val order: Byte = 0 + + override fun extras(codegenContext: C, rustCrate: RustCrate) { + rustCrate.withFile("tests/$testsFileName.rs") { writer -> + writer.testWritable() + } + } + + // Don't allow this class to be discovered on the classpath; always return false + override fun supportsCodegenContext(clazz: Class): Boolean = false +} diff --git a/rust-runtime/aws-smithy-client/Cargo.toml b/rust-runtime/aws-smithy-client/Cargo.toml index c919bdbb851a647b5fdd94dee14e8e89f158d936..30337e9db56c77063068fa582e5ad45cc4dc2c0f 100644 --- a/rust-runtime/aws-smithy-client/Cargo.toml +++ b/rust-runtime/aws-smithy-client/Cargo.toml @@ -40,7 +40,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.8.4", features = ["full", "test-util"] } tower-test = "0.4.0" -tracing-test = "0.2.1" [package.metadata.docs.rs] all-features = true diff --git a/rust-runtime/aws-smithy-client/src/builder.rs b/rust-runtime/aws-smithy-client/src/builder.rs index a2e6070ede0c1397a19905844b936b2a3b917686..a9f5586fc15e878ad943bdd41d9a9db4bf4cab9a 100644 --- a/rust-runtime/aws-smithy-client/src/builder.rs +++ b/rust-runtime/aws-smithy-client/src/builder.rs @@ -5,7 +5,7 @@ use std::sync::Arc; -use crate::{bounds, erase, retry, Client, TriState, MISSING_SLEEP_IMPL_RECOMMENDATION}; +use crate::{bounds, erase, retry, Client}; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; @@ -16,13 +16,34 @@ use aws_smithy_types::timeout; /// To start, call [`Builder::new`]. Then, chain the method calls to configure the `Builder`. /// When configured to your liking, call [`Builder::build`]. The individual methods have additional /// documentation. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Builder { connector: C, middleware: M, + // Keep a copy of standard retry config when the standard policy is used + // so that we can do additional validation against the `sleep_impl` when + // the client. + standard_retry_config: Option, retry_policy: R, timeout_config: timeout::Config, - sleep_impl: TriState>, + sleep_impl: Option>, +} + +impl Default for Builder +where + C: Default, + M: Default, +{ + fn default() -> Self { + Self { + connector: Default::default(), + middleware: Default::default(), + standard_retry_config: Some(retry::Config::default()), + retry_policy: Default::default(), + timeout_config: Default::default(), + sleep_impl: default_async_sleep(), + } + } } // It'd be nice to include R where R: Default here, but then the caller ends up always having to @@ -58,6 +79,7 @@ impl Builder<(), M, R> { pub fn connector(self, connector: C) -> Builder { Builder { connector, + standard_retry_config: self.standard_retry_config, retry_policy: self.retry_policy, middleware: self.middleware, timeout_config: self.timeout_config, @@ -114,6 +136,7 @@ impl Builder { pub fn middleware(self, middleware: M) -> Builder { Builder { connector: self.connector, + standard_retry_config: self.standard_retry_config, retry_policy: self.retry_policy, timeout_config: self.timeout_config, middleware, @@ -165,6 +188,8 @@ impl Builder { pub fn retry_policy(self, retry_policy: R) -> Builder { Builder { connector: self.connector, + // Intentionally clear out the standard retry config when the retry policy is overridden. + standard_retry_config: None, retry_policy, timeout_config: self.timeout_config, middleware: self.middleware, @@ -176,6 +201,7 @@ impl Builder { impl Builder { /// Set the standard retry policy's configuration. pub fn set_retry_config(&mut self, config: retry::Config) { + self.standard_retry_config = Some(config.clone()); self.retry_policy.with_config(config); } @@ -186,7 +212,7 @@ impl Builder { /// Set the [`AsyncSleep`] function that the [`Client`] will use to create things like timeout futures. pub fn set_sleep_impl(&mut self, async_sleep: Option>) { - self.sleep_impl = async_sleep.into(); + self.sleep_impl = async_sleep; } /// Set the [`AsyncSleep`] function that the [`Client`] will use to create things like timeout futures. @@ -194,12 +220,6 @@ impl Builder { self.set_sleep_impl(async_sleep); self } - - /// Sets the sleep implementation to [`default_async_sleep`]. - pub fn default_async_sleep(mut self) -> Self { - self.sleep_impl = TriState::or_unset(default_async_sleep()); - self - } } impl Builder { @@ -211,6 +231,7 @@ impl Builder { Builder { connector: map(self.connector), middleware: self.middleware, + standard_retry_config: self.standard_retry_config, retry_policy: self.retry_policy, timeout_config: self.timeout_config, sleep_impl: self.sleep_impl, @@ -225,6 +246,7 @@ impl Builder { Builder { connector: self.connector, middleware: map(self.middleware), + standard_retry_config: self.standard_retry_config, retry_policy: self.retry_policy, timeout_config: self.timeout_config, sleep_impl: self.sleep_impl, @@ -233,20 +255,21 @@ impl Builder { /// Build a Smithy service [`Client`]. pub fn build(self) -> Client { - if matches!(self.sleep_impl, TriState::Unset) { - if self.timeout_config.has_timeouts() { - tracing::warn!( - "One or more timeouts were set, but no `sleep_impl` was passed into the \ - builder. Timeouts and retry both require a sleep implementation. No timeouts \ - will occur with the current configuration. {}", - MISSING_SLEEP_IMPL_RECOMMENDATION - ); - } else { - tracing::warn!( - "Retries require a `sleep_impl`, but none was passed into the builder. \ - No retries will occur with the current configuration. {}", - MISSING_SLEEP_IMPL_RECOMMENDATION - ); + if self.sleep_impl.is_none() { + const ADDITIONAL_HELP: &str = + "Either disable retry by setting max attempts to one, or pass in a `sleep_impl`. \ + If you're not using Tokio, then an implementation of the `AsyncSleep` trait from \ + the `aws-smithy-async` crate is required for your async runtime. If you are using \ + Tokio, then make sure the `rt-tokio` feature is enabled to have its sleep \ + implementation set automatically."; + if self + .standard_retry_config + .map(|src| src.has_retry()) + .unwrap_or(false) + { + panic!("Retries require a `sleep_impl`, but none was passed into the builder. {ADDITIONAL_HELP}"); + } else if self.timeout_config.has_timeouts() { + panic!("Timeouts require a `sleep_impl`, but none was passed into the builder. {ADDITIONAL_HELP}"); } } @@ -298,6 +321,7 @@ mod tests { use aws_smithy_async::rt::sleep::Sleep; use aws_smithy_types::timeout; use aws_smithy_types::tristate::TriState; + use std::panic::{self, AssertUnwindSafe}; use std::time::Duration; #[derive(Clone, Debug)] @@ -308,54 +332,92 @@ mod tests { } } - const TIMEOUTS_WITHOUT_SLEEP_MSG: &str = - "One or more timeouts were set, but no `sleep_impl` was passed into the builder"; - const RETRIES_WITHOUT_SLEEP_MSG: &str = - "Retries require a `sleep_impl`, but none was passed into the builder."; - const RECOMMENDATION_MSG: &str = - "consider using the `aws-config` crate to load a shared config"; + #[test] + fn defaults_dont_panic() { + let builder = Builder::new() + .connector(NeverConnector::new()) + .middleware(tower::layer::util::Identity::new()); + + let _ = builder.build(); + } #[test] - #[tracing_test::traced_test] - fn sleep_impl_given_no_warns() { - let _client = Builder::new() + fn defaults_panic_if_default_tokio_sleep_not_available() { + let mut builder = Builder::new() .connector(NeverConnector::new()) - .middleware(tower::layer::util::Identity::new()) - .sleep_impl(Some(Arc::new(StubSleep))) - .build(); + .middleware(tower::layer::util::Identity::new()); + builder.set_sleep_impl(None); - assert!(!logs_contain(TIMEOUTS_WITHOUT_SLEEP_MSG)); - assert!(!logs_contain(RETRIES_WITHOUT_SLEEP_MSG)); - assert!(!logs_contain(RECOMMENDATION_MSG)); + let result = panic::catch_unwind(AssertUnwindSafe(move || { + let _ = builder.build(); + })); + assert!(result.is_err()); } #[test] - #[tracing_test::traced_test] - fn timeout_missing_sleep_impl_warn() { + fn timeouts_without_sleep_panics() { let mut builder = Builder::new() .connector(NeverConnector::new()) .middleware(tower::layer::util::Identity::new()); - let http_timeout_config = - timeout::Http::new().with_connect_timeout(TriState::Set(Duration::from_secs(1))); - let timeout_config = timeout::Config::new().with_http_timeouts(http_timeout_config); + builder.set_sleep_impl(None); + + let timeout_config = timeout::Config::new().with_http_timeouts( + timeout::Http::new().with_connect_timeout(TriState::Set(Duration::from_secs(1))), + ); + assert!(timeout_config.has_timeouts()); builder.set_timeout_config(timeout_config); - builder.build(); - assert!(logs_contain(TIMEOUTS_WITHOUT_SLEEP_MSG)); - assert!(!logs_contain(RETRIES_WITHOUT_SLEEP_MSG)); - assert!(logs_contain(RECOMMENDATION_MSG)); + let result = panic::catch_unwind(AssertUnwindSafe(move || { + let _ = builder.build(); + })); + assert!(result.is_err()); + } + + #[test] + fn retry_without_sleep_panics() { + let mut builder = Builder::new() + .connector(NeverConnector::new()) + .middleware(tower::layer::util::Identity::new()); + builder.set_sleep_impl(None); + + let retry_config = retry::Config::default(); + assert!(retry_config.has_retry()); + builder.set_retry_config(retry_config); + + let result = panic::catch_unwind(AssertUnwindSafe(move || { + let _ = builder.build(); + })); + assert!(result.is_err()); } #[test] - #[tracing_test::traced_test] - fn retry_missing_sleep_impl_warn() { - Builder::new() + fn custom_retry_policy_without_sleep_doesnt_panic() { + let mut builder = Builder::new() .connector(NeverConnector::new()) .middleware(tower::layer::util::Identity::new()) - .build(); + // Using standard retry here as a shortcut in the test; someone setting + // a custom retry policy would manually implement the required traits + .retry_policy(retry::Standard::default()); + builder.set_sleep_impl(None); + let _ = builder.build(); + } + + #[test] + fn no_panics_when_sleep_given() { + let mut builder = Builder::new() + .connector(NeverConnector::new()) + .middleware(tower::layer::util::Identity::new()); + + let timeout_config = timeout::Config::new().with_http_timeouts( + timeout::Http::new().with_connect_timeout(TriState::Set(Duration::from_secs(1))), + ); + assert!(timeout_config.has_timeouts()); + builder.set_timeout_config(timeout_config); + + let retry_config = retry::Config::default(); + assert!(retry_config.has_retry()); + builder.set_retry_config(retry_config); - assert!(!logs_contain(TIMEOUTS_WITHOUT_SLEEP_MSG)); - assert!(logs_contain(RETRIES_WITHOUT_SLEEP_MSG)); - assert!(logs_contain(RECOMMENDATION_MSG)); + let _ = builder.build(); } } diff --git a/rust-runtime/aws-smithy-client/src/hyper_ext.rs b/rust-runtime/aws-smithy-client/src/hyper_ext.rs index c3fc2e50c07c57a34897afaa6ef8ff1b466a4faf..0da4238a22c1d2276e39093cb5319faf1534053b 100644 --- a/rust-runtime/aws-smithy-client/src/hyper_ext.rs +++ b/rust-runtime/aws-smithy-client/src/hyper_ext.rs @@ -265,9 +265,6 @@ where /// Create a Smithy client builder with an HTTPS connector and the [standard retry /// policy](crate::retry::Standard) over the default middleware implementation. /// - /// *Note:* This function **does not** set a sleep implementation to ensure that [`default_async_sleep`](crate::Builder::default_async_sleep) - /// or [`set_sleep_impl`](crate::Builder::set_sleep_impl) is called. - /// /// For convenience, this constructor type-erases the concrete TLS connector backend used using /// dynamic dispatch. This comes at a slight runtime performance cost. See /// [`DynConnector`](crate::erase::DynConnector) for details. To avoid that overhead, use @@ -299,9 +296,7 @@ where /// [`DynConnector`](crate::erase::DynConnector) for details. To avoid that overhead, use /// [`Builder::rustls`](ClientBuilder::rustls) or `Builder::native_tls` instead. pub fn dyn_https() -> Self { - ClientBuilder::::dyn_https() - .default_async_sleep() - .build() + ClientBuilder::::dyn_https().build() } } diff --git a/rust-runtime/aws-smithy-client/src/lib.rs b/rust-runtime/aws-smithy-client/src/lib.rs index 0215241ea72c09c8b668c85b7f687cc476ba0d58..b5d44612e77c3311545af2938a88c67db39b93bd 100644 --- a/rust-runtime/aws-smithy-client/src/lib.rs +++ b/rust-runtime/aws-smithy-client/src/lib.rs @@ -98,7 +98,6 @@ use aws_smithy_http::retry::ClassifyResponse; use aws_smithy_http_tower::dispatch::DispatchLayer; use aws_smithy_http_tower::parse_response::ParseResponseLayer; use aws_smithy_types::retry::ProvideErrorKind; -use aws_smithy_types::tristate::TriState; /// Smithy service client. /// @@ -129,7 +128,7 @@ pub struct Client< middleware: Middleware, retry_policy: RetryPolicy, timeout_config: aws_smithy_types::timeout::Config, - sleep_impl: TriState>, + sleep_impl: Option>, } // Quick-create for people who just want "the default". @@ -144,7 +143,6 @@ where Builder::new() .connector(connector) .middleware(M::default()) - .default_async_sleep() .build() } } @@ -181,7 +179,7 @@ impl Client { /// /// *Note: If `None` is passed, this will prevent the client from using retries or timeouts.* pub fn set_sleep_impl(&mut self, sleep_impl: Option>) { - self.sleep_impl = sleep_impl.clone().into(); + self.sleep_impl = sleep_impl; } /// Set the [`AsyncSleep`] function that the client will use to create things like timeout futures. @@ -237,26 +235,18 @@ where bounds::Parsed<>::Service, O, Retry>: Service, Response = SdkSuccess, Error = SdkError> + Clone, { - if matches!(&self.sleep_impl, TriState::Unset) { - // during requests, debug log (a warning is emitted during client construction) - tracing::debug!( - "Client does not have a sleep implementation. Timeouts and retry \ - will not work without this. {}", - MISSING_SLEEP_IMPL_RECOMMENDATION - ); - } let connector = self.connector.clone(); let timeout_service_params = generate_timeout_service_params_from_timeout_config( &self.timeout_config.api, - self.sleep_impl.clone().into(), + self.sleep_impl.clone(), ); let svc = ServiceBuilder::new() .layer(TimeoutLayer::new(timeout_service_params.api_call)) .retry( self.retry_policy - .new_request_policy(self.sleep_impl.clone().into()), + .new_request_policy(self.sleep_impl.clone()), ) .layer(TimeoutLayer::new(timeout_service_params.api_call_attempt)) .layer(ParseResponseLayer::::new()) @@ -288,10 +278,3 @@ where }; } } - -pub(crate) const MISSING_SLEEP_IMPL_RECOMMENDATION: &str = - "If this was intentional, you can suppress this message with `Client::set_sleep_impl(None). \ - Otherwise, unless you have a good reason to use the low-level service \ - client API, consider using the `aws-config` crate to load a shared config from \ - the environment, and construct a fluent client from that. If you need to use the low-level \ - service client API, then pass in a sleep implementation to make timeouts and retry work."; diff --git a/rust-runtime/aws-smithy-client/src/retry.rs b/rust-runtime/aws-smithy-client/src/retry.rs index f40a1734b1a78221e22d3bedce4be2366f626eee..3c9bdc27c5b3d104702d72c824212a4adb336569 100644 --- a/rust-runtime/aws-smithy-client/src/retry.rs +++ b/rust-runtime/aws-smithy-client/src/retry.rs @@ -3,11 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Retry support for aws-hyper -//! -//! The actual retry policy implementation will likely be replaced -//! with the CRT implementation once the bindings exist. This -//! implementation is intended to be _correct_ but not especially long lasting. +//! Retry support //! //! Components: //! - [`Standard`]: Top level manager, intended to be associated with a [`Client`](crate::Client). @@ -102,6 +98,11 @@ impl Config { self.initial_backoff = initial_backoff; self } + + /// Returns true if retry is enabled with this config + pub fn has_retry(&self) -> bool { + self.max_attempts > 1 + } } impl Default for Config { @@ -134,13 +135,8 @@ const RETRY_COST: usize = 5; /// Manage retries for a service /// -/// An implementation of the `standard` AWS retry strategy as specified in the SEP. A `Strategy` is scoped to a client. +/// An implementation of the `standard` AWS retry strategy. A `Strategy` is scoped to a client. /// For an individual request, call [`Standard::new_request_policy()`](Standard::new_request_policy) -/// -/// In the future, adding support for the adaptive retry strategy will be added by adding a `TokenBucket` to -/// `CrossRequestRetryState` -/// Its main functionality is via `new_request_policy` which creates a `RetryHandler` to manage the retry for -/// an individual request. #[derive(Debug, Clone)] pub struct Standard { config: Config, diff --git a/rust-runtime/aws-smithy-types/src/retry.rs b/rust-runtime/aws-smithy-types/src/retry.rs index 03b00b29d6044ad99ab4dde6a782f9ebce47d184..56556154bb424aae3b6758dedcfeaaaccf09dc36 100644 --- a/rust-runtime/aws-smithy-types/src/retry.rs +++ b/rust-runtime/aws-smithy-types/src/retry.rs @@ -226,13 +226,17 @@ pub struct RetryConfig { impl RetryConfig { /// Creates a default `RetryConfig` with `RetryMode::Standard` and max attempts of three. - pub fn new() -> Self { - Default::default() + pub fn standard() -> Self { + Self { + mode: RetryMode::Standard, + max_attempts: 3, + initial_backoff: Duration::from_secs(1), + } } /// Creates a `RetryConfig` that has retries disabled. pub fn disabled() -> Self { - Self::default().with_max_attempts(1) + Self::standard().with_max_attempts(1) } /// Set this config's [retry mode](RetryMode). @@ -284,15 +288,10 @@ impl RetryConfig { pub fn initial_backoff(&self) -> Duration { self.initial_backoff } -} -impl Default for RetryConfig { - fn default() -> Self { - Self { - mode: RetryMode::Standard, - max_attempts: 3, - initial_backoff: Duration::from_secs(1), - } + /// Returns true if retry is enabled with this config + pub fn has_retry(&self) -> bool { + self.max_attempts > 1 } } diff --git a/rust-runtime/aws-smithy-types/src/timeout/config.rs b/rust-runtime/aws-smithy-types/src/timeout/config.rs index a35529e797cd83132fc6b226c82a20979a1e28e3..5ced915b6eccd992927b0a75bb69f8f709d7b1f9 100644 --- a/rust-runtime/aws-smithy-types/src/timeout/config.rs +++ b/rust-runtime/aws-smithy-types/src/timeout/config.rs @@ -111,7 +111,7 @@ impl Config { } } - /// Returns true if any of the possible timeouts are se + /// Returns true if any of the possible timeouts are set pub fn has_timeouts(&self) -> bool { self.api.has_timeouts() || self.http.has_timeouts() || self.tcp.has_timeouts() }