Unverified Commit 165253cd authored by ysaito1001's avatar ysaito1001 Committed by GitHub
Browse files

Add skeleton for supporting operation level config (#2589)

## Motivation and Context
This PR adds skeleton code for supporting operation level config behind
`enableNewSmithyRuntime`.

## Description
The approach is described in
https://github.com/awslabs/smithy-rs/pull/2527

, where fluent builders
now have a new method, `config_override`, that takes a service config
builder. The builder implements the `RuntimePlugin` trait and once
`TODO(RuntimePlugins)` is implemented, allows itself to put field values
into a config bag within `send_v2`.

## Testing
Added a line of code to an ignored test in `sra_test`, which, however,
does check compilation.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._

---------

Co-authored-by: default avatarYuki Saito <awsaito@amazon.com>
Co-authored-by: default avatarJohn DiSanti <jdisanti@amazon.com>
parent 2e6b6345
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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()
+5 −2
Original line number Diff line number Diff line
@@ -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)
            }
        }

+96 −30
Original line number Diff line number Diff line
@@ -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<FluentClientCustomization> = 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)
        withBlockTemplate(
            "pub struct $builderName#{generics:W} {",
            "}",
            "generics" to generics.decl,
        ) {
            rustTemplate(
                """
            pub struct $builderName#{generics:W} {
                handle: std::sync::Arc<crate::client::Handle${generics.inst}>,
                inner: #{Inner}
            }
                inner: #{Inner},
                """,
                "Inner" to symbolProvider.symbolForBuilder(input),
            "client" to RuntimeType.smithyClient(runtimeConfig),
                "generics" to generics.decl,
            "operation" to operationSymbol,
            )
            if (enableNewSmithyRuntime) {
                rust("config_override: std::option::Option<crate::config::Builder>,")
            }
        }

        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<crate::client::Handle${generics.inst}>) -> 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<crate::client::Handle${generics.inst}>) -> 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,17 +369,60 @@ 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"),
                )

                rustTemplate(
                    """
                    /// 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<crate::config::Builder>,
                    ) -> 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,
                    ///
                    /// 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<crate::config::Builder>,
                    ) -> &mut Self {
                        self.config_override = config_override;
                        self
                    }
                    """,
                )
            }
            PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier)?.also { paginatorType ->
            PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier)
                ?.also { paginatorType ->
                    rustTemplate(
                        """
                        /// Create a paginator for this request
+23 −1
Original line number Diff line number Diff line
@@ -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<ConfigCustomizatio
        }

        writer.docs("Builder for creating a `Config`.")
        writer.raw("#[derive(Default)]")
        writer.raw("#[derive(Clone, Debug, Default)]")
        writer.rustBlock("pub struct Builder") {
            customizations.forEach {
                it.section(ServiceConfig.BuilderStruct)(this)
@@ -270,4 +272,24 @@ class ServiceConfigGenerator(private val customizations: List<ConfigCustomizatio
            it.section(ServiceConfig.Extras)(writer)
        }
    }

    fun renderRuntimePluginImplForBuilder(writer: RustWriter, codegenContext: CodegenContext) {
        val runtimeApi = RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig)
        writer.rustBlockTemplate(
            "impl #{RuntimePlugin} for Builder",
            "RuntimePlugin" to runtimeApi.resolve("client::runtime_plugin::RuntimePlugin"),
        ) {
            rustTemplate(
                """
                fn configure(&self, _cfg: &mut #{ConfigBag}) -> 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"),
            )
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -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<fastrand::Rng>),