Make service config just contain a `FrozenLayer` in the orchestrator mode (#2762)

## Motivation and Context
Service config structs now only contain a
`aws_smithy_types::config_bag::FrozenLayer` in the orchestrator mode (no
changes for the middleware mode).

## Description
This PR reduces the individual fields of service configs to contain just
`FrozenLayer`. This makes service configs work more seamlessly with
runtime plugins in the orchestrator mode.

Note that service config _builder_ s still contain individual fields.
We're planning to make them just contain
`aws_smithy_types::config_bag::Layer`. To do that, though, we need to
make `Layer` cloneable and that will be handled in a separate PR. For
builders, the only change you will in the PR is that their `build`
method will put fields into a `Layer`, freeze it, and pass it to service
configs.

This PR is marked as a breaking change because it's based on [another
PR](https://github.com/awslabs/smithy-rs/pull/2728) that's also breaking
change.

## Testing
- [x] Passed tests in CI

----

_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: Zelda Hessler <zhessler@amazon.com>
This commit is contained in:
ysaito1001 2023-06-15 18:46:24 -05:00 committed by GitHub
parent b2bdcba57a
commit 2e472d068e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1208 additions and 458 deletions

View File

@ -1,3 +1,6 @@
allowed_external_types = [
"aws_smithy_async::rt::sleep::SharedAsyncSleep",
"aws_smithy_types::config_bag::storable::Storable",
"aws_smithy_types::config_bag::storable::StoreReplace",
"aws_smithy_types::config_bag::storable::Storer",
]

View File

@ -14,6 +14,7 @@ pub use lazy_caching::Builder as LazyBuilder;
use no_caching::NoCredentialsCache;
use crate::provider::{future, SharedCredentialsProvider};
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::sync::Arc;
/// Asynchronous Cached Credentials Provider
@ -62,6 +63,10 @@ impl ProvideCachedCredentials for SharedCredentialsCache {
}
}
impl Storable for SharedCredentialsCache {
type Storer = StoreReplace<SharedCredentialsCache>;
}
#[derive(Clone, Debug)]
pub(crate) enum Inner {
Lazy(lazy_caching::Builder),
@ -122,3 +127,7 @@ impl CredentialsCache {
}
}
}
impl Storable for CredentialsCache {
type Storer = StoreReplace<CredentialsCache>;
}

View File

@ -72,6 +72,7 @@ construct credentials from hardcoded values.
//! ```
use crate::Credentials;
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::sync::Arc;
/// Credentials provider errors
@ -350,3 +351,7 @@ impl ProvideCredentials for SharedCredentialsProvider {
self.0.provide_credentials()
}
}
impl Storable for SharedCredentialsProvider {
type Storer = StoreReplace<SharedCredentialsProvider>;
}

View File

@ -2,13 +2,16 @@ allowed_external_types = [
"aws_credential_types::cache::CredentialsCache",
"aws_credential_types::provider::SharedCredentialsProvider",
"aws_smithy_async::rt::sleep::SharedAsyncSleep",
"aws_smithy_async::time::TimeSource",
"aws_smithy_async::time::SharedTimeSource",
"aws_smithy_async::time::TimeSource",
"aws_smithy_client::http_connector",
"aws_smithy_client::http_connector::HttpConnector",
"aws_smithy_http::endpoint::Endpoint",
"aws_smithy_http::endpoint::EndpointPrefix",
"aws_smithy_http::endpoint::error::InvalidEndpointError",
"aws_smithy_types::config_bag::storable::Storable",
"aws_smithy_types::config_bag::storable::StoreReplace",
"aws_smithy_types::config_bag::storable::Storer",
"aws_smithy_types::retry::RetryConfig",
"aws_smithy_types::timeout::TimeoutConfig",
"http::uri::Uri",

View File

@ -5,6 +5,7 @@
//! New-type for a configurable app name.
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
@ -38,6 +39,10 @@ impl fmt::Display for AppName {
}
}
impl Storable for AppName {
type Storer = StoreReplace<AppName>;
}
impl AppName {
/// Creates a new app name.
///

View File

@ -0,0 +1,31 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Newtypes for endpoint-related parameters
//!
//! Parameters require newtypes so they have distinct types when stored in layers in config bag.
use aws_smithy_types::config_bag::{Storable, StoreReplace};
/// Newtype for `use_fips`
#[derive(Clone, Debug)]
pub struct UseFips(pub bool);
impl Storable for UseFips {
type Storer = StoreReplace<UseFips>;
}
/// Newtype for `use_dual_stack`
#[derive(Clone, Debug)]
pub struct UseDualStack(pub bool);
impl Storable for UseDualStack {
type Storer = StoreReplace<UseDualStack>;
}
/// Newtype for `endpoint_url`
#[derive(Clone, Debug)]
pub struct EndpointUrl(pub String);
impl Storable for EndpointUrl {
type Storer = StoreReplace<EndpointUrl>;
}

View File

@ -16,6 +16,7 @@
pub mod app_name;
pub mod build_metadata;
pub mod endpoint_config;
#[doc(hidden)]
pub mod os_shim_internal;
pub mod region;

View File

@ -5,6 +5,7 @@
//! Region type for determining the endpoint to send requests to.
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
@ -35,6 +36,10 @@ impl Display for Region {
}
}
impl Storable for Region {
type Storer = StoreReplace<Region>;
}
impl Region {
/// Creates a new `Region` from the given string.
pub fn new(region: impl Into<Cow<'static, str>>) -> Self {

View File

@ -27,7 +27,7 @@ class CredentialsCacheDecorator : ClientCodegenDecorator {
codegenContext: ClientCodegenContext,
baseCustomizations: List<ConfigCustomization>,
): List<ConfigCustomization> {
return baseCustomizations + CredentialCacheConfig(codegenContext.runtimeConfig)
return baseCustomizations + CredentialCacheConfig(codegenContext)
}
override fun operationCustomizations(
@ -49,44 +49,65 @@ class CredentialsCacheDecorator : ClientCodegenDecorator {
/**
* Add a `.credentials_cache` field and builder to the `Config` for a given service
*/
class CredentialCacheConfig(runtimeConfig: RuntimeConfig) : ConfigCustomization() {
class CredentialCacheConfig(codegenContext: ClientCodegenContext) : ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val codegenScope = arrayOf(
"cache" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("cache"),
"provider" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("provider"),
"CredentialsCache" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("cache::CredentialsCache"),
"DefaultProvider" to defaultProvider(),
"SharedCredentialsCache" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("cache::SharedCredentialsCache"),
"SharedCredentialsProvider" to AwsRuntimeType.awsCredentialTypes(runtimeConfig).resolve("provider::SharedCredentialsProvider"),
)
override fun section(section: ServiceConfig) = writable {
when (section) {
ServiceConfig.ConfigStruct -> rustTemplate(
"""pub(crate) credentials_cache: #{cache}::SharedCredentialsCache,""",
*codegenScope,
)
ServiceConfig.ConfigImpl -> rustTemplate(
"""
/// Returns the credentials cache.
pub fn credentials_cache(&self) -> #{cache}::SharedCredentialsCache {
self.credentials_cache.clone()
ServiceConfig.ConfigStruct -> {
if (runtimeMode.defaultToMiddleware) {
rustTemplate(
"""pub(crate) credentials_cache: #{SharedCredentialsCache},""",
*codegenScope,
)
}
""",
*codegenScope,
)
}
ServiceConfig.ConfigImpl -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns the credentials cache.
pub fn credentials_cache(&self) -> #{SharedCredentialsCache} {
self.inner.load::<#{SharedCredentialsCache}>().expect("credentials cache should be set").clone()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns the credentials cache.
pub fn credentials_cache(&self) -> #{SharedCredentialsCache} {
self.credentials_cache.clone()
}
""",
*codegenScope,
)
}
}
ServiceConfig.BuilderStruct ->
rustTemplate("credentials_cache: Option<#{cache}::CredentialsCache>,", *codegenScope)
rustTemplate("credentials_cache: Option<#{CredentialsCache}>,", *codegenScope)
ServiceConfig.BuilderImpl -> {
rustTemplate(
"""
/// Sets the credentials cache for this service
pub fn credentials_cache(mut self, credentials_cache: #{cache}::CredentialsCache) -> Self {
pub fn credentials_cache(mut self, credentials_cache: #{CredentialsCache}) -> Self {
self.set_credentials_cache(Some(credentials_cache));
self
}
/// Sets the credentials cache for this service
pub fn set_credentials_cache(&mut self, credentials_cache: Option<#{cache}::CredentialsCache>) -> &mut Self {
pub fn set_credentials_cache(&mut self, credentials_cache: Option<#{CredentialsCache}>) -> &mut Self {
self.credentials_cache = credentials_cache;
self
}
@ -95,29 +116,56 @@ class CredentialCacheConfig(runtimeConfig: RuntimeConfig) : ConfigCustomization(
)
}
ServiceConfig.BuilderBuild -> rustTemplate(
"""
credentials_cache: self
.credentials_cache
.unwrap_or_else({
let sleep = self.sleep_impl.clone();
|| match sleep {
Some(sleep) => {
#{cache}::CredentialsCache::lazy_builder()
.sleep(sleep)
.into_credentials_cache()
}
None => #{cache}::CredentialsCache::lazy(),
}
})
.create_cache(
self.credentials_provider.unwrap_or_else(|| {
#{provider}::SharedCredentialsProvider::new(#{DefaultProvider})
})
),
""",
*codegenScope,
)
ServiceConfig.BuilderBuild -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
layer.store_put(
self.credentials_cache
.unwrap_or_else({
let sleep = self.sleep_impl.clone();
|| match sleep {
Some(sleep) => {
#{CredentialsCache}::lazy_builder()
.sleep(sleep)
.into_credentials_cache()
}
None => #{CredentialsCache}::lazy(),
}
})
.create_cache(self.credentials_provider.unwrap_or_else(|| {
#{SharedCredentialsProvider}::new(#{DefaultProvider})
})),
);
""",
*codegenScope,
)
} else {
rustTemplate(
"""
credentials_cache: self
.credentials_cache
.unwrap_or_else({
let sleep = self.sleep_impl.clone();
|| match sleep {
Some(sleep) => {
#{CredentialsCache}::lazy_builder()
.sleep(sleep)
.into_credentials_cache()
}
None => #{CredentialsCache}::lazy(),
}
})
.create_cache(
self.credentials_provider.unwrap_or_else(|| {
#{SharedCredentialsProvider}::new(#{DefaultProvider})
})
),
""",
*codegenScope,
)
}
}
else -> emptySection
}

View File

@ -22,18 +22,21 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointRulese
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigParam
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.configParamNewtype
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.standardConfigParam
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
import software.amazon.smithy.rust.codegen.core.util.PANIC
import software.amazon.smithy.rust.codegen.core.util.dq
import software.amazon.smithy.rust.codegen.core.util.extendIf
import software.amazon.smithy.rust.codegen.core.util.orNull
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import java.util.Optional
/** load a builtIn parameter from a ruleset by name */
@ -48,14 +51,27 @@ fun ClientCodegenContext.getBuiltIn(builtIn: String): Parameter? {
return rules.getBuiltIn(builtIn)
}
private fun toConfigParam(parameter: Parameter): ConfigParam = ConfigParam(
parameter.name.rustName(),
when (parameter.type!!) {
ParameterType.STRING -> RuntimeType.String.toSymbol()
ParameterType.BOOLEAN -> RuntimeType.Bool.toSymbol()
},
parameter.documentation.orNull()?.let { writable { docs(it) } },
)
private fun promotedBuiltins(parameter: Parameter) =
parameter == Builtins.FIPS || parameter == Builtins.DUALSTACK || parameter == Builtins.SDK_ENDPOINT
private fun ConfigParam.Builder.toConfigParam(parameter: Parameter, runtimeConfig: RuntimeConfig): ConfigParam =
this.name(this.name ?: parameter.name.rustName())
.type(
when (parameter.type!!) {
ParameterType.STRING -> RuntimeType.String.toSymbol()
ParameterType.BOOLEAN -> RuntimeType.Bool.toSymbol()
},
)
.newtype(
when (promotedBuiltins(parameter)) {
true -> AwsRuntimeType.awsTypes(runtimeConfig)
.resolve("endpoint_config::${this.name!!.toPascalCase()}")
false -> configParamNewtype(this.name!!.toPascalCase(), this.type!!, runtimeConfig)
},
)
.setterDocs(this.setterDocs ?: parameter.documentation.orNull()?.let { writable { docs(it) } })
.build()
fun Model.loadBuiltIn(serviceId: ShapeId, builtInSrc: Parameter): Parameter? {
val model = this
@ -82,14 +98,14 @@ fun Model.sdkConfigSetter(
}
/**
* Create a client codegen decorator that creates bindings for a builtIn parameter. Optionally, you can provide [clientParam]
* which allows control over the config parameter that will be generated.
* Create a client codegen decorator that creates bindings for a builtIn parameter. Optionally, you can provide
* [clientParam.Builder] which allows control over the config parameter that will be generated.
*/
fun decoratorForBuiltIn(
builtIn: Parameter,
clientParam: ConfigParam? = null,
clientParamBuilder: ConfigParam.Builder? = null,
): ClientCodegenDecorator {
val nameOverride = clientParam?.name
val nameOverride = clientParamBuilder?.name
val name = nameOverride ?: builtIn.name.rustName()
return object : ClientCodegenDecorator {
override val name: String = "Auto${builtIn.builtIn.get()}"
@ -100,7 +116,7 @@ fun decoratorForBuiltIn(
override fun extraSections(codegenContext: ClientCodegenContext): List<AdHocCustomization> {
return listOfNotNull(
codegenContext.model.sdkConfigSetter(codegenContext.serviceShape.id, builtIn, clientParam?.name),
codegenContext.model.sdkConfigSetter(codegenContext.serviceShape.id, builtIn, clientParamBuilder?.name),
)
}
@ -110,7 +126,9 @@ fun decoratorForBuiltIn(
): List<ConfigCustomization> {
return baseCustomizations.extendIf(rulesetContainsBuiltIn(codegenContext)) {
standardConfigParam(
clientParam ?: toConfigParam(builtIn),
clientParamBuilder?.toConfigParam(builtIn, codegenContext.runtimeConfig) ?: ConfigParam.Builder()
.toConfigParam(builtIn, codegenContext.runtimeConfig),
codegenContext,
)
}
}
@ -120,11 +138,16 @@ fun decoratorForBuiltIn(
override fun loadBuiltInFromServiceConfig(parameter: Parameter, configRef: String): Writable? =
when (parameter.builtIn) {
builtIn.builtIn -> writable {
rust("$configRef.$name")
if (codegenContext.smithyRuntimeMode.defaultToOrchestrator) {
rust("$configRef.$name()")
} else {
rust("$configRef.$name")
}
if (parameter.type == ParameterType.STRING) {
rust(".clone()")
}
}
else -> null
}
@ -173,6 +196,6 @@ val PromotedBuiltInsDecorators =
decoratorForBuiltIn(Builtins.DUALSTACK),
decoratorForBuiltIn(
Builtins.SDK_ENDPOINT,
ConfigParam("endpoint_url", RuntimeType.String.toSymbol(), endpointUrlDocs),
ConfigParam.Builder().name("endpoint_url").type(RuntimeType.String.toSymbol()).setterDocs(endpointUrlDocs),
),
).toTypedArray()

View File

@ -13,7 +13,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.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.util.letIf
@ -32,9 +31,10 @@ class HttpConnectorDecorator : ClientCodegenDecorator {
}
class HttpConnectorConfigCustomization(
codegenContext: CodegenContext,
codegenContext: ClientCodegenContext,
) : ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val moduleUseName = codegenContext.moduleUseName()
private val codegenScope = arrayOf(
"HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"),
@ -43,18 +43,32 @@ class HttpConnectorConfigCustomization(
override fun section(section: ServiceConfig): Writable {
return when (section) {
is ServiceConfig.ConfigStruct -> writable {
rustTemplate("http_connector: Option<#{HttpConnector}>,", *codegenScope)
if (runtimeMode.defaultToMiddleware) {
rustTemplate("http_connector: Option<#{HttpConnector}>,", *codegenScope)
}
}
is ServiceConfig.ConfigImpl -> writable {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.http_connector.as_ref()
}
""",
*codegenScope,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.inner.load::<#{HttpConnector}>()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.http_connector.as_ref()
}
""",
*codegenScope,
)
}
}
is ServiceConfig.BuilderStruct -> writable {
rustTemplate("http_connector: Option<#{HttpConnector}>,", *codegenScope)
@ -145,7 +159,11 @@ class HttpConnectorConfigCustomization(
)
}
is ServiceConfig.BuilderBuild -> writable {
rust("http_connector: self.http_connector,")
if (runtimeMode.defaultToOrchestrator) {
rust("layer.store_or_unset(self.http_connector);")
} else {
rust("http_connector: self.http_connector,")
}
}
else -> emptySection
}

View File

@ -21,7 +21,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.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.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
@ -131,7 +130,11 @@ class RegionDecorator : ClientCodegenDecorator {
override fun loadBuiltInFromServiceConfig(parameter: Parameter, configRef: String): Writable? {
return when (parameter.builtIn) {
Builtins.REGION.builtIn -> writable {
rust("$configRef.region.as_ref().map(|r|r.as_ref().to_owned())")
if (codegenContext.smithyRuntimeMode.defaultToOrchestrator) {
rust("$configRef.region().as_ref().map(|r|r.as_ref().to_owned())")
} else {
rust("$configRef.region.as_ref().map(|r|r.as_ref().to_owned())")
}
}
else -> null
}
@ -153,22 +156,41 @@ class RegionDecorator : ClientCodegenDecorator {
}
}
class RegionProviderConfig(codegenContext: CodegenContext) : ConfigCustomization() {
class RegionProviderConfig(codegenContext: ClientCodegenContext) : ConfigCustomization() {
private val region = region(codegenContext.runtimeConfig)
private val moduleUseName = codegenContext.moduleUseName()
private val runtimeMode = codegenContext.smithyRuntimeMode
private val codegenScope = arrayOf("Region" to region.resolve("Region"))
override fun section(section: ServiceConfig) = writable {
when (section) {
ServiceConfig.ConfigStruct -> rustTemplate("pub(crate) region: Option<#{Region}>,", *codegenScope)
ServiceConfig.ConfigImpl -> rustTemplate(
"""
/// Returns the AWS region, if it was provided.
pub fn region(&self) -> Option<&#{Region}> {
self.region.as_ref()
ServiceConfig.ConfigStruct -> {
if (runtimeMode.defaultToMiddleware) {
rustTemplate("pub(crate) region: Option<#{Region}>,", *codegenScope)
}
""",
*codegenScope,
)
}
ServiceConfig.ConfigImpl -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns the AWS region, if it was provided.
pub fn region(&self) -> Option<&#{Region}> {
self.inner.load::<#{Region}>()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns the AWS region, if it was provided.
pub fn region(&self) -> Option<&#{Region}> {
self.region.as_ref()
}
""",
*codegenScope,
)
}
}
ServiceConfig.BuilderStruct ->
rustTemplate("pub(crate) region: Option<#{Region}>,", *codegenScope)
@ -201,10 +223,13 @@ class RegionProviderConfig(codegenContext: CodegenContext) : ConfigCustomization
*codegenScope,
)
ServiceConfig.BuilderBuild -> rustTemplate(
"""region: self.region,""",
*codegenScope,
)
ServiceConfig.BuilderBuild -> {
if (runtimeMode.defaultToOrchestrator) {
rust("layer.store_or_unset(self.region);")
} else {
rust("region: self.region,")
}
}
else -> emptySection
}

View File

@ -20,7 +20,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customizations.CrateVersionCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
@ -40,7 +40,7 @@ class UserAgentDecorator : ClientCodegenDecorator {
codegenContext: ClientCodegenContext,
baseCustomizations: List<ConfigCustomization>,
): List<ConfigCustomization> {
return baseCustomizations + AppNameCustomization(codegenContext.runtimeConfig)
return baseCustomizations + AppNameCustomization(codegenContext)
}
override fun operationCustomizations(
@ -149,15 +149,18 @@ class UserAgentDecorator : ClientCodegenDecorator {
}
}
private class AppNameCustomization(runtimeConfig: RuntimeConfig) : ConfigCustomization() {
private class AppNameCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val codegenScope = arrayOf(
*preludeScope,
"AppName" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("app_name::AppName"),
)
override fun section(section: ServiceConfig): Writable =
when (section) {
is ServiceConfig.BuilderStruct -> writable {
rustTemplate("app_name: Option<#{AppName}>,", *codegenScope)
rustTemplate("app_name: #{Option}<#{AppName}>,", *codegenScope)
}
is ServiceConfig.BuilderImpl -> writable {
@ -176,7 +179,7 @@ class UserAgentDecorator : ClientCodegenDecorator {
///
/// This _optional_ name is used to identify the application in the user agent that
/// gets sent along with requests.
pub fn set_app_name(&mut self, app_name: Option<#{AppName}>) -> &mut Self {
pub fn set_app_name(&mut self, app_name: #{Option}<#{AppName}>) -> &mut Self {
self.app_name = app_name;
self
}
@ -186,26 +189,47 @@ class UserAgentDecorator : ClientCodegenDecorator {
}
is ServiceConfig.BuilderBuild -> writable {
rust("app_name: self.app_name,")
if (runtimeMode.defaultToOrchestrator) {
rust("layer.store_or_unset(self.app_name);")
} else {
rust("app_name: self.app_name,")
}
}
is ServiceConfig.ConfigStruct -> writable {
rustTemplate("app_name: Option<#{AppName}>,", *codegenScope)
if (runtimeMode.defaultToMiddleware) {
rustTemplate("app_name: #{Option}<#{AppName}>,", *codegenScope)
}
}
is ServiceConfig.ConfigImpl -> writable {
rustTemplate(
"""
/// Returns the name of the app that is using the client, if it was provided.
///
/// This _optional_ name is used to identify the application in the user agent that
/// gets sent along with requests.
pub fn app_name(&self) -> Option<&#{AppName}> {
self.app_name.as_ref()
}
""",
*codegenScope,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns the name of the app that is using the client, if it was provided.
///
/// This _optional_ name is used to identify the application in the user agent that
/// gets sent along with requests.
pub fn app_name(&self) -> #{Option}<&#{AppName}> {
self.inner.load::<#{AppName}>()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns the name of the app that is using the client, if it was provided.
///
/// This _optional_ name is used to identify the application in the user agent that
/// gets sent along with requests.
pub fn app_name(&self) -> #{Option}<&#{AppName}> {
self.app_name.as_ref()
}
""",
*codegenScope,
)
}
}
else -> emptySection

View File

@ -52,6 +52,7 @@ class TimestreamDecorator : ClientCodegenDecorator {
Visibility.PUBLIC,
CargoDependency.Tokio.copy(scope = DependencyScope.Compile, features = setOf("sync")),
)
val runtimeMode = codegenContext.smithyRuntimeMode
rustCrate.lib {
// helper function to resolve an endpoint given a base client
rustTemplate(
@ -62,7 +63,7 @@ class TimestreamDecorator : ClientCodegenDecorator {
#{ResolveEndpointError}::from_source("failed to call describe_endpoints", e)
})?;
let endpoint = describe_endpoints.endpoints().unwrap().get(0).unwrap();
let expiry = client.conf().time_source.now() + #{Duration}::from_secs(endpoint.cache_period_in_minutes() as u64 * 60);
let expiry = client.conf().time_source().now() + #{Duration}::from_secs(endpoint.cache_period_in_minutes() as u64 * 60);
Ok((
#{Endpoint}::builder()
.url(format!("https://{}", endpoint.address().unwrap()))
@ -78,7 +79,7 @@ class TimestreamDecorator : ClientCodegenDecorator {
pub async fn enable_endpoint_discovery(self) -> #{Result}<(Self, #{endpoint_discovery}::ReloadEndpoint), #{ResolveEndpointError}> {
let mut new_conf = self.conf().clone();
let sleep = self.conf().sleep_impl().expect("sleep impl must be provided");
let time = self.conf().time_source.clone();
let time = self.conf().time_source();
let (resolver, reloader) = #{endpoint_discovery}::create_cache(
move || {
let client = self.clone();
@ -92,7 +93,6 @@ class TimestreamDecorator : ClientCodegenDecorator {
Ok((Self::from_conf(new_conf), reloader))
}
}
""",
"endpoint_discovery" to endpointDiscovery.toType(),
"SystemTime" to RuntimeType.std.resolve("time::SystemTime"),

View File

@ -5,12 +5,18 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
internal class CredentialProviderConfigTest {
@Test
fun `generates a valid config`() {
validateConfigCustomizations(CredentialProviderConfig(AwsTestRuntimeConfig))
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generates a valid config`(smithyRuntimeModeStr: String) {
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val codegenContext = awsTestCodegenContext().withSmithyRuntimeMode(smithyRuntimeMode)
validateConfigCustomizations(codegenContext, CredentialProviderConfig(codegenContext.runtimeConfig))
}
}

View File

@ -5,15 +5,20 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
class HttpConnectorConfigCustomizationTest {
@Test
fun `generates a valid config`() {
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generates a valid config`(smithyRuntimeModeStr: String) {
val project = TestWorkspace.testProject()
val codegenContext = awsTestCodegenContext()
validateConfigCustomizations(HttpConnectorConfigCustomization(codegenContext), project)
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val codegenContext = awsTestCodegenContext().withSmithyRuntimeMode(smithyRuntimeMode)
validateConfigCustomizations(codegenContext, HttpConnectorConfigCustomization(codegenContext), project)
}
}

View File

@ -5,22 +5,27 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.rustSettings
internal class RegionProviderConfigTest {
@Test
fun `generates a valid config`() {
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generates a valid config`(smithyRuntimeModeStr: String) {
val project = TestWorkspace.testProject()
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val codegenContext = awsTestCodegenContext(
settings = testClientRustSettings(
moduleName = project.rustSettings().moduleName,
runtimeConfig = AwsTestRuntimeConfig,
),
)
validateConfigCustomizations(RegionProviderConfig(codegenContext), project)
).withSmithyRuntimeMode(smithyRuntimeMode)
validateConfigCustomizations(codegenContext, RegionProviderConfig(codegenContext), project)
}
}

View File

@ -5,19 +5,31 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.aws.traits.auth.SigV4Trait
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.stubConfigProject
import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
internal class SigV4SigningDecoratorTest {
@Test
fun `generates a valid config`() {
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generates a valid config`(smithyRuntimeModeStr: String) {
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val codegenContext = awsTestCodegenContext(
settings = testClientRustSettings(
runtimeConfig = AwsTestRuntimeConfig,
),
).withSmithyRuntimeMode(smithyRuntimeMode)
val project = stubConfigProject(
codegenContext,
SigV4SigningConfig(
AwsTestRuntimeConfig,
codegenContext.runtimeConfig,
true,
SigV4Trait.builder().name("test-service").build(),
),

View File

@ -25,6 +25,7 @@ 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.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.util.letIf
@ -46,7 +47,7 @@ class ApiKeyAuthDecorator : ClientCodegenDecorator {
baseCustomizations: List<ConfigCustomization>,
): List<ConfigCustomization> {
return baseCustomizations.letIf(applies(codegenContext)) { customizations ->
customizations + ApiKeyConfigCustomization(codegenContext.runtimeConfig)
customizations + ApiKeyConfigCustomization(codegenContext)
}
}
@ -156,15 +157,18 @@ private class ApiKeyOperationCustomization(private val runtimeConfig: RuntimeCon
}
}
private class ApiKeyConfigCustomization(runtimeConfig: RuntimeConfig) : ConfigCustomization() {
private class ApiKeyConfigCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() {
val runtimeMode = codegenContext.smithyRuntimeMode
val runtimeConfig = codegenContext.runtimeConfig
private val codegenScope = arrayOf(
*preludeScope,
"ApiKey" to apiKey(runtimeConfig),
)
override fun section(section: ServiceConfig): Writable =
when (section) {
is ServiceConfig.BuilderStruct -> writable {
rustTemplate("api_key: Option<#{ApiKey}>,", *codegenScope)
rustTemplate("api_key: #{Option}<#{ApiKey}>,", *codegenScope)
}
is ServiceConfig.BuilderImpl -> writable {
rustTemplate(
@ -176,7 +180,7 @@ private class ApiKeyConfigCustomization(runtimeConfig: RuntimeConfig) : ConfigCu
}
/// Sets the API key that will be used by the client.
pub fn set_api_key(&mut self, api_key: Option<#{ApiKey}>) -> &mut Self {
pub fn set_api_key(&mut self, api_key: #{Option}<#{ApiKey}>) -> &mut Self {
self.api_key = api_key;
self
}
@ -185,21 +189,39 @@ private class ApiKeyConfigCustomization(runtimeConfig: RuntimeConfig) : ConfigCu
)
}
is ServiceConfig.BuilderBuild -> writable {
rust("api_key: self.api_key,")
if (runtimeMode.defaultToOrchestrator) {
rust("layer.store_or_unset(self.api_key);")
} else {
rust("api_key: self.api_key,")
}
}
is ServiceConfig.ConfigStruct -> writable {
rustTemplate("api_key: Option<#{ApiKey}>,", *codegenScope)
if (runtimeMode.defaultToMiddleware) {
rustTemplate("api_key: #{Option}<#{ApiKey}>,", *codegenScope)
}
}
is ServiceConfig.ConfigImpl -> writable {
rustTemplate(
"""
/// Returns API key used by the client, if it was provided.
pub fn api_key(&self) -> Option<&#{ApiKey}> {
self.api_key.as_ref()
}
""",
*codegenScope,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns API key used by the client, if it was provided.
pub fn api_key(&self) -> #{Option}<&#{ApiKey}> {
self.inner.load::<#{ApiKey}>()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns API key used by the client, if it was provided.
pub fn api_key(&self) -> #{Option}<&#{ApiKey}> {
self.api_key.as_ref()
}
""",
*codegenScope,
)
}
}
else -> emptySection
}

View File

@ -237,6 +237,7 @@ private class HttpAuthConfigCustomization(
private val authSchemes: HttpAuthSchemes,
) : ConfigCustomization() {
private val codegenScope = codegenScope(codegenContext.runtimeConfig)
private val runtimeMode = codegenContext.smithyRuntimeMode
override fun section(section: ServiceConfig): Writable = writable {
when (section) {
@ -324,7 +325,9 @@ private class HttpAuthConfigCustomization(
}
is ServiceConfig.BuilderBuild -> {
rust("identity_resolvers: self.identity_resolvers,")
if (runtimeMode.defaultToMiddleware) {
rust("identity_resolvers: self.identity_resolvers,")
}
}
is ServiceConfig.ConfigStruct -> {
@ -343,6 +346,10 @@ private class HttpAuthConfigCustomization(
)
}
is ServiceConfig.BuilderBuildExtras -> {
rust("identity_resolvers: self.identity_resolvers,")
}
else -> {}
}
}

View File

@ -13,7 +13,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.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.util.letIf
@ -31,9 +30,10 @@ class HttpConnectorConfigDecorator : ClientCodegenDecorator {
}
private class HttpConnectorConfigCustomization(
codegenContext: CodegenContext,
codegenContext: ClientCodegenContext,
) : ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val moduleUseName = codegenContext.moduleUseName()
private val codegenScope = arrayOf(
"HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"),
@ -42,19 +42,33 @@ private class HttpConnectorConfigCustomization(
override fun section(section: ServiceConfig): Writable {
return when (section) {
is ServiceConfig.ConfigStruct -> writable {
rustTemplate("http_connector: Option<#{HttpConnector}>,", *codegenScope)
if (runtimeMode.defaultToMiddleware) {
rustTemplate("http_connector: Option<#{HttpConnector}>,", *codegenScope)
}
}
is ServiceConfig.ConfigImpl -> writable {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.http_connector.as_ref()
}
""",
*codegenScope,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.inner.load::<#{HttpConnector}>()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.http_connector.as_ref()
}
""",
*codegenScope,
)
}
}
is ServiceConfig.BuilderStruct -> writable {
@ -147,7 +161,11 @@ private class HttpConnectorConfigCustomization(
}
is ServiceConfig.BuilderBuild -> writable {
rust("http_connector: self.http_connector,")
if (runtimeMode.defaultToOrchestrator) {
rust("self.http_connector.map(|c| layer.store_put(c));")
} else {
rust("http_connector: self.http_connector,")
}
}
else -> emptySection

View File

@ -26,17 +26,13 @@ class InterceptorConfigCustomization(codegenContext: CodegenContext) : ConfigCus
writable {
when (section) {
ServiceConfig.ConfigStruct -> rustTemplate(
"""
pub(crate) interceptors: Vec<#{SharedInterceptor}>,
""",
"pub(crate) interceptors: Vec<#{SharedInterceptor}>,",
*codegenScope,
)
ServiceConfig.BuilderStruct ->
rustTemplate(
"""
interceptors: Vec<#{SharedInterceptor}>,
""",
"interceptors: Vec<#{SharedInterceptor}>,",
*codegenScope,
)
@ -171,18 +167,14 @@ class InterceptorConfigCustomization(codegenContext: CodegenContext) : ConfigCus
*codegenScope,
)
ServiceConfig.BuilderBuild -> rust(
"""
interceptors: self.interceptors,
""",
)
is ServiceConfig.RuntimePluginInterceptors -> rust(
"""
${section.interceptors}.extend(self.interceptors.iter().cloned());
""",
)
is ServiceConfig.BuilderBuildExtras -> rust("interceptors: self.interceptors,")
else -> emptySection
}
}

View File

@ -5,6 +5,7 @@
package software.amazon.smithy.rust.codegen.client.smithy.customizations
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection
@ -14,13 +15,13 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.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.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCustomization() {
class ResiliencyConfigCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val retryConfig = RuntimeType.smithyTypes(runtimeConfig).resolve("retry")
private val sleepModule = RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep")
private val timeoutModule = RuntimeType.smithyTypes(runtimeConfig).resolve("timeout")
@ -35,38 +36,64 @@ class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCust
override fun section(section: ServiceConfig) =
writable {
when (section) {
is ServiceConfig.ConfigStruct -> rustTemplate(
"""
retry_config: Option<#{RetryConfig}>,
sleep_impl: Option<#{SharedAsyncSleep}>,
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 shared async sleep implementation from this config, if any.
pub fn sleep_impl(&self) -> Option<#{SharedAsyncSleep}> {
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.ConfigStruct -> {
if (runtimeMode.defaultToMiddleware) {
rustTemplate(
"""
retry_config: Option<#{RetryConfig}>,
sleep_impl: Option<#{SharedAsyncSleep}>,
timeout_config: Option<#{TimeoutConfig}>,
""",
*codegenScope,
)
}
}
is ServiceConfig.BuilderStruct ->
is ServiceConfig.ConfigImpl -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Return a reference to the retry configuration contained in this config, if any.
pub fn retry_config(&self) -> Option<&#{RetryConfig}> {
self.inner.load::<#{RetryConfig}>()
}
/// Return a cloned shared async sleep implementation from this config, if any.
pub fn sleep_impl(&self) -> Option<#{SharedAsyncSleep}> {
self.inner.load::<#{SharedAsyncSleep}>().cloned()
}
/// Return a reference to the timeout configuration contained in this config, if any.
pub fn timeout_config(&self) -> Option<&#{TimeoutConfig}> {
self.inner.load::<#{TimeoutConfig}>()
}
""",
*codegenScope,
)
} else {
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 shared async sleep implementation from this config, if any.
pub fn sleep_impl(&self) -> Option<#{SharedAsyncSleep}> {
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}>,
@ -75,6 +102,7 @@ class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCust
""",
*codegenScope,
)
}
ServiceConfig.BuilderImpl ->
rustTemplate(
@ -216,21 +244,30 @@ class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCust
*codegenScope,
)
ServiceConfig.BuilderBuild -> rustTemplate(
// We call clone on sleep_impl because the field is used by
// initializing the credentials_cache field later in the build
// method of a Config builder.
// We could rearrange the order of decorators so that AwsCodegenDecorator
// runs before RequiredCustomizations, which in turns renders
// CredentialsCacheDecorator before this class, but that is a bigger
// change than adding a call to the clone method on sleep_impl.
"""
retry_config: self.retry_config,
sleep_impl: self.sleep_impl.clone(),
timeout_config: self.timeout_config,
""",
*codegenScope,
)
ServiceConfig.BuilderBuild -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
self.retry_config.map(|r| layer.store_put(r));
self.sleep_impl.clone().map(|s| layer.store_put(s));
self.timeout_config.map(|t| layer.store_put(t));
""",
*codegenScope,
)
} else {
rustTemplate(
// We call clone on sleep_impl because the field is used by
// initializing the credentials_cache field later in the build
// method of a Config builder.
"""
retry_config: self.retry_config,
sleep_impl: self.sleep_impl.clone(),
timeout_config: self.timeout_config,
""",
*codegenScope,
)
}
}
else -> emptySection
}
@ -276,7 +313,7 @@ class ResiliencyServiceRuntimePluginCustomization : ServiceRuntimePluginCustomiz
if let Some(timeout_config) = self.handle.conf.timeout_config() {
${section.newLayerName}.put(timeout_config.clone());
}
${section.newLayerName}.put(self.handle.conf.time_source.clone());
${section.newLayerName}.put(self.handle.conf.time_source().clone());
""",
)
}

View File

@ -0,0 +1,127 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.client.smithy.customizations
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() {
private val runtimeMode = codegenContext.smithyRuntimeMode
private val codegenScope = arrayOf(
*preludeScope,
"SharedTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::SharedTimeSource"),
)
override fun section(section: ServiceConfig) =
writable {
when (section) {
is ServiceConfig.ConfigStruct -> {
if (runtimeMode.defaultToMiddleware) {
rustTemplate(
"""
pub(crate) time_source: #{SharedTimeSource},
""",
*codegenScope,
)
}
}
is ServiceConfig.ConfigImpl -> {
rust("/// Return time source used for this service.")
rustBlockTemplate(
"pub fn time_source(&self) -> #{SharedTimeSource}",
*codegenScope,
) {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""self.inner.load::<#{SharedTimeSource}>().expect("time source should be set").clone()""",
*codegenScope,
)
} else {
rust("self.time_source.clone()")
}
}
}
is ServiceConfig.BuilderStruct ->
rustTemplate(
"""
time_source: #{Option}<#{SharedTimeSource}>,
""",
*codegenScope,
)
ServiceConfig.BuilderImpl ->
rustTemplate(
"""
/// Sets the time source used for this service
pub fn time_source(
mut self,
time_source: impl #{Into}<#{SharedTimeSource}>,
) -> Self {
self.time_source = Some(time_source.into());
self
}
/// Sets the time source used for this service
pub fn set_time_source(
&mut self,
time_source: #{Option}<#{SharedTimeSource}>,
) -> &mut Self {
self.time_source = time_source;
self
}
""",
*codegenScope,
)
ServiceConfig.BuilderBuild -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
layer.store_put(self.time_source.unwrap_or_default());
""",
*codegenScope,
)
} else {
rustTemplate(
"""
time_source: self.time_source.unwrap_or_default(),
""",
*codegenScope,
)
}
}
else -> emptySection
}
}
}
class TimeSourceOperationCustomization : OperationCustomization() {
override fun section(section: OperationSection): Writable {
return when (section) {
is OperationSection.MutateRequest -> writable {
rust(
"""
${section.request}.properties_mut().insert(${section.config}.time_source.clone());
""",
)
}
else -> emptySection
}
}
}

View File

@ -16,11 +16,11 @@ import software.amazon.smithy.rust.codegen.client.smithy.customizations.Intercep
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyReExportCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyServiceRuntimePluginCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.TimeSourceCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.TimeSourceOperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.TimeSourceOperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.timeSourceCustomization
import software.amazon.smithy.rust.codegen.core.rustlang.Feature
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customizations.AllowLintsCustomization
@ -59,9 +59,9 @@ class RequiredCustomizations : ClientCodegenDecorator {
if (codegenContext.smithyRuntimeMode.generateOrchestrator) {
baseCustomizations + ResiliencyConfigCustomization(codegenContext) + InterceptorConfigCustomization(
codegenContext,
) + timeSourceCustomization(codegenContext)
) + TimeSourceCustomization(codegenContext)
} else {
baseCustomizations + ResiliencyConfigCustomization(codegenContext) + timeSourceCustomization(codegenContext)
baseCustomizations + ResiliencyConfigCustomization(codegenContext) + TimeSourceCustomization(codegenContext)
}
override fun libRsCustomizations(

View File

@ -11,19 +11,22 @@ import software.amazon.smithy.model.shapes.ShapeType
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.rulesengine.traits.ClientContextParamDefinition
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigParam
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.configParamNewtype
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.standardConfigParam
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.join
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.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.core.util.getTrait
import software.amazon.smithy.rust.codegen.core.util.orNull
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
/**
@ -32,10 +35,11 @@ import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
* This handles injecting parameters like `s3::Accelerate` or `s3::ForcePathStyle`. The resulting parameters become
* setters on the config builder object.
*/
class ClientContextConfigCustomization(ctx: CodegenContext) : ConfigCustomization() {
class ClientContextConfigCustomization(ctx: ClientCodegenContext) : ConfigCustomization() {
private val runtimeConfig = ctx.runtimeConfig
private val configParams = ctx.serviceShape.getTrait<ClientContextParamsTrait>()?.parameters.orEmpty().toList()
.map { (key, value) -> fromClientParam(key, value, ctx.symbolProvider) }
private val decorators = configParams.map { standardConfigParam(it) }
.map { (key, value) -> fromClientParam(key, value, ctx.symbolProvider, runtimeConfig) }
private val decorators = configParams.map { standardConfigParam(it, ctx) }
companion object {
fun toSymbol(shapeType: ShapeType, symbolProvider: RustSymbolProvider): Symbol =
@ -51,10 +55,13 @@ class ClientContextConfigCustomization(ctx: CodegenContext) : ConfigCustomizatio
name: String,
definition: ClientContextParamDefinition,
symbolProvider: RustSymbolProvider,
runtimeConfig: RuntimeConfig,
): ConfigParam {
val inner = toSymbol(definition.type, symbolProvider)
return ConfigParam(
RustReservedWords.escapeIfNeeded(name.toSnakeCase()),
toSymbol(definition.type, symbolProvider),
inner,
configParamNewtype(RustReservedWords.escapeIfNeeded(name.toPascalCase()), inner, runtimeConfig),
definition.documentation.orNull()?.let { writable { docs(it) } },
)
}

View File

@ -25,6 +25,7 @@ internal class EndpointConfigCustomization(
ConfigCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val moduleUseName = codegenContext.moduleUseName()
private val runtimeMode = codegenContext.smithyRuntimeMode
private val types = Types(runtimeConfig)
override fun section(section: ServiceConfig): Writable {
@ -38,21 +39,38 @@ internal class EndpointConfigCustomization(
"Params" to typesGenerator.paramsStruct(),
)
when (section) {
is ServiceConfig.ConfigStruct -> rustTemplate(
"pub (crate) endpoint_resolver: $sharedEndpointResolver,",
*codegenScope,
)
is ServiceConfig.ConfigStruct -> {
if (runtimeMode.defaultToMiddleware) {
rustTemplate(
"pub (crate) endpoint_resolver: $sharedEndpointResolver,",
*codegenScope,
)
}
}
is ServiceConfig.ConfigImpl ->
rustTemplate(
"""
/// Returns the endpoint resolver.
pub fn endpoint_resolver(&self) -> $sharedEndpointResolver {
self.endpoint_resolver.clone()
}
""",
*codegenScope,
)
is ServiceConfig.ConfigImpl -> {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns the endpoint resolver.
pub fn endpoint_resolver(&self) -> $sharedEndpointResolver {
self.inner.load::<$sharedEndpointResolver>().expect("endpoint resolver should be set").clone()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns the endpoint resolver.
pub fn endpoint_resolver(&self) -> $sharedEndpointResolver {
self.endpoint_resolver.clone()
}
""",
*codegenScope,
)
}
}
is ServiceConfig.BuilderStruct ->
rustTemplate(
@ -123,15 +141,27 @@ internal class EndpointConfigCustomization(
ServiceConfig.BuilderBuild -> {
val defaultResolver = typesGenerator.defaultResolver()
if (defaultResolver != null) {
rustTemplate(
"""
endpoint_resolver: self.endpoint_resolver.unwrap_or_else(||
#{SharedEndpointResolver}::new(#{DefaultResolver}::new())
),
""",
*codegenScope,
"DefaultResolver" to defaultResolver,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
layer.store_put(self.endpoint_resolver.unwrap_or_else(||
#{SharedEndpointResolver}::new(#{DefaultResolver}::new())
));
""",
*codegenScope,
"DefaultResolver" to defaultResolver,
)
} else {
rustTemplate(
"""
endpoint_resolver: self.endpoint_resolver.unwrap_or_else(||
#{SharedEndpointResolver}::new(#{DefaultResolver}::new())
),
""",
*codegenScope,
"DefaultResolver" to defaultResolver,
)
}
} else {
val alwaysFailsResolver =
RuntimeType.forInlineFun("MissingResolver", ClientRustModule.Endpoint) {
@ -152,13 +182,23 @@ internal class EndpointConfigCustomization(
}
// To keep this diff under control, rather than `.expect` here, insert a resolver that will
// always fail. In the future, this will be changed to an `expect()`
rustTemplate(
"""
endpoint_resolver: self.endpoint_resolver.unwrap_or_else(||#{SharedEndpointResolver}::new(#{FailingResolver})),
""",
*codegenScope,
"FailingResolver" to alwaysFailsResolver,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
layer.store_put(self.endpoint_resolver.unwrap_or_else(||#{SharedEndpointResolver}::new(#{FailingResolver})));
""",
*codegenScope,
"FailingResolver" to alwaysFailsResolver,
)
} else {
rustTemplate(
"""
endpoint_resolver: self.endpoint_resolver.unwrap_or_else(||#{SharedEndpointResolver}::new(#{FailingResolver})),
""",
*codegenScope,
"FailingResolver" to alwaysFailsResolver,
)
}
}
}

View File

@ -119,9 +119,9 @@ class EndpointParamsInterceptorGenerator(
val paramName = EndpointParamsGenerator.memberName(name)
val setterName = EndpointParamsGenerator.setterName(name)
if (param.type == ShapeType.BOOLEAN) {
rust(".$setterName(_config.$paramName)")
rust(".$setterName(_config.$paramName())")
} else {
rust(".$setterName(_config.$paramName.clone())")
rust(".$setterName(_config.$paramName().clone())")
}
}

View File

@ -170,7 +170,7 @@ class ServiceRuntimePluginGenerator(
}
fn interceptors(&self, interceptors: &mut #{InterceptorRegistrar}) {
interceptors.extend(self.handle.conf.interceptors.iter().cloned());
interceptors.extend(self.handle.conf.interceptors().cloned());
#{additional_interceptors}
}
}

View File

@ -5,62 +5,98 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.config
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
/**
* Add a `make_token` field to Service config. See below for the resulting generated code.
*/
class IdempotencyTokenProviderCustomization : NamedCustomization<ServiceConfig>() {
class IdempotencyTokenProviderCustomization(codegenContext: ClientCodegenContext) : NamedCustomization<ServiceConfig>() {
private val runtimeConfig = codegenContext.runtimeConfig
private val runtimeMode = codegenContext.smithyRuntimeMode
private val codegenScope = arrayOf(
*preludeScope,
"default_provider" to RuntimeType.idempotencyToken(runtimeConfig).resolve("default_provider"),
"IdempotencyTokenProvider" to RuntimeType.idempotencyToken(runtimeConfig).resolve("IdempotencyTokenProvider"),
)
override fun section(section: ServiceConfig): Writable {
return when (section) {
is ServiceConfig.ConfigStruct -> writable {
rust("pub (crate) make_token: #T::IdempotencyTokenProvider,", RuntimeType.IdempotencyToken)
if (runtimeMode.defaultToMiddleware) {
rustTemplate("pub (crate) make_token: #{IdempotencyTokenProvider},", *codegenScope)
}
}
ServiceConfig.ConfigImpl -> writable {
rust(
"""
/// Returns a copy of the idempotency token provider.
/// If a random token provider was configured,
/// a newly-randomized token provider will be returned.
pub fn make_token(&self) -> #T::IdempotencyTokenProvider {
self.make_token.clone()
}
""",
RuntimeType.IdempotencyToken,
)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
/// Returns a copy of the idempotency token provider.
/// If a random token provider was configured,
/// a newly-randomized token provider will be returned.
pub fn make_token(&self) -> #{IdempotencyTokenProvider} {
self.inner.load::<#{IdempotencyTokenProvider}>().expect("the idempotency provider should be set").clone()
}
""",
*codegenScope,
)
} else {
rustTemplate(
"""
/// Returns a copy of the idempotency token provider.
/// If a random token provider was configured,
/// a newly-randomized token provider will be returned.
pub fn make_token(&self) -> #{IdempotencyTokenProvider} {
self.make_token.clone()
}
""",
*codegenScope,
)
}
}
ServiceConfig.BuilderStruct -> writable {
rust("make_token: Option<#T::IdempotencyTokenProvider>,", RuntimeType.IdempotencyToken)
rustTemplate("make_token: #{Option}<#{IdempotencyTokenProvider}>,", *codegenScope)
}
ServiceConfig.BuilderImpl -> writable {
rustTemplate(
"""
/// Sets the idempotency token provider to use for service calls that require tokens.
pub fn make_token(mut self, make_token: impl Into<#{TokenProvider}>) -> Self {
self.set_make_token(Some(make_token.into()));
pub fn make_token(mut self, make_token: impl #{Into}<#{IdempotencyTokenProvider}>) -> Self {
self.set_make_token(#{Some}(make_token.into()));
self
}
/// Sets the idempotency token provider to use for service calls that require tokens.
pub fn set_make_token(&mut self, make_token: Option<#{TokenProvider}>) -> &mut Self {
pub fn set_make_token(&mut self, make_token: #{Option}<#{IdempotencyTokenProvider}>) -> &mut Self {
self.make_token = make_token;
self
}
""",
"TokenProvider" to RuntimeType.IdempotencyToken.resolve("IdempotencyTokenProvider"),
*codegenScope,
)
}
ServiceConfig.BuilderBuild -> writable {
rust("make_token: self.make_token.unwrap_or_else(#T::default_provider),", RuntimeType.IdempotencyToken)
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"layer.store_put(self.make_token.unwrap_or_else(#{default_provider}));",
*codegenScope,
)
} else {
rustTemplate(
"make_token: self.make_token.unwrap_or_else(#{default_provider}),",
*codegenScope,
)
}
}
is ServiceConfig.DefaultForTests -> writable {
@ -71,41 +107,3 @@ class IdempotencyTokenProviderCustomization : NamedCustomization<ServiceConfig>(
}
}
}
/* Generated Code
pub struct Config {
pub(crate) make_token: Box<dyn crate::idempotency_token::MakeIdempotencyToken>,
}
impl Config {
pub fn builder() -> Builder {
Builder::default()
}
}
#[derive(Default)]
pub struct Builder {
#[allow(dead_code)]
make_token: Option<Box<dyn crate::idempotency_token::MakeIdempotencyToken>>,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
/// Sets the idempotency token provider to use for service calls that require tokens.
pub fn make_token(
mut self,
make_token: impl crate::idempotency_token::MakeIdempotencyToken + 'static,
) -> Self {
self.make_token = Some(Box::new(make_token));
self
}
pub fn build(self) -> Config {
Config {
make_token: self
.make_token
.unwrap_or_else(|| Box::new(crate::idempotency_token::default_provider())),
}
}
}
*/

View File

@ -11,6 +11,9 @@ import software.amazon.smithy.model.knowledge.OperationIndex
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.traits.IdempotencyTokenTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customizations.codegenScope
import software.amazon.smithy.rust.codegen.client.smithy.customize.TestUtilFeature
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
@ -22,7 +25,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
@ -89,6 +92,14 @@ sealed class ServiceConfig(name: String) : Section(name) {
*/
object BuilderBuild : ServiceConfig("BuilderBuild")
// TODO(enableNewSmithyRuntime): This is temporary until config builder is backed by a CloneableLayer.
// It is needed because certain config fields appear explicitly regardless of the smithy runtime mode, e.g.
// interceptors. The [BuilderBuild] section is bifurcated depending on the runtime mode (in the orchestrator mode,
// storing a field into a frozen layer and in the middleware moving it into a corresponding service config field)
// so we need a different temporary section to always move a field from a builder to service config within the
// build method.
object BuilderBuildExtras : ServiceConfig("BuilderBuildExtras")
/**
* A section for setting up a field to be used by RuntimePlugin
*/
@ -110,10 +121,53 @@ sealed class ServiceConfig(name: String) : Section(name) {
data class ConfigParam(
val name: String,
val type: Symbol,
val newtype: RuntimeType?,
val setterDocs: Writable?,
val getterDocs: Writable? = null,
val optional: Boolean = true,
)
) {
data class Builder(
var name: String? = null,
var type: Symbol? = null,
var newtype: RuntimeType? = null,
var setterDocs: Writable? = null,
var getterDocs: Writable? = null,
var optional: Boolean = true,
) {
fun name(name: String) = apply { this.name = name }
fun type(type: Symbol) = apply { this.type = type }
fun newtype(newtype: RuntimeType) = apply { this.newtype = newtype }
fun setterDocs(setterDocs: Writable?) = apply { this.setterDocs = setterDocs }
fun getterDocs(getterDocs: Writable?) = apply { this.getterDocs = getterDocs }
fun optional(optional: Boolean) = apply { this.optional = optional }
fun build() = ConfigParam(name!!, type!!, newtype, setterDocs, getterDocs, optional)
}
}
/**
* Generate a [RuntimeType] for a newtype whose name is [newtypeName] that wraps [inner].
*
* When config parameters are stored in a config map in Rust, stored parameters are keyed by type.
* Therefore, primitive types, such as bool and String, need to be wrapped in newtypes to make them distinct.
*/
fun configParamNewtype(newtypeName: String, inner: Symbol, runtimeConfig: RuntimeConfig) =
RuntimeType.forInlineFun(newtypeName, ClientRustModule.Config) {
val codegenScope = arrayOf(
"Storable" to RuntimeType.smithyTypes(runtimeConfig).resolve("config_bag::Storable"),
"StoreReplace" to RuntimeType.smithyTypes(runtimeConfig).resolve("config_bag::StoreReplace"),
)
rustTemplate(
"""
##[derive(Debug, Clone)]
pub(crate) struct $newtypeName($inner);
impl #{Storable} for $newtypeName {
type Storer = #{StoreReplace}<$newtypeName>;
}
""",
*codegenScope,
)
}
/**
* Config customization for a config param with no special behavior:
@ -121,19 +175,47 @@ data class ConfigParam(
* 2. convenience setter (non-optional)
* 3. standard setter (&mut self)
*/
fun standardConfigParam(param: ConfigParam): ConfigCustomization = object : ConfigCustomization() {
fun standardConfigParam(param: ConfigParam, codegenContext: ClientCodegenContext): ConfigCustomization = object : ConfigCustomization() {
private val runtimeMode = codegenContext.smithyRuntimeMode
override fun section(section: ServiceConfig): Writable {
return when (section) {
is ServiceConfig.ConfigStruct -> writable {
docsOrFallback(param.getterDocs)
val t = when (param.optional) {
true -> param.type.makeOptional()
false -> param.type
ServiceConfig.ConfigStruct -> writable {
if (runtimeMode.defaultToMiddleware) {
docsOrFallback(param.getterDocs)
val t = when (param.optional) {
true -> param.type.makeOptional()
false -> param.type
}
rust("pub (crate) ${param.name}: #T,", t)
}
}
ServiceConfig.ConfigImpl -> writable {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
pub(crate) fn ${param.name}(&self) -> #{output} {
self.inner.load::<#{newtype}>().map(#{f})
}
""",
"f" to writable {
if (param.type.name == "bool") {
rust("|ty| ty.0")
} else {
rust("|ty| ty.0.clone()")
}
},
"newtype" to param.newtype!!,
"output" to if (param.optional) {
param.type.makeOptional()
} else {
param.type
},
)
}
rust("pub (crate) ${param.name}: #T,", t)
}
ServiceConfig.ConfigImpl -> emptySection
ServiceConfig.BuilderStruct -> writable {
rust("${param.name}: #T,", param.type.makeOptional())
}
@ -162,8 +244,15 @@ fun standardConfigParam(param: ConfigParam): ConfigCustomization = object : Conf
}
ServiceConfig.BuilderBuild -> writable {
val default = "".letIf(!param.optional) { ".unwrap_or_default() " }
rust("${param.name}: self.${param.name}$default,")
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"layer.store_or_unset(self.${param.name}.map(#{newtype}));",
"newtype" to param.newtype!!,
)
} else {
val default = "".letIf(!param.optional) { ".unwrap_or_default() " }
rust("${param.name}: self.${param.name}$default,")
}
}
is ServiceConfig.RuntimePluginConfig -> emptySection
@ -199,19 +288,19 @@ typealias ConfigCustomization = NamedCustomization<ServiceConfig>
* // builder implementation
* }
*/
class ServiceConfigGenerator(
private val codegenContext: CodegenContext,
private val codegenContext: ClientCodegenContext,
private val customizations: List<ConfigCustomization> = listOf(),
) {
companion object {
fun withBaseBehavior(
codegenContext: CodegenContext,
codegenContext: ClientCodegenContext,
extraCustomizations: List<ConfigCustomization>,
): ServiceConfigGenerator {
val baseFeatures = mutableListOf<ConfigCustomization>()
if (codegenContext.serviceShape.needsIdempotencyToken(codegenContext.model)) {
baseFeatures.add(IdempotencyTokenProviderCustomization())
baseFeatures.add(IdempotencyTokenProviderCustomization(codegenContext))
}
return ServiceConfigGenerator(codegenContext, baseFeatures + extraCustomizations)
}
@ -228,6 +317,8 @@ class ServiceConfigGenerator(
"RuntimePlugin" to runtimeApi.resolve("client::runtime_plugin::RuntimePlugin"),
*preludeScope,
)
private val moduleUseName = codegenContext.moduleUseName()
private val runtimeMode = codegenContext.smithyRuntimeMode
fun render(writer: RustWriter) {
writer.docs("Service config.\n")
@ -236,6 +327,12 @@ class ServiceConfigGenerator(
}
Attribute(Attribute.derive(RuntimeType.Clone)).render(writer)
writer.rustBlock("pub struct Config") {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"inner: #{FrozenLayer},",
*codegenScope,
)
}
customizations.forEach {
it.section(ServiceConfig.ConfigStruct)(this)
}
@ -287,7 +384,7 @@ class ServiceConfigGenerator(
writer.rustBlock("impl Builder") {
writer.docs("Constructs a config builder.")
writer.rustTemplate("pub fn new() -> Self { Self::default() }")
writer.rust("pub fn new() -> Self { Self::default() }")
customizations.forEach {
it.section(ServiceConfig.BuilderImpl)(this)
}
@ -312,15 +409,31 @@ class ServiceConfigGenerator(
docs("Builds a [`Config`].")
rustBlock("pub fn build(self) -> Config") {
rustBlock("Config") {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""let mut layer = #{Layer}::new("$moduleUseName::Config");""",
*codegenScope,
)
customizations.forEach {
it.section(ServiceConfig.BuilderBuild)(this)
}
rustBlock("Config") {
customizations.forEach {
it.section(ServiceConfig.BuilderBuildExtras)(this)
}
rust("inner: layer.freeze(),")
}
} else {
rustBlock("Config") {
customizations.forEach {
it.section(ServiceConfig.BuilderBuild)(this)
}
}
}
}
}
customizations.forEach {
it.section(ServiceConfig.Extras)(writer)
customizations.forEach {
it.section(ServiceConfig.Extras)(writer)
}
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.client.smithy.generators.config
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
fun timeSourceCustomization(codegenContext: ClientCodegenContext) = standardConfigParam(
ConfigParam(
"time_source",
RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::SharedTimeSource").toSymbol(),
setterDocs = writable { docs("""Sets the time source used for this service""") },
optional = false,
),
)
class TimeSourceOperationCustomization : OperationCustomization() {
override fun section(section: OperationSection): Writable {
return when (section) {
is OperationSection.MutateRequest -> writable {
rust(
"""
${section.request}.properties_mut().insert(${section.config}.time_source.clone());
""",
)
}
else -> emptySection
}
}
}

View File

@ -5,36 +5,61 @@
package software.amazon.smithy.rust.codegen.client.testutil
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfigGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.configParamNewtype
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.TestWriterDelegator
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
/**
* Test helper to produce a valid config customization to test that a [ConfigCustomization] can be used in conjunction
* with other [ConfigCustomization]s.
*/
fun stubConfigCustomization(name: String): ConfigCustomization {
fun stubConfigCustomization(name: String, codegenContext: ClientCodegenContext): ConfigCustomization {
return object : ConfigCustomization() {
override fun section(section: ServiceConfig): Writable = writable {
when (section) {
ServiceConfig.ConfigStruct -> rust("_$name: u64,")
ServiceConfig.ConfigImpl -> rust(
"""
##[allow(missing_docs)]
pub fn $name(&self) -> u64 {
self._$name
ServiceConfig.ConfigStruct -> {
if (codegenContext.smithyRuntimeMode.defaultToMiddleware) {
rust("_$name: u64,")
}
""",
)
}
ServiceConfig.ConfigImpl -> {
if (codegenContext.smithyRuntimeMode.defaultToOrchestrator) {
rustTemplate(
"""
##[allow(missing_docs)]
pub fn $name(&self) -> u64 {
self.inner.load::<#{T}>().map(|u| u.0).unwrap()
}
""",
"T" to configParamNewtype(
"_$name".toPascalCase(), RuntimeType.U64.toSymbol(),
codegenContext.runtimeConfig,
),
)
} else {
rust(
"""
##[allow(missing_docs)]
pub fn $name(&self) -> u64 {
self._$name
}
""",
)
}
}
ServiceConfig.BuilderStruct -> rust("_$name: Option<u64>,")
ServiceConfig.BuilderImpl -> rust(
"""
@ -45,11 +70,25 @@ fun stubConfigCustomization(name: String): ConfigCustomization {
}
""",
)
ServiceConfig.BuilderBuild -> rust(
"""
_$name: self._$name.unwrap_or(123),
""",
)
ServiceConfig.BuilderBuild -> {
if (codegenContext.smithyRuntimeMode.defaultToOrchestrator) {
rustTemplate(
"""
layer.store_or_unset(self._$name.map(#{T}));
""",
"T" to configParamNewtype(
"_$name".toPascalCase(), RuntimeType.U64.toSymbol(),
codegenContext.runtimeConfig,
),
)
} else {
rust(
"""
_$name: self._$name.unwrap_or(123),
""",
)
}
}
else -> emptySection
}
}
@ -63,18 +102,19 @@ fun stubConfigCustomization(name: String): ConfigCustomization {
* */
@Suppress("NAME_SHADOWING")
fun validateConfigCustomizations(
codegenContext: ClientCodegenContext,
customization: ConfigCustomization,
project: TestWriterDelegator? = null,
): TestWriterDelegator {
val project = project ?: TestWorkspace.testProject()
stubConfigProject(customization, project)
stubConfigProject(codegenContext, customization, project)
project.compileAndTest()
return project
}
fun stubConfigProject(customization: ConfigCustomization, project: TestWriterDelegator): TestWriterDelegator {
val customizations = listOf(stubConfigCustomization("a")) + customization + stubConfigCustomization("b")
val generator = ServiceConfigGenerator(testClientCodegenContext("namespace test".asSmithyModel()), customizations = customizations.toList())
fun stubConfigProject(codegenContext: ClientCodegenContext, customization: ConfigCustomization, project: TestWriterDelegator): TestWriterDelegator {
val customizations = listOf(stubConfigCustomization("a", codegenContext)) + customization + stubConfigCustomization("b", codegenContext)
val generator = ServiceConfigGenerator(codegenContext, customizations = customizations.toList())
project.withModule(ClientRustModule.Config) {
generator.render(this)
unitTest(

View File

@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientModuleProvider
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustSettings
import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
@ -90,6 +91,9 @@ fun testClientCodegenContext(
rootDecorator ?: CombinedClientCodegenDecorator(emptyList()),
)
fun ClientCodegenContext.withSmithyRuntimeMode(smithyRuntimeMode: SmithyRuntimeMode): ClientCodegenContext =
copy(settings = settings.copy(codegenConfig = settings.codegenConfig.copy(enableNewSmithyRuntime = smithyRuntimeMode)))
fun TestWriterDelegator.clientRustSettings() =
testClientRustSettings(
service = ShapeId.from("fake#Fake"),

View File

@ -39,7 +39,7 @@ internal class ResiliencyConfigCustomizationTest {
val project = TestWorkspace.testProject(model, ClientCodegenConfig())
val codegenContext = testClientCodegenContext(model, settings = project.clientRustSettings())
stubConfigProject(ResiliencyConfigCustomization(codegenContext), project)
stubConfigProject(codegenContext, ResiliencyConfigCustomization(codegenContext), project)
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(project)
project.compileAndTest()
}

View File

@ -5,9 +5,12 @@
package software.amazon.smithy.rust.codegen.client.smithy.endpoint
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
@ -29,28 +32,51 @@ class ClientContextConfigCustomizationTest {
service TestService { operations: [] }
""".asSmithyModel()
@Test
fun `client params generate a valid customization`() {
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `client params generate a valid customization`(smithyRuntimeModeStr: String) {
val project = TestWorkspace.testProject()
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
project.unitTest {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").a_bool_param(true).build();
assert_eq!(conf.a_string_param.unwrap(), "hello!");
assert_eq!(conf.a_bool_param, Some(true));
""",
)
if (smithyRuntimeMode.defaultToOrchestrator) {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").a_bool_param(true).build();
assert_eq!(conf.a_string_param().unwrap(), "hello!");
assert_eq!(conf.a_bool_param(), Some(true));
""",
)
} else {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").a_bool_param(true).build();
assert_eq!(conf.a_string_param.unwrap(), "hello!");
assert_eq!(conf.a_bool_param, Some(true));
""",
)
}
}
// unset fields
project.unitTest {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").build();
assert_eq!(conf.a_string_param.unwrap(), "hello!");
assert_eq!(conf.a_bool_param, None);
""",
)
if (smithyRuntimeMode.defaultToOrchestrator) {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").build();
assert_eq!(conf.a_string_param().unwrap(), "hello!");
assert_eq!(conf.a_bool_param(), None);
""",
)
} else {
rust(
"""
let conf = crate::Config::builder().a_string_param("hello!").build();
assert_eq!(conf.a_string_param.unwrap(), "hello!");
assert_eq!(conf.a_bool_param, None);
""",
)
}
}
validateConfigCustomizations(ClientContextConfigCustomization(testClientCodegenContext(model)), project)
val context = testClientCodegenContext(model).withSmithyRuntimeMode(smithyRuntimeMode)
validateConfigCustomizations(context, ClientContextConfigCustomization(context), project)
}
}

View File

@ -5,12 +5,24 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.config
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
class IdempotencyTokenProviderCustomizationTest {
@Test
fun `generates a valid config`() {
validateConfigCustomizations(IdempotencyTokenProviderCustomization())
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generates a valid config`(smithyRuntimeModeStr: String) {
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val model = "namespace test".asSmithyModel()
val codegenContext = testClientCodegenContext(model).withSmithyRuntimeMode(smithyRuntimeMode)
validateConfigCustomizations(
codegenContext,
IdempotencyTokenProviderCustomization(codegenContext),
)
}
}

View File

@ -7,18 +7,26 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.config
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode
import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.client.testutil.withSmithyRuntimeMode
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
import software.amazon.smithy.rust.codegen.core.util.lookup
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
internal class ServiceConfigGeneratorTest {
@Test
@ -76,46 +84,98 @@ internal class ServiceConfigGeneratorTest {
model.lookup<ServiceShape>("com.example#ResourceService").needsIdempotencyToken(model) shouldBe true
}
@Test
fun `generate customizations as specified`() {
class ServiceCustomizer : NamedCustomization<ServiceConfig>() {
@ParameterizedTest
@ValueSource(strings = ["middleware", "orchestrator"])
fun `generate customizations as specified`(smithyRuntimeModeStr: String) {
class ServiceCustomizer(private val codegenContext: ClientCodegenContext) :
NamedCustomization<ServiceConfig>() {
private val runtimeMode = codegenContext.smithyRuntimeMode
override fun section(section: ServiceConfig): Writable {
return when (section) {
ServiceConfig.ConfigStructAdditionalDocs -> emptySection
ServiceConfig.ConfigStruct -> writable { rust("config_field: u64,") }
ServiceConfig.ConfigImpl -> writable {
rust(
"""
pub fn config_field(&self) -> u64 {
self.config_field
}
""",
)
ServiceConfig.ConfigStruct -> writable {
if (runtimeMode.defaultToMiddleware) {
rust("config_field: u64,")
}
}
ServiceConfig.ConfigImpl -> writable {
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
##[allow(missing_docs)]
pub fn config_field(&self) -> u64 {
self.inner.load::<#{T}>().map(|u| u.0).unwrap()
}
""",
"T" to configParamNewtype(
"config_field".toPascalCase(), RuntimeType.U64.toSymbol(),
codegenContext.runtimeConfig,
),
)
} else {
rust(
"""
##[allow(missing_docs)]
pub fn config_field(&self) -> u64 {
self.config_field
}
""",
)
}
}
ServiceConfig.BuilderStruct -> writable { rust("config_field: Option<u64>") }
ServiceConfig.BuilderImpl -> emptySection
ServiceConfig.BuilderBuild -> writable {
rust("config_field: self.config_field.unwrap_or_default(),")
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"layer.store_or_unset(self.config_field.map(#{T}));",
"T" to configParamNewtype(
"config_field".toPascalCase(), RuntimeType.U64.toSymbol(),
codegenContext.runtimeConfig,
),
)
} else {
rust("config_field: self.config_field.unwrap_or_default(),")
}
}
else -> emptySection
}
}
}
val ctx = testClientCodegenContext()
val sut = ServiceConfigGenerator(ctx, listOf(ServiceCustomizer()))
val symbolProvider = ctx.symbolProvider
val model = "namespace empty".asSmithyModel()
val smithyRuntimeMode = SmithyRuntimeMode.fromString(smithyRuntimeModeStr)
val codegenContext = testClientCodegenContext(model).withSmithyRuntimeMode(smithyRuntimeMode)
val sut = ServiceConfigGenerator(codegenContext, listOf(ServiceCustomizer(codegenContext)))
val symbolProvider = codegenContext.symbolProvider
val project = TestWorkspace.testProject(symbolProvider)
project.withModule(ClientRustModule.Config) {
sut.render(this)
unitTest(
"set_config_fields",
"""
let mut builder = Config::builder();
builder.config_field = Some(99);
let config = builder.build();
assert_eq!(config.config_field, 99);
""",
)
if (smithyRuntimeMode.defaultToOrchestrator) {
unitTest(
"set_config_fields",
"""
let mut builder = Config::builder();
builder.config_field = Some(99);
let config = builder.build();
assert_eq!(config.config_field(), 99);
""",
)
} else {
unitTest(
"set_config_fields",
"""
let mut builder = Config::builder();
builder.config_field = Some(99);
let config = builder.build();
assert_eq!(config.config_field, 99);
""",
)
}
}
project.compileAndTest()
}

View File

@ -104,8 +104,12 @@ class InlineDependency(
CargoDependency.Http,
)
fun idempotencyToken() =
forInlineableRustFile("idempotency_token", CargoDependency.FastRand)
fun idempotencyToken(runtimeConfig: RuntimeConfig) =
forInlineableRustFile(
"idempotency_token",
CargoDependency.FastRand,
CargoDependency.smithyTypes(runtimeConfig),
)
fun ec2QueryErrors(runtimeConfig: RuntimeConfig): InlineDependency =
forInlineableRustFile("ec2_query_errors", CargoDependency.smithyXml(runtimeConfig))
@ -232,7 +236,8 @@ data class CargoDependency(
val AsyncStream: CargoDependency = CargoDependency("async-stream", CratesIo("0.3.0"), DependencyScope.Dev)
val Criterion: CargoDependency = CargoDependency("criterion", CratesIo("0.4.0"), DependencyScope.Dev)
val FuturesCore: CargoDependency = CargoDependency("futures-core", CratesIo("0.3.25"), DependencyScope.Dev)
val FuturesUtil: CargoDependency = CargoDependency("futures-util", CratesIo("0.3.25"), DependencyScope.Dev, defaultFeatures = false)
val FuturesUtil: CargoDependency =
CargoDependency("futures-util", CratesIo("0.3.25"), DependencyScope.Dev, defaultFeatures = false)
val HdrHistogram: CargoDependency = CargoDependency("hdrhistogram", CratesIo("7.5.2"), DependencyScope.Dev)
val Hound: CargoDependency = CargoDependency("hound", CratesIo("3.4.0"), DependencyScope.Dev)
val PrettyAssertions: CargoDependency =

View File

@ -274,6 +274,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
val String = std.resolve("string::String")
val Sync = std.resolve("marker::Sync")
val TryFrom = stdConvert.resolve("TryFrom")
val U64 = std.resolve("primitive::u64")
val Vec = std.resolve("vec::Vec")
// external cargo dependency types
@ -431,7 +432,8 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
fun unwrappedXmlErrors(runtimeConfig: RuntimeConfig) =
forInlineDependency(InlineDependency.unwrappedXmlErrors(runtimeConfig))
val IdempotencyToken by lazy { forInlineDependency(InlineDependency.idempotencyToken()) }
fun idempotencyToken(runtimeConfig: RuntimeConfig) =
forInlineDependency(InlineDependency.idempotencyToken(runtimeConfig))
fun runtimePlugin(runtimeConfig: RuntimeConfig) =
RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::runtime_plugin::RuntimePlugin")

View File

@ -10,6 +10,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
@ -32,20 +33,23 @@ internal class InlineDependencyTest {
@Test
fun `locate dependencies from the inlineable module`() {
val dep = InlineDependency.idempotencyToken()
val runtimeConfig = TestRuntimeConfig
val dep = InlineDependency.serializationSettings(runtimeConfig)
val testProject = TestWorkspace.testProject()
testProject.lib {
rustTemplate(
"""
##[test]
fn idempotency_works() {
use #{idempotency}::uuid_v4;
let res = uuid_v4(0);
assert_eq!(res, "00000000-0000-4000-8000-000000000000");
fn header_serialization_settings_can_be_constructed() {
use #{serialization_settings}::HeaderSerializationSettings;
use #{aws_smithy_http}::header::set_request_header_if_absent;
let _settings = HeaderSerializationSettings::default();
}
""",
"idempotency" to dep.toType(),
"serialization_settings" to dep.toType(),
"aws_smithy_http" to RuntimeType.smithyHttp(runtimeConfig),
)
}
testProject.compileAndTest()

View File

@ -12,6 +12,7 @@ rt-tokio = ["tokio/time"]
test-util = []
[dependencies]
aws-smithy-types = { path = "../aws-smithy-types" }
pin-project-lite = "0.2"
tokio = { version = "1.23.1", features = ["sync"] }
tokio-stream = { version = "0.1.5", default-features = false }

View File

@ -1,4 +1,8 @@
allowed_external_types = [
"aws_smithy_types::config_bag::storable::Storable",
"aws_smithy_types::config_bag::storable::StoreReplace",
"aws_smithy_types::config_bag::storable::Storer",
# TODO(https://github.com/awslabs/smithy-rs/issues/1193): Switch to AsyncIterator once standardized
"futures_core::stream::Stream",

View File

@ -6,6 +6,7 @@
//! Provides an [`AsyncSleep`] trait that returns a future that sleeps for a given duration,
//! and implementations of `AsyncSleep` for different async runtimes.
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::pin::Pin;
@ -68,6 +69,10 @@ impl AsyncSleep for SharedAsyncSleep {
}
}
impl Storable for SharedAsyncSleep {
type Storer = StoreReplace<SharedAsyncSleep>;
}
#[cfg(feature = "rt-tokio")]
/// Returns a default sleep implementation based on the features enabled
pub fn default_async_sleep() -> Option<SharedAsyncSleep> {

View File

@ -24,7 +24,7 @@ pub struct ManualTimeSource {
impl TimeSource for ManualTimeSource {
fn now(&self) -> SystemTime {
self.start_time + self.log.lock().unwrap().iter().sum()
self.start_time + self.log.lock().unwrap().iter().sum::<Duration>()
}
}

View File

@ -4,6 +4,7 @@
*/
//! Time source abstraction to support WASM and testing
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::fmt::Debug;
use std::sync::Arc;
use std::time::SystemTime;
@ -86,3 +87,7 @@ impl TimeSource for SharedTimeSource {
self.0.now()
}
}
impl Storable for SharedTimeSource {
type Storer = StoreReplace<SharedTimeSource>;
}

View File

@ -8,6 +8,7 @@
use crate::erase::DynConnector;
use aws_smithy_async::rt::sleep::SharedAsyncSleep;
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use aws_smithy_types::timeout::TimeoutConfig;
use std::time::Duration;
use std::{fmt::Debug, sync::Arc};
@ -41,6 +42,10 @@ impl Debug for HttpConnector {
}
}
impl Storable for HttpConnector {
type Storer = StoreReplace<HttpConnector>;
}
impl HttpConnector {
/// If `HttpConnector` is `Prebuilt`, return a clone of that connector.
/// If `HttpConnector` is `ConnectorFn`, generate a new connector from settings and return it.

View File

@ -7,6 +7,7 @@
use crate::endpoint::error::InvalidEndpointError;
use crate::operation::error::BuildError;
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use http::uri::{Authority, Uri};
use std::borrow::Cow;
use std::fmt::{Debug, Formatter};
@ -66,6 +67,10 @@ impl<T> From<Arc<dyn ResolveEndpoint<T>>> for SharedEndpointResolver<T> {
}
}
impl<T: 'static> Storable for SharedEndpointResolver<T> {
type Storer = StoreReplace<SharedEndpointResolver<T>>;
}
impl<T> ResolveEndpoint<T> for SharedEndpointResolver<T> {
fn resolve_endpoint(&self, params: &T) -> Result {
self.0.resolve_endpoint(params)

View File

@ -5,7 +5,7 @@
use crate::client::auth::AuthSchemeId;
use crate::client::orchestrator::Future;
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use std::any::Any;
use std::fmt::Debug;
use std::sync::Arc;
@ -23,6 +23,10 @@ pub struct IdentityResolvers {
identity_resolvers: Vec<(AuthSchemeId, Arc<dyn IdentityResolver>)>,
}
impl Storable for IdentityResolvers {
type Storer = StoreReplace<IdentityResolvers>;
}
impl IdentityResolvers {
pub fn builder() -> builders::IdentityResolversBuilder {
builders::IdentityResolversBuilder::new()

View File

@ -9,7 +9,7 @@ pub mod error;
use crate::client::interceptors::context::wrappers::{
FinalizerInterceptorContextMut, FinalizerInterceptorContextRef,
};
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreAppend};
use aws_smithy_types::error::display::DisplayErrorContext;
pub use context::{
wrappers::{
@ -635,6 +635,10 @@ impl Deref for SharedInterceptor {
}
}
impl Storable for SharedInterceptor {
type Storer = StoreAppend<SharedInterceptor>;
}
/// Collection of [`SharedInterceptor`] that allows for only registration
#[derive(Debug, Clone, Default)]
pub struct InterceptorRegistrar {

View File

@ -5,6 +5,7 @@
//! This module defines types that describe when to retry given a response.
use crate::config_bag::{Storable, StoreReplace};
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
@ -278,6 +279,10 @@ pub struct RetryConfig {
reconnect_mode: ReconnectMode,
}
impl Storable for RetryConfig {
type Storer = StoreReplace<RetryConfig>;
}
/// Mode for connection re-establishment
///
/// By default, when a transient error is encountered, the connection in use will be poisoned. This

View File

@ -6,6 +6,7 @@
//! This module defines types that describe timeouts that can be applied to various stages of the
//! Smithy networking stack.
use crate::config_bag::{Storable, StoreReplace};
use std::time::Duration;
/// Builder for [`TimeoutConfig`].
@ -208,6 +209,10 @@ pub struct TimeoutConfig {
operation_attempt_timeout: Option<Duration>,
}
impl Storable for TimeoutConfig {
type Storer = StoreReplace<TimeoutConfig>;
}
impl TimeoutConfig {
/// Returns a builder to create a `TimeoutConfig`.
pub fn builder() -> TimeoutConfigBuilder {

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::sync::Mutex;
pub(crate) fn uuid_v4(input: u128) -> String {
@ -58,6 +59,10 @@ impl From<&'static str> for IdempotencyTokenProvider {
}
}
impl Storable for IdempotencyTokenProvider {
type Storer = StoreReplace<IdempotencyTokenProvider>;
}
impl IdempotencyTokenProvider {
pub fn make_idempotency_token(&self) -> String {
match &self.inner {