Skip to content

Commit

Permalink
Add skeleton for supporting operation level config (#2589)
Browse files Browse the repository at this point in the history
## Motivation and Context
This PR adds skeleton code for supporting operation level config behind
`enableNewSmithyRuntime`.

## Description
The approach is described in
#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: Yuki Saito <awsaito@amazon.com>
Co-authored-by: John DiSanti <jdisanti@amazon.com>
  • Loading branch information
3 people authored Apr 19, 2023
1 parent 2e6b634 commit 165253c
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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)
Expand Down Expand Up @@ -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<crate::client::Handle${generics.inst}>,
inner: #{Inner}
inner: #{Inner},
""",
"Inner" to symbolProvider.symbolForBuilder(input),
"generics" to generics.decl,
)
if (enableNewSmithyRuntime) {
rust("config_override: std::option::Option<crate::config::Builder>,")
}
""",
"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}",
Expand All @@ -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<
Expand Down Expand Up @@ -316,7 +336,7 @@ class FluentClientGenerator(
generics.toRustGenerics(),
),
)
if (codegenContext.settings.codegenConfig.enableNewSmithyRuntime) {
if (enableNewSmithyRuntime) {
rustTemplate(
"""
// TODO(enableNewSmithyRuntime): Replace `send` with `send_v2`
Expand All @@ -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)
Expand All @@ -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<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,
///
/// 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<crate::config::Builder>,
) -> &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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 changes: 2 additions & 0 deletions rust-runtime/inlineable/src/idempotency_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
Expand Down

0 comments on commit 165253c

Please sign in to comment.