diff --git a/CHANGELOG.md b/CHANGELOG.md index 750e64f46f9f0b0574caf90e05e9d53f97705c79..0c571ce7b09df290e2cc045eed23bdd4f680bd80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ vNext (Month Day, Year) - (When complete) Add profile file provider for region (#594, #xyz) - Improve documentation on collection-aware builders (#664) - Add support for Transcribe `StartStreamTranscription` and S3 `SelectObjectContent` operations (#667) +- Add support for shared configuration between multiple services (#673) **Internal Changes** - Add NowOrLater future to smithy-async (#672) diff --git a/aws/rust-runtime/aws-types/src/config.rs b/aws/rust-runtime/aws-types/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..307a8ee7b961eaf22ea067eb026ddcff16b6c883 --- /dev/null +++ b/aws/rust-runtime/aws-types/src/config.rs @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#![deny(missing_docs)] + +//! AWS Shared Config +//! +//! This module contains an shared configuration representation that is agnostic from a specific service. + +use crate::region::Region; + +/// AWS Shared Configuration +pub struct Config { + region: Option, +} + +/// Builder for AWS Shared Configuration +#[derive(Default)] +pub struct Builder { + region: Option, +} + +impl Builder { + /// Set the region for the builder + /// + /// # Example + /// ```rust + /// use aws_types::config::Config; + /// use aws_types::region::Region; + /// let config = Config::builder().region(Region::new("us-east-1")).build(); + /// ``` + pub fn region(mut self, region: impl Into>) -> Self { + self.set_region(region); + self + } + + /// Set the region for the builder + /// + /// # Example + /// ```rust + /// fn region_override() -> Option { + /// // ... + /// # None + /// } + /// use aws_types::config::Config; + /// use aws_types::region::Region; + /// let mut builder = Config::builder(); + /// if let Some(region) = region_override() { + /// builder.set_region(region); + /// } + /// let config = builder.build(); + /// ``` + pub fn set_region(&mut self, region: impl Into>) -> &mut Self { + self.region = region.into(); + self + } + + /// Build a [`Config`](Config) from this builder + pub fn build(self) -> Config { + Config { + region: self.region, + } + } +} + +impl Config { + /// Configured region + pub fn region(&self) -> Option<&Region> { + self.region.as_ref() + } + + /// Config builder + pub fn builder() -> Builder { + Builder::default() + } +} diff --git a/aws/rust-runtime/aws-types/src/lib.rs b/aws/rust-runtime/aws-types/src/lib.rs index 5916d2facfcf1a081b4b8febb67f51fe366bd429..44f298b43bcdb132d0a866e82ff524775c4b3963 100644 --- a/aws/rust-runtime/aws-types/src/lib.rs +++ b/aws/rust-runtime/aws-types/src/lib.rs @@ -5,6 +5,7 @@ pub mod build_metadata; // internal APIs, may be unstable +pub mod config; #[doc(hidden)] pub mod os_shim_internal; pub mod profile; diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index a97fd8d3f710c2e4b0a848551732d25608f783fd..ba577815727688f37d239a11b545e49696ba4ac8 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -22,6 +22,7 @@ val DECORATORS = listOf( IntegrationTestDecorator(), AwsFluentClientDecorator(), CrateLicenseDecorator(), + SharedConfigDecorator(), // Service specific decorators DisabledAuthDecorator(), diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SharedConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SharedConfigDecorator.kt new file mode 100644 index 0000000000000000000000000000000000000000..bc085be058e8845e9f87e6c1f59ee60f69c265b7 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SharedConfigDecorator.kt @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.rust.codegen.rustlang.RustModule +import software.amazon.smithy.rust.codegen.rustlang.Writable +import software.amazon.smithy.rust.codegen.rustlang.asType +import software.amazon.smithy.rust.codegen.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.rustlang.writable +import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.smithy.RustCrate +import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator +import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig +import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization +import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig + +/** + * Adds functionality for constructing ::Config objects from aws_types::Config (SharedConfig) + * + * - `From<&aws_types::Config> for ::config::Builder`: Enabling customization + * - `pub fn new(&aws_types::Config) -> ::Config`: Direct construction without customization + */ +class SharedConfigDecorator : RustCodegenDecorator { + override val name: String = "SharedConfig" + override val order: Byte = 0 + + override fun configCustomizations( + protocolConfig: ProtocolConfig, + baseCustomizations: List + ): List { + return baseCustomizations + NewFromShared(protocolConfig.runtimeConfig) + } + + override fun extras(protocolConfig: ProtocolConfig, rustCrate: RustCrate) { + val codegenScope = arrayOf( + "Config" to awsTypes(runtimeConfig = protocolConfig.runtimeConfig).asType().member("config::Config") + ) + rustCrate.withModule(RustModule.Config) { + // TODO(sharedconfig): As more items are added to aws_types::Config, use them here to configure the config builder + it.rustTemplate( + """ + impl From<&#{Config}> for Builder { + fn from(input: &#{Config}) -> Self { + let mut builder = Builder::default(); + builder = builder.region(input.region().cloned()); + builder + } + } + """, + *codegenScope + ) + } + } +} + +class NewFromShared(runtimeConfig: RuntimeConfig) : ConfigCustomization() { + private val codegenScope = arrayOf( + "Config" to awsTypes(runtimeConfig = runtimeConfig).asType().member("config::Config") + ) + override fun section(section: ServiceConfig): Writable { + return when (section) { + ServiceConfig.ConfigImpl -> writable { + rustTemplate( + """ + pub fn new(config: &#{Config}) -> Self { + Builder::from(config).build() + } + """, + *codegenScope + ) + } + else -> emptySection + } + } +} diff --git a/aws/sdk/integration-tests/dynamodb/Cargo.toml b/aws/sdk/integration-tests/dynamodb/Cargo.toml index 8be40e6b4c57956aa08cd42fbde73acbc57169c0..b99f9ca9bc34ef9c30b62d2a6d9c79d746dd10cb 100644 --- a/aws/sdk/integration-tests/dynamodb/Cargo.toml +++ b/aws/sdk/integration-tests/dynamodb/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" aws-auth = { path = "../../build/aws-sdk/aws-auth" } aws-http = { path = "../../build/aws-sdk/aws-http" } aws-hyper = { path = "../../build/aws-sdk/aws-hyper" } +aws-types = { path = "../../build/aws-sdk/aws-types" } aws-sdk-dynamodb = { path = "../../build/aws-sdk/dynamodb" } bytes = "1" criterion = { version = "0.3.4" } diff --git a/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs new file mode 100644 index 0000000000000000000000000000000000000000..512017c6800b38e3d3029ad7d7a1ea9a4fc7b183 --- /dev/null +++ b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use aws_sdk_dynamodb::{Credentials, Region}; +use http::Uri; + +/// Iterative test of loading clients from shared configuration +#[tokio::test] +async fn shared_config_testbed() { + let shared_config = aws_types::config::Config::builder() + .region(Region::new("us-east-4")) + .build(); + let conf = aws_sdk_dynamodb::config::Builder::from(&shared_config) + .credentials_provider(Credentials::from_keys("asdf", "asdf", None)) + .build(); + let (conn, request) = smithy_client::test_connection::capture_request(None); + let svc = aws_sdk_dynamodb::Client::from_conf_conn(conf, conn); + let _ = svc.list_tables().send().await; + assert_eq!( + request.expect_request().uri(), + &Uri::from_static("https://dynamodb.us-east-4.amazonaws.com") + ); +}