diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs b/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs index da778c3737..2250e7927e 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs +++ b/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs @@ -59,6 +59,7 @@ async fn sra_test() { let _ = dbg!( client .list_objects_v2() + .config_override(aws_sdk_s3::Config::builder().force_path_style(false)) .bucket("test-bucket") .prefix("prefix~") .send_v2() 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 3be412dfee..ac5804aed9 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 @@ -40,14 +40,17 @@ class ServiceGenerator( ).render(rustCrate) rustCrate.withModule(ClientRustModule.Config) { - ServiceConfigGenerator.withBaseBehavior( + val serviceConfigGenerator = ServiceConfigGenerator.withBaseBehavior( codegenContext, extraCustomizations = decorator.configCustomizations(codegenContext, listOf()), - ).render(this) + ) + serviceConfigGenerator.render(this) if (codegenContext.settings.codegenConfig.enableNewSmithyRuntime) { ServiceRuntimePluginGenerator(codegenContext) .render(this, decorator.serviceRuntimePluginCustomizations(codegenContext, emptyList())) + + serviceConfigGenerator.renderRuntimePluginImplForBuilder(this, codegenContext) } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt index 6669ee04f1..062e9d3d0a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt @@ -33,10 +33,12 @@ import software.amazon.smithy.rust.codegen.core.rustlang.escape import software.amazon.smithy.rust.codegen.core.rustlang.normalizeHtml import software.amazon.smithy.rust.codegen.core.rustlang.qualifiedName 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.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTypeParameters import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter +import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate 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 @@ -60,11 +62,13 @@ class FluentClientGenerator( client = RuntimeType.smithyClient(codegenContext.runtimeConfig), ), private val customizations: List = emptyList(), - private val retryClassifier: RuntimeType = RuntimeType.smithyHttp(codegenContext.runtimeConfig).resolve("retry::DefaultResponseRetryClassifier"), + private val retryClassifier: RuntimeType = RuntimeType.smithyHttp(codegenContext.runtimeConfig) + .resolve("retry::DefaultResponseRetryClassifier"), ) { companion object { fun clientOperationFnName(operationShape: OperationShape, symbolProvider: RustSymbolProvider): String = RustReservedWords.escapeIfNeeded(symbolProvider.toSymbol(operationShape).name.toSnakeCase()) + fun clientOperationModuleName(operationShape: OperationShape, symbolProvider: RustSymbolProvider): String = RustReservedWords.escapeIfNeeded( symbolProvider.toSymbol(operationShape).name.toSnakeCase(), @@ -79,6 +83,7 @@ class FluentClientGenerator( private val model = codegenContext.model private val runtimeConfig = codegenContext.runtimeConfig private val core = FluentClientCore(model) + private val enableNewSmithyRuntime = codegenContext.settings.codegenConfig.enableNewSmithyRuntime fun render(crate: RustCrate) { renderFluentClient(crate) @@ -242,18 +247,23 @@ class FluentClientGenerator( documentShape(operation, model, autoSuppressMissingDocs = false) deprecatedShape(operation) Attribute(derive(derives.toSet())).render(this) - rustTemplate( - """ - pub struct $builderName#{generics:W} { + withBlockTemplate( + "pub struct $builderName#{generics:W} {", + "}", + "generics" to generics.decl, + ) { + rustTemplate( + """ handle: std::sync::Arc, - inner: #{Inner} + inner: #{Inner}, + """, + "Inner" to symbolProvider.symbolForBuilder(input), + "generics" to generics.decl, + ) + if (enableNewSmithyRuntime) { + rust("config_override: std::option::Option,") } - """, - "Inner" to symbolProvider.symbolForBuilder(input), - "client" to RuntimeType.smithyClient(runtimeConfig), - "generics" to generics.decl, - "operation" to operationSymbol, - ) + } rustBlockTemplate( "impl${generics.inst} $builderName${generics.inst} #{bounds:W}", @@ -263,14 +273,24 @@ class FluentClientGenerator( val outputType = symbolProvider.toSymbol(operation.outputShape(model)) val errorType = symbolProvider.symbolForOperationError(operation) - // Have to use fully-qualified result here or else it could conflict with an op named Result + rust("/// Creates a new `${operationSymbol.name}`.") + withBlockTemplate( + "pub(crate) fn new(handle: std::sync::Arc) -> Self {", + "}", + "generics" to generics.decl, + ) { + withBlockTemplate( + "Self {", + "}", + ) { + rust("handle, inner: Default::default(),") + if (enableNewSmithyRuntime) { + rust("config_override: None,") + } + } + } rustTemplate( """ - /// Creates a new `${operationSymbol.name}`. - pub(crate) fn new(handle: std::sync::Arc) -> Self { - Self { handle, inner: Default::default() } - } - /// Consume this builder, creating a customizable operation that can be modified before being /// sent. The operation's inner [http::Request] can be modified as well. pub async fn customize(self) -> std::result::Result< @@ -316,7 +336,7 @@ class FluentClientGenerator( generics.toRustGenerics(), ), ) - if (codegenContext.settings.codegenConfig.enableNewSmithyRuntime) { + if (enableNewSmithyRuntime) { rustTemplate( """ // TODO(enableNewSmithyRuntime): Replace `send` with `send_v2` @@ -329,9 +349,12 @@ class FluentClientGenerator( /// is configurable with the [RetryConfig](aws_smithy_types::retry::RetryConfig), which can be /// set when configuring the client. pub async fn send_v2(self) -> std::result::Result<#{OperationOutput}, #{SdkError}<#{OperationError}, #{HttpResponse}>> { - let runtime_plugins = #{RuntimePlugins}::new() - .with_client_plugin(crate::config::ServiceRuntimePlugin::new(self.handle.clone())) - .with_operation_plugin(#{Operation}::new()); + let mut runtime_plugins = #{RuntimePlugins}::new() + .with_client_plugin(crate::config::ServiceRuntimePlugin::new(self.handle.clone())); + if let Some(config_override) = self.config_override { + runtime_plugins = runtime_plugins.with_operation_plugin(config_override); + } + runtime_plugins = runtime_plugins.with_operation_plugin(#{Operation}::new()); let input = self.inner.build().map_err(#{SdkError}::construction_failure)?; let input = #{TypedBox}::new(input).erase(); let output = #{invoke}(input, &runtime_plugins) @@ -346,29 +369,72 @@ class FluentClientGenerator( Ok(#{TypedBox}::<#{OperationOutput}>::assume_from(output).expect("correct output type").unwrap()) } """, - "HttpResponse" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::HttpResponse"), + "HttpResponse" to RuntimeType.smithyRuntimeApi(runtimeConfig) + .resolve("client::orchestrator::HttpResponse"), "OperationError" to errorType, "Operation" to symbolProvider.toSymbol(operation), "OperationOutput" to outputType, - "RuntimePlugins" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::runtime_plugin::RuntimePlugins"), + "RuntimePlugins" to RuntimeType.smithyRuntimeApi(runtimeConfig) + .resolve("client::runtime_plugin::RuntimePlugins"), "SdkError" to RuntimeType.sdkError(runtimeConfig), "TypedBox" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("type_erasure::TypedBox"), "invoke" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::orchestrator::invoke"), ) - } - PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier)?.also { paginatorType -> + rustTemplate( """ - /// Create a paginator for this request + /// Sets the `config_override` for the builder. + /// + /// `config_override` is applied to the operation configuration level. + /// The fields in the builder that are `Some` override those applied to the service + /// configuration level. For instance, + /// + /// Config A overridden by Config B == Config C + /// field_1: None, field_1: Some(v2), field_1: Some(v2), + /// field_2: Some(v1), field_2: Some(v2), field_2: Some(v2), + /// field_3: Some(v1), field_3: None, field_3: Some(v1), + pub fn config_override( + mut self, + config_override: impl Into, + ) -> Self { + self.set_config_override(Some(config_override.into())); + self + } + + /// Sets the `config_override` for the builder. + /// + /// `config_override` is applied to the operation configuration level. + /// The fields in the builder that are `Some` override those applied to the service + /// configuration level. For instance, /// - /// Paginators are used by calling [`send().await`](#{Paginator}::send) which returns a `Stream`. - pub fn into_paginator(self) -> #{Paginator}${generics.inst} { - #{Paginator}::new(self.handle, self.inner) + /// Config A overridden by Config B == Config C + /// field_1: None, field_1: Some(v2), field_1: Some(v2), + /// field_2: Some(v1), field_2: Some(v2), field_2: Some(v2), + /// field_3: Some(v1), field_3: None, field_3: Some(v1), + pub fn set_config_override( + &mut self, + config_override: Option, + ) -> &mut Self { + self.config_override = config_override; + self } """, - "Paginator" to paginatorType, ) } + PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier) + ?.also { paginatorType -> + rustTemplate( + """ + /// Create a paginator for this request + /// + /// Paginators are used by calling [`send().await`](#{Paginator}::send) which returns a `Stream`. + pub fn into_paginator(self) -> #{Paginator}${generics.inst} { + #{Paginator}::new(self.handle, self.inner) + } + """, + "Paginator" to paginatorType, + ) + } writeCustomizations( customizations, FluentClientSection.FluentBuilderImpl( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt index 00bc592ff4..f6972f5e6a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGenerator.kt @@ -20,9 +20,11 @@ import software.amazon.smithy.rust.codegen.core.rustlang.docsOrFallback import software.amazon.smithy.rust.codegen.core.rustlang.raw 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.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.customize.NamedCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.Section import software.amazon.smithy.rust.codegen.core.smithy.makeOptional @@ -226,7 +228,7 @@ class ServiceConfigGenerator(private val customizations: List Result<(), #{BoxError}> { + // TODO(RuntimePlugins): Put into `cfg` the fields in `self.config_override` that are not `None`. + + Ok(()) + } + """, + "BoxError" to runtimeApi.resolve("client::runtime_plugin::BoxError"), + "ConfigBag" to runtimeApi.resolve("config_bag::ConfigBag"), + ) + } + } } diff --git a/rust-runtime/inlineable/src/idempotency_token.rs b/rust-runtime/inlineable/src/idempotency_token.rs index 9d55f46db5..ac71874bc1 100644 --- a/rust-runtime/inlineable/src/idempotency_token.rs +++ b/rust-runtime/inlineable/src/idempotency_token.rs @@ -37,10 +37,12 @@ pub(crate) fn uuid_v4(input: u128) -> String { /// for testing, two options are available: /// 1. Utilize the From<&'static str>` implementation to hard code an idempotency token /// 2. Seed the token provider with [`IdempotencyTokenProvider::with_seed`](IdempotencyTokenProvider::with_seed) +#[derive(Debug)] pub struct IdempotencyTokenProvider { inner: Inner, } +#[derive(Debug)] enum Inner { Static(&'static str), Random(Mutex),