From e71720693e49df1a913fbb269c14f39924a8012f Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 13 Jul 2023 17:51:55 -0700 Subject: [PATCH 01/17] Change the default runtime mode to orchestrator --- .github/workflows/ci.yml | 4 +- aws/rust-runtime/aws-config/src/lib.rs | 2 +- aws/sdk-adhoc-test/build.gradle.kts | 2 +- aws/sdk/build.gradle.kts | 2 +- .../dynamodb/benches/deserialization_bench.rs | 2 +- .../dynamodb/benches/serialization_bench.rs | 2 +- .../retries-with-client-rate-limiting.rs | 2 +- .../kms/tests/retryable_errors.rs | 4 +- .../s3/tests/alternative-async-runtime.rs | 10 ++-- .../s3/tests/config-override.rs | 10 ++-- .../s3/tests/config_to_builder.rs | 2 +- .../integration-tests/s3/tests/endpoints.rs | 4 +- .../integration-tests/sts/tests/signing-it.rs | 4 +- .../timestreamquery/tests/endpoint_disco.rs | 2 +- codegen-client-test/build.gradle.kts | 2 +- .../client/smithy/ClientRustSettings.kt | 2 +- .../ci-scripts/check-aws-sdk-middleware-impl | 27 ++++++++++ .../check-aws-sdk-orchestrator-impl | 49 ------------------- 18 files changed, 55 insertions(+), 77 deletions(-) create mode 100755 tools/ci-scripts/check-aws-sdk-middleware-impl delete mode 100755 tools/ci-scripts/check-aws-sdk-orchestrator-impl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cd2c8877b..9a1e8f274d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,8 +84,8 @@ jobs: test: - action: check-aws-sdk-adhoc-tests runner: ubuntu-latest - # TODO(enableNewSmithyRuntimeCleanup): Remove `check-aws-sdk-orchestrator-impl` when cleaning up middleware - - action: check-aws-sdk-orchestrator-impl + # TODO(enableNewSmithyRuntimeCleanup): Remove `check-aws-sdk-middleware-impl` when cleaning up middleware + - action: check-aws-sdk-middleware-impl runner: smithy_ubuntu-latest_8-core - action: check-client-codegen-integration-tests runner: smithy_ubuntu-latest_8-core diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index f0e4e3bdca..0da03ab2b4 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -764,7 +764,7 @@ mod loader { assert_eq!(Some(&app_name), conf.app_name()); } - #[cfg(aws_sdk_orchestrator_mode)] + #[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn disable_default_credentials() { let config = from_env().no_credentials().load().await; diff --git a/aws/sdk-adhoc-test/build.gradle.kts b/aws/sdk-adhoc-test/build.gradle.kts index 3a631a5bb2..2e09902e5d 100644 --- a/aws/sdk-adhoc-test/build.gradle.kts +++ b/aws/sdk-adhoc-test/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") } -fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "middleware" +fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "orchestrator" val allCodegenTests = listOf( CodegenTest( diff --git a/aws/sdk/build.gradle.kts b/aws/sdk/build.gradle.kts index d5225d66a9..f7f79c962d 100644 --- a/aws/sdk/build.gradle.kts +++ b/aws/sdk/build.gradle.kts @@ -61,7 +61,7 @@ val crateVersioner by lazy { aws.sdk.CrateVersioner.defaultFor(rootProject, prop fun getRustMSRV(): String = properties.get("rust.msrv") ?: throw Exception("Rust MSRV missing") fun getPreviousReleaseVersionManifestPath(): String? = properties.get("aws.sdk.previous.release.versions.manifest") -fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "middleware" +fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "orchestrator" fun loadServiceMembership(): Membership { val membershipOverride = properties.get("aws.services")?.let { parseMembership(it) } diff --git a/aws/sdk/integration-tests/dynamodb/benches/deserialization_bench.rs b/aws/sdk/integration-tests/dynamodb/benches/deserialization_bench.rs index a29f74d72f..6b83b189d5 100644 --- a/aws/sdk/integration-tests/dynamodb/benches/deserialization_bench.rs +++ b/aws/sdk/integration-tests/dynamodb/benches/deserialization_bench.rs @@ -6,7 +6,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; fn do_bench() { - #[cfg(not(aws_sdk_orchestrator_mode))] + #[cfg(aws_sdk_middleware_mode)] { use aws_sdk_dynamodb::operation::query::Query; use aws_smithy_http::response::ParseHttpResponse; diff --git a/aws/sdk/integration-tests/dynamodb/benches/serialization_bench.rs b/aws/sdk/integration-tests/dynamodb/benches/serialization_bench.rs index 909ed85a51..f103c07c00 100644 --- a/aws/sdk/integration-tests/dynamodb/benches/serialization_bench.rs +++ b/aws/sdk/integration-tests/dynamodb/benches/serialization_bench.rs @@ -34,7 +34,7 @@ macro_rules! attr_obj { } fn do_bench(_config: &Config, _input: &PutItemInput) { - #[cfg(not(aws_sdk_orchestrator_mode))] + #[cfg(aws_sdk_middleware_mode)] { use futures_util::FutureExt; diff --git a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs index 21ad0470bc..162c1fc50b 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] mod test { use aws_sdk_dynamodb::config::{Credentials, Region, SharedAsyncSleep}; use aws_sdk_dynamodb::{config::retry::RetryConfig, error::ProvideErrorMetadata}; diff --git a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs index e052dc28f2..3a275b460d 100644 --- a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs +++ b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#[cfg(not(aws_sdk_orchestrator_mode))] +#[cfg(aws_sdk_middleware_mode)] mod middleware_mode_tests { use aws_http::retry::AwsResponseRetryClassifier; use aws_sdk_kms as kms; @@ -66,7 +66,7 @@ mod middleware_mode_tests { } } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] mod orchestrator_mode_tests { use aws_credential_types::Credentials; use aws_runtime::retries::classifier::AwsErrorCodeClassifier; diff --git a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs index 8d1a604d95..5c1d8969cd 100644 --- a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -20,7 +20,7 @@ use aws_smithy_types::timeout::TimeoutConfig; use std::fmt::Debug; use std::time::{Duration, Instant}; -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; #[derive(Debug)] @@ -36,7 +36,7 @@ impl AsyncSleep for SmolSleep { #[test] fn test_smol_runtime_timeouts() { - #[cfg(aws_sdk_orchestrator_mode)] + #[cfg(not(aws_sdk_middleware_mode))] let _guard = capture_test_logs(); if let Err(err) = smol::block_on(async { timeout_test(SharedAsyncSleep::new(SmolSleep)).await }) @@ -48,7 +48,7 @@ fn test_smol_runtime_timeouts() { #[test] fn test_smol_runtime_retry() { - #[cfg(aws_sdk_orchestrator_mode)] + #[cfg(not(aws_sdk_middleware_mode))] let _guard = capture_test_logs(); if let Err(err) = smol::block_on(async { retry_test(SharedAsyncSleep::new(SmolSleep)).await }) { @@ -68,7 +68,7 @@ impl AsyncSleep for AsyncStdSleep { #[test] fn test_async_std_runtime_timeouts() { - #[cfg(aws_sdk_orchestrator_mode)] + #[cfg(not(aws_sdk_middleware_mode))] let _guard = capture_test_logs(); if let Err(err) = async_std::task::block_on(async { @@ -81,7 +81,7 @@ fn test_async_std_runtime_timeouts() { #[test] fn test_async_std_runtime_retry() { - #[cfg(aws_sdk_orchestrator_mode)] + #[cfg(not(aws_sdk_middleware_mode))] let _guard = capture_test_logs(); if let Err(err) = diff --git a/aws/sdk/integration-tests/s3/tests/config-override.rs b/aws/sdk/integration-tests/s3/tests/config-override.rs index b3d93be867..620b6405fa 100644 --- a/aws/sdk/integration-tests/s3/tests/config-override.rs +++ b/aws/sdk/integration-tests/s3/tests/config-override.rs @@ -9,7 +9,7 @@ use aws_sdk_s3::Client; use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; use aws_types::SdkConfig; -// TODO(enableNewSmithyRuntimeCleanup): Remove this attribute once #[cfg(aws_sdk_orchestrator_mode)] +// TODO(enableNewSmithyRuntimeCleanup): Remove this attribute once #[cfg(aws_sdk_middleware_mode)] // has been removed #[allow(dead_code)] fn test_client() -> (CaptureRequestReceiver, Client) { @@ -23,7 +23,7 @@ fn test_client() -> (CaptureRequestReceiver, Client) { (captured_request, client) } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn operation_overrides_force_path_style() { let (captured_request, client) = test_client(); @@ -42,7 +42,7 @@ async fn operation_overrides_force_path_style() { ); } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn operation_overrides_fips() { let (captured_request, client) = test_client(); @@ -61,7 +61,7 @@ async fn operation_overrides_fips() { ); } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn operation_overrides_dual_stack() { let (captured_request, client) = test_client(); @@ -84,7 +84,7 @@ async fn operation_overrides_dual_stack() { // accessed in ServiceRuntimePlugin::config. Currently, a credentials cache created for a single // operation invocation is not picked up by an identity resolver. /* -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn operation_overrides_credentials_provider() { let (captured_request, client) = test_client(); diff --git a/aws/sdk/integration-tests/s3/tests/config_to_builder.rs b/aws/sdk/integration-tests/s3/tests/config_to_builder.rs index 122b0418b6..af899ee2f6 100644 --- a/aws/sdk/integration-tests/s3/tests/config_to_builder.rs +++ b/aws/sdk/integration-tests/s3/tests/config_to_builder.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn test_config_to_builder() { use aws_sdk_s3::config::AppName; diff --git a/aws/sdk/integration-tests/s3/tests/endpoints.rs b/aws/sdk/integration-tests/s3/tests/endpoints.rs index ec87eac2a3..0479f94bc5 100644 --- a/aws/sdk/integration-tests/s3/tests/endpoints.rs +++ b/aws/sdk/integration-tests/s3/tests/endpoints.rs @@ -62,7 +62,7 @@ async fn dual_stack() { ); } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn multi_region_access_points() { let (_captured_request, client) = test_client(|b| b); @@ -84,7 +84,7 @@ async fn multi_region_access_points() { ); } -#[cfg(not(aws_sdk_orchestrator_mode))] +#[cfg(aws_sdk_middleware_mode)] #[tokio::test] async fn multi_region_access_points() { let (_captured_request, client) = test_client(|b| b); diff --git a/aws/sdk/integration-tests/sts/tests/signing-it.rs b/aws/sdk/integration-tests/sts/tests/signing-it.rs index 01727a53c9..74e82e0026 100644 --- a/aws/sdk/integration-tests/sts/tests/signing-it.rs +++ b/aws/sdk/integration-tests/sts/tests/signing-it.rs @@ -25,7 +25,7 @@ async fn assume_role_signed() { } // TODO(enableNewSmithyRuntimeCleanup): Delete the middleware version of this test -#[cfg(not(aws_sdk_orchestrator_mode))] +#[cfg(aws_sdk_middleware_mode)] #[tokio::test] async fn web_identity_unsigned() { let creds = Credentials::for_tests(); @@ -44,7 +44,7 @@ async fn web_identity_unsigned() { ); } -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn web_identity_unsigned() { let (server, request) = capture_request(None); diff --git a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs index 447bdead21..16d4222515 100644 --- a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs +++ b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#[cfg(aws_sdk_orchestrator_mode)] +#[cfg(not(aws_sdk_middleware_mode))] #[tokio::test] async fn do_endpoint_discovery() { use aws_credential_types::provider::SharedCredentialsProvider; diff --git a/codegen-client-test/build.gradle.kts b/codegen-client-test/build.gradle.kts index 8ccf6f6ad4..ae84542a3e 100644 --- a/codegen-client-test/build.gradle.kts +++ b/codegen-client-test/build.gradle.kts @@ -15,7 +15,7 @@ plugins { val smithyVersion: String by project val defaultRustDocFlags: String by project val properties = PropertyRetriever(rootProject, project) -fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "middleware" +fun getSmithyRuntimeMode(): String = properties.get("smithy.runtime.mode") ?: "orchestrator" val pluginName = "rust-client-codegen" val workingDirUnderBuildDir = "smithyprojections/codegen-client-test/" diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt index f4d2fdeede..7f286714b8 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt @@ -133,7 +133,7 @@ data class ClientCodegenConfig( private const val defaultIncludeFluentClient = true private const val defaultAddMessageToErrors = true private val defaultEventStreamAllowList: Set = emptySet() - private val defaultEnableNewSmithyRuntime = SmithyRuntimeMode.Middleware + private val defaultEnableNewSmithyRuntime = SmithyRuntimeMode.Orchestrator fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional) = if (node.isPresent) { diff --git a/tools/ci-scripts/check-aws-sdk-middleware-impl b/tools/ci-scripts/check-aws-sdk-middleware-impl new file mode 100755 index 0000000000..ea6cfe85df --- /dev/null +++ b/tools/ci-scripts/check-aws-sdk-middleware-impl @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +# This script tests the SDK smoke test services against the middleware implementation + +C_YELLOW='\033[1;33m' +C_RESET='\033[0m' + +set -eu +cd smithy-rs + +./gradlew aws:sdk:assemble -Psmithy.runtime.mode=middleware + +cd aws/sdk/build/aws-sdk/sdk +for service in */; do + pushd "${service}" + echo -e "${C_YELLOW}# Running 'cargo test --all-features' on '${service}'${C_RESET}" + RUSTFLAGS="${RUSTFLAGS:-} --cfg aws_sdk_middleware_mode" cargo test --all-features --all-targets --no-fail-fast + echo -e "${C_YELLOW}# Running 'cargo clippy --all-features' on '${service}'${C_RESET}" + RUSTFLAGS="${RUSTFLAGS:-} --cfg aws_sdk_middleware_mode" cargo clippy --all-features + popd +done + +echo "SUCCESS" diff --git a/tools/ci-scripts/check-aws-sdk-orchestrator-impl b/tools/ci-scripts/check-aws-sdk-orchestrator-impl deleted file mode 100755 index b86ebf3f0f..0000000000 --- a/tools/ci-scripts/check-aws-sdk-orchestrator-impl +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -# - -# This script tests the SDK smoke test services against the orchestrator implementation - -C_YELLOW='\033[1;33m' -C_RESET='\033[0m' - -set -eu -cd smithy-rs - -services_that_pass_tests=(\ - "aws-config"\ - "config"\ - "dynamodb"\ - "ec2"\ - "ecs"\ - "glacier"\ - "iam"\ - "kms"\ - "lambda"\ - "polly"\ - "qldbsession"\ - "route53"\ - "s3"\ - "s3control"\ - "sso"\ - "sts"\ - "timestreamquery"\ - "timestreamwrite"\ - "transcribestreaming"\ -) - -./gradlew aws:sdk:assemble -Psmithy.runtime.mode=orchestrator - -cd aws/sdk/build/aws-sdk/sdk -for service in "${services_that_pass_tests[@]}"; do - pushd "${service}" - echo -e "${C_YELLOW}# Running 'cargo test --all-features' on '${service}'${C_RESET}" - RUSTFLAGS="${RUSTFLAGS:-} --cfg aws_sdk_orchestrator_mode" cargo test --all-features --all-targets --no-fail-fast - echo -e "${C_YELLOW}# Running 'cargo clippy --all-features' on '${service}'${C_RESET}" - RUSTFLAGS="${RUSTFLAGS:-} --cfg aws_sdk_orchestrator_mode" cargo clippy --all-features - popd -done - -echo "SUCCESS" From e803842b0bb0b50b17ca2373b3910a2e7994eee1 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Fri, 14 Jul 2023 15:12:36 -0700 Subject: [PATCH 02/17] Fix examples --- aws/rust-runtime/aws-types/src/sdk_config.rs | 4 +-- .../endpoint/EndpointConfigCustomization.kt | 33 +++++++++++++++++++ examples/pokemon-service-common/Cargo.toml | 4 +++ examples/pokemon-service-common/src/lib.rs | 22 ++----------- .../tests/plugins_execution_order.rs | 21 +++++++----- .../pokemon-service-tls/tests/common/mod.rs | 27 ++++++--------- examples/pokemon-service/tests/common/mod.rs | 22 +++++-------- .../pokemon-service-test/tests/helpers.rs | 30 +++-------------- 8 files changed, 77 insertions(+), 86 deletions(-) diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index 4d0728a985..08e49469ba 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -117,7 +117,7 @@ impl Builder { self } - /// Set the endpoint url to use when making requests. + /// Set the endpoint URL to use when making requests. /// # Examples /// ``` /// use aws_types::SdkConfig; @@ -128,7 +128,7 @@ impl Builder { self } - /// Set the endpoint url to use when making requests. + /// Set the endpoint URL to use when making requests. pub fn set_endpoint_url(&mut self, endpoint_url: Option) -> &mut Self { self.endpoint_url = endpoint_url; self diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt index 81b89d989d..297c7550a9 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt @@ -31,6 +31,7 @@ internal class EndpointConfigCustomization( private val codegenScope = arrayOf( *preludeScope, "DefaultEndpointResolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::orchestrator::endpoints::DefaultEndpointResolver"), + "Endpoint" to RuntimeType.smithyHttp(runtimeConfig).resolve("endpoint::Endpoint"), "OldSharedEndpointResolver" to types.sharedEndpointResolver, "Params" to typesGenerator.paramsStruct(), "Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"), @@ -127,7 +128,39 @@ internal class EndpointConfigCustomization( } rustTemplate( """ + /// Set the endpoint URL to use when making requests. + /// + /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. + /// + /// ## Panics + /// Panics if an invalid URL is given. + pub fn endpoint_url(mut self, endpoint_url: impl #{Into}<#{String}>) -> Self { + self.set_endpoint_url(#{Some}(endpoint_url.into())); + self + } + + /// Set the endpoint URL to use when making requests. + /// + /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. + /// + /// ## Panics + /// Panics if an invalid URL is given. + pub fn set_endpoint_url(&mut self, endpoint_url: #{Option}<#{String}>) -> &mut Self { + ##[allow(deprecated)] + self.set_endpoint_resolver( + endpoint_url.map(|url| { + #{OldSharedEndpointResolver}::new( + #{Endpoint}::immutable(url).expect("invalid endpoint URL") + ) + }) + ); + self + } + /// Sets the endpoint resolver to use when making requests. + /// + /// Note: setting an endpoint resolver will replace any endpoint URL that has been set. + /// $defaultResolverDocs pub fn endpoint_resolver(mut self, endpoint_resolver: impl $resolverTrait + 'static) -> Self { self.set_endpoint_resolver(#{Some}(#{OldSharedEndpointResolver}::new(endpoint_resolver))); diff --git a/examples/pokemon-service-common/Cargo.toml b/examples/pokemon-service-common/Cargo.toml index d0012e178b..f2c86eee0e 100644 --- a/examples/pokemon-service-common/Cargo.toml +++ b/examples/pokemon-service-common/Cargo.toml @@ -21,3 +21,7 @@ aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http" } aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server" } pokemon-service-client = { path = "../pokemon-service-client" } pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk" } + +[dev-dependencies] +aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client", features = ["test-util"] } +aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["test-util"] } diff --git a/examples/pokemon-service-common/src/lib.rs b/examples/pokemon-service-common/src/lib.rs index d8618e0cce..cb5c4bd604 100644 --- a/examples/pokemon-service-common/src/lib.rs +++ b/examples/pokemon-service-common/src/lib.rs @@ -16,12 +16,9 @@ use std::{ use async_stream::stream; use aws_smithy_client::{conns, hyper_ext::Adapter}; -use aws_smithy_http::{body::SdkBody, byte_stream::ByteStream, operation::Request}; +use aws_smithy_http::{body::SdkBody, byte_stream::ByteStream}; use aws_smithy_http_server::Extension; -use http::{ - uri::{Authority, Scheme}, - Uri, -}; +use http::Uri; use pokemon_service_server_sdk::{ error, input, model, model::CapturingPayload, output, types::Blob, }; @@ -38,21 +35,6 @@ const PIKACHU_ITALIAN_FLAVOR_TEXT: &str = const PIKACHU_JAPANESE_FLAVOR_TEXT: &str = "ほっぺたの りょうがわに ちいさい でんきぶくろを もつ。ピンチのときに ほうでんする。"; -/// Rewrites the base URL of a request -pub fn rewrite_base_url( - scheme: Scheme, - authority: Authority, -) -> impl Fn(Request) -> Request + Clone { - move |mut req| { - let http_req = req.http_mut(); - let mut uri_parts = http_req.uri().clone().into_parts(); - uri_parts.authority = Some(authority.clone()); - uri_parts.scheme = Some(scheme.clone()); - *http_req.uri_mut() = Uri::from_parts(uri_parts).expect("failed to create uri from parts"); - req - } -} - /// Kills [`Child`] process when dropped. #[derive(Debug)] #[must_use] diff --git a/examples/pokemon-service-common/tests/plugins_execution_order.rs b/examples/pokemon-service-common/tests/plugins_execution_order.rs index 7a768cb005..fb5f0fb4d7 100644 --- a/examples/pokemon-service-common/tests/plugins_execution_order.rs +++ b/examples/pokemon-service-common/tests/plugins_execution_order.rs @@ -14,7 +14,8 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http_server::plugin::{HttpMarker, HttpPlugins, IdentityPlugin, Plugin}; use tower::{Layer, Service}; -use pokemon_service_client::{operation::do_nothing::DoNothingInput, Config}; +use aws_smithy_client::test_connection::capture_request; +use pokemon_service_client::{Client, Config}; use pokemon_service_common::do_nothing; trait OperationExt { @@ -43,13 +44,17 @@ async fn plugin_layers_are_executed_in_registration_order() { ) .do_nothing(do_nothing) .build_unchecked(); - let request = DoNothingInput::builder() - .build() - .unwrap() - .make_operation(&Config::builder().build()) - .await - .unwrap() - .into_http(); + + let request = { + let (conn, rcvr) = capture_request(None); + let config = Config::builder() + .http_connector(conn) + .endpoint_url("http://localhost:1234") + .build(); + Client::from_conf(config).do_nothing().send().await.unwrap(); + rcvr.expect_request() + }; + app.call(request).await.unwrap(); let output_guard = output.lock().unwrap(); diff --git a/examples/pokemon-service-tls/tests/common/mod.rs b/examples/pokemon-service-tls/tests/common/mod.rs index 5eb9d804ba..c56c5fe59f 100644 --- a/examples/pokemon-service-tls/tests/common/mod.rs +++ b/examples/pokemon-service-tls/tests/common/mod.rs @@ -3,18 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -use std::{fs::File, io::BufReader, process::Command, str::FromStr, time::Duration}; +use std::{fs::File, io::BufReader, process::Command, time::Duration}; use assert_cmd::prelude::*; -use aws_smithy_client::{ - erase::{DynConnector, DynMiddleware}, - hyper_ext::Adapter, -}; -use hyper::http::uri::{Authority, Scheme}; +use aws_smithy_client::hyper_ext::Adapter; use tokio::time::sleep; -use pokemon_service_client::{Builder, Client, Config}; -use pokemon_service_common::{rewrite_base_url, ChildDrop}; +use pokemon_service_client::{Client, Config}; +use pokemon_service_common::ChildDrop; use pokemon_service_tls::{DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_TEST_CERT}; pub async fn run_server() -> ChildDrop { @@ -28,7 +24,7 @@ pub async fn run_server() -> ChildDrop { // Returns a client that only talks through https and http2 connections. // It is useful in testing whether our server can talk to http2. -pub fn client_http2_only() -> Client> { +pub fn client_http2_only() -> Client { // Create custom cert store and add our test certificate to prevent unknown cert issues. let mut reader = BufReader::new(File::open(DEFAULT_TEST_CERT).expect("could not open certificate")); @@ -47,12 +43,9 @@ pub fn client_http2_only() -> Client> .enable_http2() .build(); - let authority = Authority::from_str(&format!("{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) - .expect("could not parse authority"); - let raw_client = Builder::new() - .connector(DynConnector::new(Adapter::builder().build(connector))) - .middleware_fn(rewrite_base_url(Scheme::HTTPS, authority)) - .build_dyn(); - let config = Config::builder().build(); - Client::with_config(raw_client, config) + let config = Config::builder() + .http_connector(Adapter::builder().build(connector)) + .endpoint_url(format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) + .build(); + Client::from_conf(config) } diff --git a/examples/pokemon-service/tests/common/mod.rs b/examples/pokemon-service/tests/common/mod.rs index d10430bfbd..2d58f4a975 100644 --- a/examples/pokemon-service/tests/common/mod.rs +++ b/examples/pokemon-service/tests/common/mod.rs @@ -3,16 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -use std::{process::Command, str::FromStr, time::Duration}; +use std::{process::Command, time::Duration}; use assert_cmd::prelude::*; -use aws_smithy_client::erase::{DynConnector, DynMiddleware}; -use hyper::http::uri::{Authority, Scheme}; use tokio::time::sleep; use pokemon_service::{DEFAULT_ADDRESS, DEFAULT_PORT}; -use pokemon_service_client::{Builder, Client, Config}; -use pokemon_service_common::{rewrite_base_url, ChildDrop}; +use pokemon_service_client::{Client, Config}; +use pokemon_service_common::ChildDrop; pub async fn run_server() -> ChildDrop { let crate_name = std::env::var("CARGO_PKG_NAME").unwrap(); @@ -27,13 +25,9 @@ pub fn base_url() -> String { format!("http://{DEFAULT_ADDRESS}:{DEFAULT_PORT}") } -pub fn client() -> Client> { - let authority = Authority::from_str(&format!("{DEFAULT_ADDRESS}:{DEFAULT_PORT}")) - .expect("could not parse authority"); - let raw_client = Builder::new() - .rustls_connector(Default::default()) - .middleware_fn(rewrite_base_url(Scheme::HTTP, authority)) - .build_dyn(); - let config = Config::builder().build(); - Client::with_config(raw_client, config) +pub fn client() -> Client { + let config = Config::builder() + .endpoint_url(format!("http://{DEFAULT_ADDRESS}:{DEFAULT_PORT}")) + .build(); + Client::from_conf(config) } diff --git a/examples/python/pokemon-service-test/tests/helpers.rs b/examples/python/pokemon-service-test/tests/helpers.rs index 23050a3da3..6d1dac12c6 100644 --- a/examples/python/pokemon-service-test/tests/helpers.rs +++ b/examples/python/pokemon-service-test/tests/helpers.rs @@ -17,10 +17,7 @@ use tokio::time; const TEST_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.key"); const TEST_CERT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.crt"); -pub type PokemonClient = Client< - aws_smithy_client::erase::DynConnector, - aws_smithy_client::erase::DynMiddleware, ->; +pub type PokemonClient = Client; enum PokemonServiceVariant { Http, @@ -94,12 +91,8 @@ impl Drop for PokemonService { #[allow(dead_code)] pub fn client() -> PokemonClient { let base_url = PokemonServiceVariant::Http.base_url(); - let raw_client = Builder::new() - .rustls_connector(Default::default()) - .middleware_fn(rewrite_base_url(base_url)) - .build_dyn(); - let config = Config::builder().build(); - Client::with_config(raw_client, config) + let config = Config::builder().endpoint_url(base_url).build(); + Client::from_conf(config) } #[allow(dead_code)] @@ -122,19 +115,6 @@ pub fn http2_client() -> PokemonClient { .build(); let base_url = PokemonServiceVariant::Http2.base_url(); - let raw_client = Builder::new() - .connector(DynConnector::new(Adapter::builder().build(connector))) - .middleware_fn(rewrite_base_url(base_url)) - .build_dyn(); - let config = Config::builder().build(); - Client::with_config(raw_client, config) -} - -fn rewrite_base_url(base_url: &'static str) -> impl Fn(Request) -> Request + Clone { - move |mut req| { - let http_req = req.http_mut(); - let uri = format!("{base_url}{}", http_req.uri().path()); - *http_req.uri_mut() = uri.parse().unwrap(); - req - } + let config = Config::builder().endpoint_url(base_url).build(); + Client::from_conf(config) } From bb578678add7e3296c2ebfdd786b2fe34e0afbde Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Fri, 14 Jul 2023 16:52:49 -0700 Subject: [PATCH 03/17] Fix Python examples --- examples/python/pokemon-service-test/tests/helpers.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/python/pokemon-service-test/tests/helpers.rs b/examples/python/pokemon-service-test/tests/helpers.rs index 6d1dac12c6..708e17fe88 100644 --- a/examples/python/pokemon-service-test/tests/helpers.rs +++ b/examples/python/pokemon-service-test/tests/helpers.rs @@ -8,10 +8,9 @@ use std::io::BufReader; use std::process::Command; use std::time::Duration; -use aws_smithy_client::{erase::DynConnector, hyper_ext::Adapter}; -use aws_smithy_http::operation::Request; +use aws_smithy_client::hyper_ext::Adapter; use command_group::{CommandGroup, GroupChild}; -use pokemon_service_client::{Builder, Client, Config}; +use pokemon_service_client::{Client, Config}; use tokio::time; const TEST_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.key"); @@ -115,6 +114,9 @@ pub fn http2_client() -> PokemonClient { .build(); let base_url = PokemonServiceVariant::Http2.base_url(); - let config = Config::builder().endpoint_url(base_url).build(); + let config = Config::builder() + .http_connector(Adapter::builder().build(connector)) + .endpoint_url(base_url) + .build(); Client::from_conf(config) } From 8641b042129ba1b5460adcc2ce41421896583fb5 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Fri, 14 Jul 2023 17:04:05 -0700 Subject: [PATCH 04/17] Fix duplicate endpoint url methods --- .../rustsdk/EndpointBuiltInsDecorator.kt | 2 +- aws/sdk/build.gradle.kts | 1 + .../client/smithy/ClientRustSettings.kt | 11 +++- .../endpoint/EndpointConfigCustomization.kt | 64 ++++++++++--------- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecorator.kt index 3cc4783a17..1d79f10a6a 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecorator.kt @@ -178,7 +178,7 @@ fun decoratorForBuiltIn( private val endpointUrlDocs = writable { rust( """ - /// Sets the endpoint url used to communicate with this service + /// Sets the endpoint URL used to communicate with this service /// Note: this is used in combination with other endpoint rules, e.g. an API that applies a host-label prefix /// will be prefixed onto this URL. To fully override the endpoint resolver, use diff --git a/aws/sdk/build.gradle.kts b/aws/sdk/build.gradle.kts index f7f79c962d..97c9c96077 100644 --- a/aws/sdk/build.gradle.kts +++ b/aws/sdk/build.gradle.kts @@ -101,6 +101,7 @@ fun generateSmithyBuild(services: AwsServices): String { }, "codegen": { "includeFluentClient": false, + "includeEndpointUrlConfig": false, "renameErrors": false, "debugMode": $debugMode, "eventStreamAllowList": [$eventStreamAllowListMembers], diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt index 7f286714b8..0fec857bec 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt @@ -125,6 +125,8 @@ data class ClientCodegenConfig( val eventStreamAllowList: Set = defaultEventStreamAllowList, // TODO(SmithyRuntime): Remove this once we commit to switch to aws-smithy-runtime and aws-smithy-runtime-api val enableNewSmithyRuntime: SmithyRuntimeMode = defaultEnableNewSmithyRuntime, + /** If true, adds `endpoint_url`/`set_endpoint_url` methods to the service config */ + val includeEndpointUrlConfig: Boolean = defaultIncludeEndpointUrlConfig, ) : CoreCodegenConfig( formatTimeoutSeconds, debugMode, ) { @@ -134,6 +136,7 @@ data class ClientCodegenConfig( private const val defaultAddMessageToErrors = true private val defaultEventStreamAllowList: Set = emptySet() private val defaultEnableNewSmithyRuntime = SmithyRuntimeMode.Orchestrator + private const val defaultIncludeEndpointUrlConfig = true fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional) = if (node.isPresent) { @@ -144,11 +147,15 @@ data class ClientCodegenConfig( .map { array -> array.toList().mapNotNull { node -> node.asStringNode().orNull()?.value } } .orNull()?.toSet() ?: defaultEventStreamAllowList, renameExceptions = node.get().getBooleanMemberOrDefault("renameErrors", defaultRenameExceptions), - includeFluentClient = node.get().getBooleanMemberOrDefault("includeFluentClient", defaultIncludeFluentClient), - addMessageToErrors = node.get().getBooleanMemberOrDefault("addMessageToErrors", defaultAddMessageToErrors), + includeFluentClient = node.get() + .getBooleanMemberOrDefault("includeFluentClient", defaultIncludeFluentClient), + addMessageToErrors = node.get() + .getBooleanMemberOrDefault("addMessageToErrors", defaultAddMessageToErrors), enableNewSmithyRuntime = SmithyRuntimeMode.fromString( node.get().getStringMemberOrDefault("enableNewSmithyRuntime", "middleware"), ), + includeEndpointUrlConfig = node.get() + .getBooleanMemberOrDefault("includeEndpointUrlConfig", defaultIncludeEndpointUrlConfig), ) } else { ClientCodegenConfig( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt index 297c7550a9..2f81ef33f5 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt @@ -126,37 +126,43 @@ internal class EndpointConfigCustomization( } else { "" } + if (codegenContext.settings.codegenConfig.includeEndpointUrlConfig) { + rustTemplate( + """ + /// Set the endpoint URL to use when making requests. + /// + /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. + /// + /// ## Panics + /// Panics if an invalid URL is given. + pub fn endpoint_url(mut self, endpoint_url: impl #{Into}<#{String}>) -> Self { + self.set_endpoint_url(#{Some}(endpoint_url.into())); + self + } + + /// Set the endpoint URL to use when making requests. + /// + /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. + /// + /// ## Panics + /// Panics if an invalid URL is given. + pub fn set_endpoint_url(&mut self, endpoint_url: #{Option}<#{String}>) -> &mut Self { + ##[allow(deprecated)] + self.set_endpoint_resolver( + endpoint_url.map(|url| { + #{OldSharedEndpointResolver}::new( + #{Endpoint}::immutable(url).expect("invalid endpoint URL") + ) + }) + ); + self + } + """, + *codegenScope, + ) + } rustTemplate( """ - /// Set the endpoint URL to use when making requests. - /// - /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. - /// - /// ## Panics - /// Panics if an invalid URL is given. - pub fn endpoint_url(mut self, endpoint_url: impl #{Into}<#{String}>) -> Self { - self.set_endpoint_url(#{Some}(endpoint_url.into())); - self - } - - /// Set the endpoint URL to use when making requests. - /// - /// Note: setting an endpoint URL will replace any endpoint resolver that has been set. - /// - /// ## Panics - /// Panics if an invalid URL is given. - pub fn set_endpoint_url(&mut self, endpoint_url: #{Option}<#{String}>) -> &mut Self { - ##[allow(deprecated)] - self.set_endpoint_resolver( - endpoint_url.map(|url| { - #{OldSharedEndpointResolver}::new( - #{Endpoint}::immutable(url).expect("invalid endpoint URL") - ) - }) - ); - self - } - /// Sets the endpoint resolver to use when making requests. /// /// Note: setting an endpoint resolver will replace any endpoint URL that has been set. From 870b89e59ecbcbf2da71d23d4ec9e399b17179a2 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 17 Jul 2023 15:36:25 -0700 Subject: [PATCH 05/17] Fix codegen-client tests --- .../smithy/rustsdk/AwsPresigningDecorator.kt | 6 +- .../EndpointParamsInterceptorGenerator.kt | 3 +- .../smithy/generators/OperationGenerator.kt | 2 +- .../OperationRuntimePluginGenerator.kt | 8 +- .../ServiceRuntimePluginGenerator.kt | 4 +- .../protocol/ProtocolTestGenerator.kt | 56 ++-- .../customizations/ApiKeyAuthDecoratorTest.kt | 9 +- .../HttpVersionListGeneratorTest.kt | 9 +- .../ResiliencyConfigCustomizationTest.kt | 8 + .../smithy/endpoint/EndpointsDecoratorTest.kt | 103 +++++++- .../generators/EndpointTraitBindingsTest.kt | 140 +++++++++- .../protocol/ProtocolTestGeneratorTest.kt | 244 +++++++++--------- .../protocols/AwsQueryCompatibleTest.kt | 124 +++++---- .../rust/codegen/core/smithy/RuntimeType.kt | 2 + 14 files changed, 492 insertions(+), 226 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt index f236a08f49..fac47eede2 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt @@ -359,14 +359,14 @@ class AwsPresignedFluentBuilderMethod( val smithyTypes = RuntimeType.smithyTypes(codegenContext.runtimeConfig) rustTemplate( """ - ##[derive(Debug)] + ##[derive(::std::fmt::Debug)] struct AlternatePresigningSerializerRuntimePlugin; impl #{RuntimePlugin} for AlternatePresigningSerializerRuntimePlugin { - fn config(&self) -> Option<#{FrozenLayer}> { + fn config(&self) -> #{Option}<#{FrozenLayer}> { use #{ConfigBagAccessors}; let mut cfg = #{Layer}::new("presigning_serializer"); cfg.set_request_serializer(#{SharedRequestSerializer}::new(#{AlternateSerializer})); - Some(cfg.freeze()) + #{Some}(cfg.freeze()) } } """, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt index b9dca27732..1894dcd02a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt @@ -32,6 +32,7 @@ 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.inputShape import software.amazon.smithy.rust.codegen.core.util.orNull +import software.amazon.smithy.rust.codegen.core.util.toPascalCase class EndpointParamsInterceptorGenerator( private val codegenContext: ClientCodegenContext, @@ -122,7 +123,7 @@ class EndpointParamsInterceptorGenerator( idx.getClientContextParams(codegenContext.serviceShape).orNull()?.parameters?.forEach { (name, param) -> val setterName = EndpointParamsGenerator.setterName(name) val inner = ClientContextConfigCustomization.toSymbol(param.type, symbolProvider) - val newtype = configParamNewtype(name, inner, codegenContext.runtimeConfig) + val newtype = configParamNewtype(name.toPascalCase(), inner, codegenContext.runtimeConfig) rustTemplate( ".$setterName(cfg.#{load_from_service_config_layer})", "load_from_service_config_layer" to loadFromConfigBag(inner.name, newtype), diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationGenerator.kt index 39795ed503..0d93284e20 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationGenerator.kt @@ -167,7 +167,7 @@ open class OperationGenerator( ) -> #{RuntimePlugins} { let mut runtime_plugins = client_runtime_plugins.with_operation_plugin(Self::new()); #{additional_runtime_plugins} - if let Some(config_override) = config_override { + if let #{Some}(config_override) = config_override { for plugin in config_override.runtime_plugins.iter().cloned() { runtime_plugins = runtime_plugins.with_operation_plugin(plugin); } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt index a798a9753b..73bfe50780 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt @@ -67,15 +67,15 @@ class OperationRuntimePluginGenerator( let mut cfg = #{Layer}::new(${operationShape.id.name.dq()}); use #{ConfigBagAccessors} as _; - cfg.set_request_serializer(#{SharedRequestSerializer}::new(${operationStructName}RequestSerializer)); - cfg.set_response_deserializer(#{DynResponseDeserializer}::new(${operationStructName}ResponseDeserializer)); + cfg.store_put(#{SharedRequestSerializer}::new(${operationStructName}RequestSerializer)); + cfg.store_put(#{DynResponseDeserializer}::new(${operationStructName}ResponseDeserializer)); ${"" /* TODO(IdentityAndAuth): Resolve auth parameters from input for services that need this */} cfg.set_auth_option_resolver_params(#{AuthOptionResolverParams}::new(#{StaticAuthOptionResolverParams}::new())); #{additional_config} - Some(cfg.freeze()) + #{Some}(cfg.freeze()) } fn runtime_components(&self) -> #{Cow}<'_, #{RuntimeComponentsBuilder}> { @@ -85,7 +85,7 @@ class OperationRuntimePluginGenerator( #{Cow}::Owned( #{RuntimeComponentsBuilder}::new(${operationShape.id.name.dq()}) - .with_retry_classifiers(Some(retry_classifiers)) + .with_retry_classifiers(#{Some}(retry_classifiers)) #{auth_options} #{interceptors} ) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt index b5da3eaba9..ed68a0b306 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt @@ -101,7 +101,7 @@ class ServiceRuntimePluginGenerator( } writer.rustTemplate( """ - ##[derive(Debug)] + ##[derive(::std::fmt::Debug)] pub(crate) struct ServiceRuntimePlugin { config: #{Option}<#{FrozenLayer}>, runtime_components: #{RuntimeComponentsBuilder}, @@ -136,7 +136,7 @@ class ServiceRuntimePluginGenerator( """ let mut cfg = #{Layer}::new(${codegenContext.serviceShape.id.name.dq()}); #{additional_config} - Some(cfg.freeze()) + #{Some}(cfg.freeze()) """, *codegenScope, "additional_config" to additionalConfig, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt index 22c64d36e0..f4d9a4c705 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt @@ -34,7 +34,6 @@ 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.withBlock 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.generators.protocol.ProtocolSupport import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.getTrait @@ -45,6 +44,7 @@ import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.outputShape import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import java.util.logging.Logger +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType as RT data class ClientCreationParams( val codegenContext: ClientCodegenContext, @@ -81,9 +81,9 @@ class DefaultProtocolTestGenerator( """, "Client" to ClientRustModule.root.toType().resolve("Client"), "Builder" to ClientRustModule.client.toType().resolve("Builder"), - "SmithyEndpointStage" to RuntimeType.smithyHttp(codegenContext.runtimeConfig) + "SmithyEndpointStage" to RT.smithyHttp(codegenContext.runtimeConfig) .resolve("endpoint::middleware::SmithyEndpointStage"), - "MapRequestLayer" to RuntimeType.smithyHttpTower(codegenContext.runtimeConfig) + "MapRequestLayer" to RT.smithyHttpTower(codegenContext.runtimeConfig) .resolve("map_request::MapRequestLayer"), ) } else { @@ -100,6 +100,7 @@ class DefaultProtocolTestGenerator( } }, ) : ProtocolTestGenerator { + private val rc = codegenContext.runtimeConfig private val logger = Logger.getLogger(javaClass.name) private val inputShape = operationShape.inputShape(codegenContext.model) @@ -110,8 +111,8 @@ class DefaultProtocolTestGenerator( private val instantiator = ClientInstantiator(codegenContext) private val codegenScope = arrayOf( - "SmithyHttp" to RuntimeType.smithyHttp(codegenContext.runtimeConfig), - "AssertEq" to RuntimeType.PrettyAssertions.resolve("assert_eq!"), + "SmithyHttp" to RT.smithyHttp(rc), + "AssertEq" to RT.PrettyAssertions.resolve("assert_eq!"), ) sealed class TestCase { @@ -227,7 +228,7 @@ class DefaultProtocolTestGenerator( #{customParams} """, - "capture_request" to CargoDependency.smithyClient(codegenContext.runtimeConfig) + "capture_request" to CargoDependency.smithyClient(rc) .toDevDependency() .withFeature("test-util") .toType() @@ -334,7 +335,7 @@ class DefaultProtocolTestGenerator( writeInline("let expected_output =") instantiator.render(this, expectedShape, testCase.params) write(";") - write("let mut http_response = #T::new()", RuntimeType.HttpResponseBuilder) + write("let mut http_response = #T::new()", RT.HttpResponseBuilder) testCase.headers.forEach { (key, value) -> writeWithNoFormatting(".header(${key.dq()}, ${value.dq()})") } @@ -344,12 +345,12 @@ class DefaultProtocolTestGenerator( .body(#T::from(${testCase.body.orNull()?.dq()?.replace("#", "##") ?: "vec![]"})) .unwrap(); """, - RuntimeType.sdkBody(runtimeConfig = codegenContext.runtimeConfig), + RT.sdkBody(runtimeConfig = rc), ) if (codegenContext.smithyRuntimeMode.defaultToMiddleware) { rust( "let mut op_response = #T::new(http_response);", - RuntimeType.operationModule(codegenContext.runtimeConfig).resolve("Response"), + RT.operationModule(rc).resolve("Response"), ) rustTemplate( """ @@ -363,14 +364,19 @@ class DefaultProtocolTestGenerator( }); """, "op" to operationSymbol, - "copy_from_slice" to RuntimeType.Bytes.resolve("copy_from_slice"), - "parse_http_response" to RuntimeType.parseHttpResponse(codegenContext.runtimeConfig), + "copy_from_slice" to RT.Bytes.resolve("copy_from_slice"), + "parse_http_response" to RT.parseHttpResponse(rc), ) } else { rustTemplate( """ use #{ResponseDeserializer}; - let de = #{OperationDeserializer}; + use #{RuntimePlugin}; + + let op = #{Operation}::new(); + let config = op.config().expect("the operation has config"); + let de = config.load::<#{DynResponseDeserializer}>().expect("the config must have a deserializer"); + let parsed = de.deserialize_streaming(&mut http_response); let parsed = parsed.unwrap_or_else(|| { let http_response = http_response.map(|body| { @@ -379,12 +385,12 @@ class DefaultProtocolTestGenerator( de.deserialize_nonstreaming(&http_response) }); """, - "OperationDeserializer" to codegenContext.symbolProvider.moduleForShape(operationShape).toType() - .resolve("${operationSymbol.name}ResponseDeserializer"), - "copy_from_slice" to RuntimeType.Bytes.resolve("copy_from_slice"), - "ResponseDeserializer" to CargoDependency.smithyRuntimeApi(codegenContext.runtimeConfig).toType() - .resolve("client::orchestrator::ResponseDeserializer"), - "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "copy_from_slice" to RT.Bytes.resolve("copy_from_slice"), + "DynResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::DynResponseDeserializer"), + "Operation" to codegenContext.symbolProvider.toSymbol(operationShape), + "ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::ResponseDeserializer"), + "RuntimePlugin" to RT.runtimePlugin(rc), + "SdkBody" to RT.sdkBody(rc), ) } if (expectedShape.hasTrait()) { @@ -432,9 +438,7 @@ class DefaultProtocolTestGenerator( } else { when (codegenContext.model.expectShape(member.target)) { is DoubleShape, is FloatShape -> { - addUseImports( - RuntimeType.protocolTest(codegenContext.runtimeConfig, "FloatEquals").toSymbol(), - ) + addUseImports(RT.protocolTest(rc, "FloatEquals").toSymbol()) rust( """ assert!(parsed.$memberName.float_equals(&expected_output.$memberName), @@ -470,8 +474,8 @@ class DefaultProtocolTestGenerator( "#T(&body, ${ rustWriter.escape(body).dq() }, #T::from(${(mediaType ?: "unknown").dq()}))", - RuntimeType.protocolTest(codegenContext.runtimeConfig, "validate_body"), - RuntimeType.protocolTest(codegenContext.runtimeConfig, "MediaType"), + RT.protocolTest(rc, "validate_body"), + RT.protocolTest(rc, "MediaType"), ) } } @@ -512,7 +516,7 @@ class DefaultProtocolTestGenerator( assertOk(rustWriter) { write( "#T($actualExpression, $variableName)", - RuntimeType.protocolTest(codegenContext.runtimeConfig, "validate_headers"), + RT.protocolTest(rc, "validate_headers"), ) } } @@ -566,7 +570,7 @@ class DefaultProtocolTestGenerator( assertOk(rustWriter) { write( "#T($actualExpression, $expectedVariableName)", - RuntimeType.protocolTest(codegenContext.runtimeConfig, checkFunction), + RT.protocolTest(rc, checkFunction), ) } } @@ -576,7 +580,7 @@ class DefaultProtocolTestGenerator( * for pretty printing protocol test helper results */ private fun assertOk(rustWriter: RustWriter, inner: Writable) { - rustWriter.write("#T(", RuntimeType.protocolTest(codegenContext.runtimeConfig, "assert_ok")) + rustWriter.write("#T(", RT.protocolTest(rc, "assert_ok")) inner(rustWriter) rustWriter.write(");") } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecoratorTest.kt index 1673ac299f..a6da0a29a3 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecoratorTest.kt @@ -6,14 +6,15 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.rust -import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings +// TODO(enableNewSmithyRuntimeCleanup): Delete this test (replaced by HttpAuthDecoratorTest) internal class ApiKeyAuthDecoratorTest { private val modelQuery = """ namespace test @@ -46,7 +47,8 @@ internal class ApiKeyAuthDecoratorTest { val testDir = clientIntegrationTest( modelQuery, // just run integration tests - IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }), + TestCodegenSettings.middlewareModeTestParams + .copy(command = { "cargo test --test *".runWithWarnings(it) }), ) { clientCodegenContext, rustCrate -> rustCrate.integrationTest("api_key_present_in_property_bag") { val moduleName = clientCodegenContext.moduleUseName() @@ -136,7 +138,8 @@ internal class ApiKeyAuthDecoratorTest { val testDir = clientIntegrationTest( modelHeader, // just run integration tests - IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }), + TestCodegenSettings.middlewareModeTestParams + .copy(command = { "cargo test --test *".runWithWarnings(it) }), ) { clientCodegenContext, rustCrate -> rustCrate.integrationTest("api_key_auth_is_set_in_http_header") { val moduleName = clientCodegenContext.moduleUseName() diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpVersionListGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpVersionListGeneratorTest.kt index 73633a603e..602e848261 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpVersionListGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpVersionListGeneratorTest.kt @@ -6,10 +6,10 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.rust -import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest // ./gradlew codegen-client:test --tests software.amazon.smithy.rust.codegen.client.HttpVersionListGeneratorTest --info // ``` +// TODO(enableNewSmithyRuntimeCleanup): Delete this test (incomplete http version support wasn't ported to orchestrator) internal class HttpVersionListGeneratorTest { @Test fun `http version list integration test (no preferred version)`() { @@ -44,7 +45,7 @@ internal class HttpVersionListGeneratorTest { greeting: String } """.asSmithyModel() - clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + clientIntegrationTest(model, TestCodegenSettings.middlewareModeTestParams) { clientCodegenContext, rustCrate -> val moduleName = clientCodegenContext.moduleUseName() rustCrate.integrationTest("http_version_list") { Attribute.TokioTest.render(this) @@ -95,7 +96,7 @@ internal class HttpVersionListGeneratorTest { greeting: String } """.asSmithyModel() - clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + clientIntegrationTest(model, TestCodegenSettings.middlewareModeTestParams) { clientCodegenContext, rustCrate -> val moduleName = clientCodegenContext.moduleUseName() rustCrate.integrationTest("validate_http") { Attribute.TokioTest.render(this) @@ -161,7 +162,7 @@ internal class HttpVersionListGeneratorTest { clientIntegrationTest( model, - IntegrationTestParams(addModuleToEventStreamAllowList = true), + TestCodegenSettings.middlewareModeTestParams.copy(addModuleToEventStreamAllowList = true), ) { clientCodegenContext, rustCrate -> val moduleName = clientCodegenContext.moduleUseName() rustCrate.integrationTest("validate_eventstream_http") { diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomizationTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomizationTest.kt index b84f204c24..b25e28c75e 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomizationTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomizationTest.kt @@ -7,6 +7,8 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenConfig +import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginGenerator import software.amazon.smithy.rust.codegen.client.testutil.clientRustSettings import software.amazon.smithy.rust.codegen.client.testutil.stubConfigProject import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext @@ -40,6 +42,12 @@ internal class ResiliencyConfigCustomizationTest { val codegenContext = testClientCodegenContext(model, settings = project.clientRustSettings()) stubConfigProject(codegenContext, ResiliencyConfigCustomization(codegenContext), project) + project.withModule(ClientRustModule.config) { + ServiceRuntimePluginGenerator(codegenContext).render( + this, + listOf(ResiliencyServiceRuntimePluginCustomization(codegenContext)), + ) + } ResiliencyReExportCustomization(codegenContext).extras(project) project.compileAndTest() } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt index 62fff162aa..6d5bcaa05f 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt @@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.endpoint import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -120,12 +121,14 @@ class EndpointsDecoratorTest { } """.asSmithyModel() + // TODO(enableNewSmithyRuntimeCleanup): Delete this test (replaced by the second @Test below) @Test fun `set an endpoint in the property bag`() { val testDir = clientIntegrationTest( model, // Just run integration tests. - IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }), + TestCodegenSettings.middlewareModeTestParams + .copy(command = { "cargo test --test *".runWithWarnings(it) }), ) { clientCodegenContext, rustCrate -> rustCrate.integrationTest("endpoint_params_test") { val moduleName = clientCodegenContext.moduleUseName() @@ -167,4 +170,102 @@ class EndpointsDecoratorTest { failure.output shouldContain "https://failingtest.com" "cargo clippy".runWithWarnings(testDir) } + + @Test + fun `resolve endpoint`() { + val testDir = clientIntegrationTest( + model, + // Just run integration tests. + IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }), + ) { clientCodegenContext, rustCrate -> + rustCrate.integrationTest("endpoint_params_test") { + val moduleName = clientCodegenContext.moduleUseName() + Attribute.TokioTest.render(this) + rust( + """ + async fn endpoint_params_are_set() { + use aws_smithy_async::rt::sleep::TokioSleep; + use aws_smithy_client::never::NeverConnector; + use aws_smithy_runtime_api::box_error::BoxError; + use aws_smithy_runtime_api::client::orchestrator::EndpointResolverParams; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; + use aws_smithy_types::config_bag::ConfigBag; + use aws_smithy_types::endpoint::Endpoint; + use aws_smithy_types::timeout::TimeoutConfig; + use std::sync::atomic::AtomicBool; + use std::sync::atomic::Ordering; + use std::sync::Arc; + use std::time::Duration; + use $moduleName::{ + config::endpoint::Params, config::interceptors::BeforeTransmitInterceptorContextRef, + config::Interceptor, config::SharedAsyncSleep, Client, Config, + }; + + ##[derive(Clone, Debug, Default)] + struct TestInterceptor { + called: Arc, + } + impl Interceptor for TestInterceptor { + fn read_before_transmit( + &self, + _context: &BeforeTransmitInterceptorContextRef<'_>, + _runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + let params = cfg + .load::() + .expect("params set in config"); + let params: &Params = params.get().expect("correct type"); + assert_eq!( + params, + &Params::builder() + .bucket("bucket-name".to_string()) + .built_in_with_default("some-default") + .bool_built_in_with_default(true) + .a_bool_param(false) + .a_string_param("hello".to_string()) + .region("us-east-2".to_string()) + .build() + .unwrap() + ); + + let endpoint = cfg.load::().expect("endpoint set in config"); + assert_eq!(endpoint.url(), "https://www.us-east-2.example.com"); + + self.called.store(true, Ordering::Relaxed); + Ok(()) + } + } + + let interceptor = TestInterceptor::default(); + let config = Config::builder() + .http_connector(NeverConnector::new()) + .interceptor(interceptor.clone()) + .timeout_config( + TimeoutConfig::builder() + .operation_timeout(Duration::from_millis(30)) + .build(), + ) + .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) + .a_string_param("hello") + .a_bool_param(false) + .build(); + let client = Client::from_conf(config); + + let _ = dbg!(client.test_operation().bucket("bucket-name").send().await); + assert!( + interceptor.called.load(Ordering::Relaxed), + "the interceptor should have been called" + ); + } + """, + ) + } + } + // the model has an intentionally failing test—ensure it fails + val failure = shouldThrow { "cargo test".runWithWarnings(testDir) } + failure.output shouldContain "endpoint::test::test_1" + failure.output shouldContain "https://failingtest.com" + "cargo clippy".runWithWarnings(testDir) + } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt index 34225e8dfd..a94148d21c 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt @@ -12,13 +12,16 @@ import org.junit.jupiter.params.provider.ValueSource import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.traits.EndpointTrait import software.amazon.smithy.rust.codegen.client.smithy.SmithyRuntimeMode +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.Attribute +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.implBlock 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.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig @@ -120,9 +123,10 @@ internal class EndpointTraitBindingsTest { project.compileAndTest() } + // TODO(enableNewSmithyRuntimeCleanup): Delete this test (replaced by the @Test below it) @ExperimentalPathApi @Test - fun `endpoint integration test`() { + fun `endpoint integration test middleware`() { val model = """ namespace com.example use aws.protocols#awsJson1_0 @@ -142,7 +146,7 @@ internal class EndpointTraitBindingsTest { greeting: String } """.asSmithyModel() - clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + clientIntegrationTest(model, TestCodegenSettings.middlewareModeTestParams) { clientCodegenContext, rustCrate -> val moduleName = clientCodegenContext.moduleUseName() rustCrate.integrationTest("test_endpoint_prefix") { Attribute.TokioTest.render(this) @@ -168,4 +172,136 @@ internal class EndpointTraitBindingsTest { } } } + + @ExperimentalPathApi + @Test + fun `endpoint integration test`() { + val model = """ + namespace com.example + use aws.protocols#awsJson1_0 + use smithy.rules#endpointRuleSet + + @awsJson1_0 + @aws.api#service(sdkId: "Test", endpointPrefix: "differentprefix") + @endpointRuleSet({ + "version": "1.0", + "rules": [{ + "conditions": [], + "type": "endpoint", + "endpoint": { + "url": "https://example.com", + "properties": {} + } + }], + "parameters": {} + }) + service TestService { + operations: [SayHello], + version: "1" + } + @endpoint(hostPrefix: "test123.{greeting}.") + operation SayHello { + input: SayHelloInput + } + structure SayHelloInput { + @required + @hostLabel + greeting: String + } + """.asSmithyModel() + clientIntegrationTest(model) { clientCodegenContext, rustCrate -> + val moduleName = clientCodegenContext.moduleUseName() + rustCrate.integrationTest("test_endpoint_prefix") { + Attribute.TokioTest.render(this) + rustTemplate( + """ + async fn test_endpoint_prefix() { + use #{aws_smithy_client}::test_connection::capture_request; + use aws_smithy_http::body::SdkBody; + use aws_smithy_http::endpoint::EndpointPrefix; + use aws_smithy_runtime_api::box_error::BoxError; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; + use aws_smithy_types::config_bag::ConfigBag; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::{Arc, Mutex}; + use $moduleName::{ + config::interceptors::BeforeTransmitInterceptorContextRef, + config::Interceptor, + error::DisplayErrorContext, + {Client, Config}, + }; + + ##[derive(Clone, Debug, Default)] + struct TestInterceptor { + called: Arc, + last_endpoint_prefix: Arc>>, + } + impl Interceptor for TestInterceptor { + fn read_before_transmit( + &self, + _context: &BeforeTransmitInterceptorContextRef<'_>, + _runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + self.called.fetch_add(1, Ordering::Relaxed); + if let Some(prefix) = cfg.load::() { + self.last_endpoint_prefix + .lock() + .unwrap() + .replace(prefix.clone()); + } + Ok(()) + } + } + + let (conn, _r) = capture_request(Some( + http::Response::builder() + .status(200) + .body(SdkBody::from("")) + .unwrap(), + )); + let interceptor = TestInterceptor::default(); + let config = Config::builder() + .http_connector(conn) + .interceptor(interceptor.clone()) + .build(); + let client = Client::from_conf(config); + let err = dbg!(client.say_hello().greeting("hey there!").send().await) + .expect_err("the endpoint should be invalid since it has an exclamation mark in it"); + let err_fmt = format!("{}", DisplayErrorContext(err)); + assert!( + err_fmt.contains("endpoint prefix could not be built"), + "expected '{}' to contain 'endpoint prefix could not be built'", + err_fmt + ); + + assert!( + interceptor.called.load(Ordering::Relaxed) == 0, + "the interceptor should not have been called since endpoint resolution failed" + ); + + dbg!(client.say_hello().greeting("hello").send().await) + .expect("hello is a valid endpoint prefix"); + assert!( + interceptor.called.load(Ordering::Relaxed) == 1, + "the interceptor should have been called" + ); + assert_eq!( + "test123.hello.", + interceptor + .last_endpoint_prefix + .lock() + .unwrap() + .clone() + .unwrap() + .as_str() + ); + } + """, + "aws_smithy_client" to CargoDependency.smithyClient(clientCodegenContext.runtimeConfig) + .toDevDependency().withFeature("test-util").toType(), + ) + } + } + } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt index 28ffe74561..1d1cddeca4 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt @@ -8,138 +8,112 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import software.amazon.smithy.aws.traits.protocols.RestJson1Trait import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization -import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationGenerator -import software.amazon.smithy.rust.codegen.client.smithy.protocols.HttpBoundProtocolTraitImplGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest -import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.rustlang.escape -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.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.AdditionalPayloadContext -import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolPayloadGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport -import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol -import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory -import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap -import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.util.CommandError import software.amazon.smithy.rust.codegen.core.util.dq -import software.amazon.smithy.rust.codegen.core.util.outputShape import java.nio.file.Path - -private class TestProtocolPayloadGenerator(private val body: String) : ProtocolPayloadGenerator { - override fun payloadMetadata(operationShape: OperationShape, additionalPayloadContext: AdditionalPayloadContext) = - ProtocolPayloadGenerator.PayloadMetadata(takesOwnership = false) - - override fun generatePayload( - writer: RustWriter, - shapeName: String, - operationShape: OperationShape, - additionalPayloadContext: AdditionalPayloadContext, - ) { - writer.writeWithNoFormatting(body) - } -} - -private class TestProtocolTraitImplGenerator( - private val codegenContext: ClientCodegenContext, - private val correctResponse: String, -) : HttpBoundProtocolTraitImplGenerator(codegenContext, RestJson(codegenContext)) { - private val symbolProvider = codegenContext.symbolProvider - - override fun generateTraitImpls( - operationWriter: RustWriter, - operationShape: OperationShape, - customizations: List, - ) { - operationWriter.rustTemplate( - """ - impl #{parse_strict} for ${operationShape.id.name}{ - type Output = Result<#{Output}, #{Error}>; - fn parse(&self, _response: &#{Response}<#{Bytes}>) -> Self::Output { - ${operationWriter.escape(correctResponse)} - } +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType as RT + +private class TestServiceRuntimePluginCustomization( + private val context: ClientCodegenContext, + private val fakeRequestBuilder: String, + private val fakeRequestBody: String, +) : ServiceRuntimePluginCustomization() { + override fun section(section: ServiceRuntimePluginSection): Writable = writable { + if (section is ServiceRuntimePluginSection.RegisterRuntimeComponents) { + val rc = context.runtimeConfig + section.registerInterceptor(rc, this) { + rustTemplate( + """ + { + ##[derive(::std::fmt::Debug)] + struct TestInterceptor; + impl #{Interceptor} for TestInterceptor { + fn modify_before_retry_loop( + &self, + context: &mut #{BeforeTransmitInterceptorContextMut}<'_>, + _rc: &#{RuntimeComponents}, + _cfg: &mut #{ConfigBag}, + ) -> #{Result}<(), #{BoxError}> { + // Replace the serialized request + let mut fake_req = ::http::Request::builder() + $fakeRequestBuilder + .body(#{SdkBody}::from($fakeRequestBody)) + .expect("valid request"); + ::std::mem::swap( + context.request_mut(), + &mut fake_req, + ); + Ok(()) + } + } + + TestInterceptor + } + """, + *preludeScope, + "BeforeTransmitInterceptorContextMut" to RT.beforeTransmitInterceptorContextMut(rc), + "BoxError" to RT.boxError(rc), + "ConfigBag" to RT.configBag(rc), + "Interceptor" to RT.interceptor(rc), + "RuntimeComponents" to RT.runtimeComponents(rc), + "SdkBody" to RT.sdkBody(rc), + ) } - """, - "parse_strict" to RuntimeType.parseStrictResponse(codegenContext.runtimeConfig), - "Output" to symbolProvider.toSymbol(operationShape.outputShape(codegenContext.model)), - "Error" to symbolProvider.symbolForOperationError(operationShape), - "Response" to RuntimeType.HttpResponse, - "Bytes" to RuntimeType.Bytes, - ) - } -} - -private class TestProtocolMakeOperationGenerator( - codegenContext: CodegenContext, - protocol: Protocol, - body: String, - private val httpRequestBuilder: String, -) : MakeOperationGenerator( - codegenContext, - protocol, - TestProtocolPayloadGenerator(body), - public = true, - includeDefaultPayloadHeaders = true, -) { - override fun createHttpRequest(writer: RustWriter, operationShape: OperationShape) { - writer.rust("#T::new()", RuntimeType.HttpRequestBuilder) - writer.writeWithNoFormatting(httpRequestBuilder) + } } } -// A stubbed test protocol to do enable testing intentionally broken protocols -private class TestProtocolGenerator( - codegenContext: ClientCodegenContext, - protocol: Protocol, - httpRequestBuilder: String, - body: String, - correctResponse: String, -) : OperationGenerator( - codegenContext, - protocol, - TestProtocolMakeOperationGenerator(codegenContext, protocol, body, httpRequestBuilder), - TestProtocolPayloadGenerator(body), - TestProtocolTraitImplGenerator(codegenContext, correctResponse), -) - -private class TestProtocolFactory( - private val httpRequestBuilder: String, - private val body: String, - private val correctResponse: String, -) : ProtocolGeneratorFactory { - override fun protocol(codegenContext: ClientCodegenContext): Protocol = RestJson(codegenContext) - - override fun buildProtocolGenerator(codegenContext: ClientCodegenContext): OperationGenerator { - return TestProtocolGenerator( - codegenContext, - protocol(codegenContext), - httpRequestBuilder, - body, - correctResponse, - ) - } - - override fun support(): ProtocolSupport { - return ProtocolSupport( - requestSerialization = true, - requestBodySerialization = true, - responseDeserialization = true, - errorDeserialization = true, - requestDeserialization = false, - requestBodyDeserialization = false, - responseSerialization = false, - errorSerialization = false, - ) +private class TestOperationCustomization( + private val context: ClientCodegenContext, + private val fakeOutput: String, +) : OperationCustomization() { + override fun section(section: OperationSection): Writable = writable { + val rc = context.runtimeConfig + if (section is OperationSection.AdditionalRuntimePluginConfig) { + rustTemplate( + """ + // Override the default response deserializer with our fake output + ##[derive(::std::fmt::Debug)] + struct TestDeser; + impl #{ResponseDeserializer} for TestDeser { + fn deserialize_nonstreaming( + &self, + _response: &#{HttpResponse}, + ) -> #{Result}<#{Output}, #{OrchestratorError}<#{Error}>> { + let fake_out: #{Result}< + crate::operation::say_hello::SayHelloOutput, + crate::operation::say_hello::SayHelloError, + > = $fakeOutput; + fake_out + .map(|o| #{TypedBox}::new(o).erase()) + .map_err(|e| #{OrchestratorError}::operation(#{TypedBox}::new(e).erase_error())) + } + } + cfg.store_put(#{DynResponseDeserializer}::new(TestDeser)); + """, + *preludeScope, + "DynResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::DynResponseDeserializer"), + "Error" to RT.smithyRuntimeApi(rc).resolve("client::interceptors::context::Error"), + "HttpResponse" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::HttpResponse"), + "OrchestratorError" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::OrchestratorError"), + "Output" to RT.smithyRuntimeApi(rc).resolve("client::interceptors::context::Output"), + "ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::ResponseDeserializer"), + "TypedBox" to RT.smithyTypes(rc).resolve("type_erasure::TypedBox"), + ) + } } } @@ -226,22 +200,34 @@ class ProtocolTestGeneratorTest { * Returns the [Path] the service was generated at, suitable for running `cargo test` */ private fun testService( - httpRequestBuilder: String, - body: String = "${correctBody.dq()}.to_string()", - correctResponse: String = """Ok(crate::operation::say_hello::SayHelloOutput::builder().value("hey there!").build())""", + fakeRequestBuilder: String, + fakeRequestBody: String = "${correctBody.dq()}.to_string()", + fakeOutput: String = """Ok(crate::operation::say_hello::SayHelloOutput::builder().value("hey there!").build())""", ): Path { val codegenDecorator = object : ClientCodegenDecorator { override val name: String = "mock" override val order: Byte = 0 override fun classpathDiscoverable(): Boolean = false - override fun protocols( - serviceId: ShapeId, - currentProtocols: ProtocolMap, - ): ProtocolMap = - // Intentionally replace the builtin implementation of RestJson1 with our fake protocol - mapOf(RestJson1Trait.ID to TestProtocolFactory(httpRequestBuilder, body, correctResponse)) + + override fun serviceRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations + TestServiceRuntimePluginCustomization( + codegenContext, + fakeRequestBuilder, + fakeRequestBody, + ) + + override fun operationCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = baseCustomizations + TestOperationCustomization(codegenContext, fakeOutput) } - return clientIntegrationTest(model, additionalDecorators = listOf(codegenDecorator)) + return clientIntegrationTest( + model, + additionalDecorators = listOf(codegenDecorator), + ) } @Test @@ -264,7 +250,7 @@ class ProtocolTestGeneratorTest { .header("X-Greeting", "Hi") .method("POST") """, - correctResponse = "Ok(crate::operation::say_hello::SayHelloOutput::builder().build())", + fakeOutput = "Ok(crate::operation::say_hello::SayHelloOutput::builder().build())", ) } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt index c5b2470dd9..332e331cb2 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -6,10 +6,12 @@ package software.amazon.smithy.rust.codegen.client.smithy.protocols import org.junit.jupiter.api.Test +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest -import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel -import software.amazon.smithy.rust.codegen.core.testutil.integrationTest +import software.amazon.smithy.rust.codegen.core.util.lookup class AwsQueryCompatibleTest { @Test @@ -48,33 +50,41 @@ class AwsQueryCompatibleTest { } """.asSmithyModel() - clientIntegrationTest(model) { clientCodegenContext, rustCrate -> - val moduleName = clientCodegenContext.moduleUseName() - rustCrate.integrationTest("should_parse_code_and_type_fields") { - rust( + clientIntegrationTest(model) { context, rustCrate -> + val operation: OperationShape = context.model.lookup("test#SomeOperation") + rustCrate.withModule(context.symbolProvider.moduleForShape(operation)) { + rustTemplate( """ - ##[test] - fn should_parse_code_and_type_fields() { - use aws_smithy_http::response::ParseStrictResponse; - - let response = http::Response::builder() - .header( - "x-amzn-query-error", - http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), - ) - .status(400) - .body( - r##"{ - "__type": "com.amazonaws.sqs##QueueDoesNotExist", - "message": "Some user-visible message" - }"##, - ) - .unwrap(); - let some_operation = $moduleName::operation::some_operation::SomeOperation::new(); - let error = some_operation - .parse(&response.map(bytes::Bytes::from)) - .err() - .unwrap(); + ##[cfg(test)] + ##[#{tokio}::test] + async fn should_parse_code_and_type_fields() { + use #{smithy_client}::test_connection::infallible_connection_fn; + use aws_smithy_http::body::SdkBody; + + let response = |_: http::Request| { + http::Response::builder() + .header( + "x-amzn-query-error", + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), + ) + .status(400) + .body( + SdkBody::from( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"## + ) + ) + .unwrap() + }; + let client = crate::Client::from_conf( + crate::Config::builder() + .http_connector(infallible_connection_fn(response)) + .endpoint_url("http://localhost:1234") + .build() + ); + let error = dbg!(client.some_operation().send().await).err().unwrap().into_service_error(); assert_eq!( Some("AWS.SimpleQueueService.NonExistentQueue"), error.meta().code(), @@ -82,6 +92,9 @@ class AwsQueryCompatibleTest { assert_eq!(Some("Sender"), error.meta().extra("type")); } """, + "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) + .toDevDependency().withFeature("test-util").toType(), + "tokio" to CargoDependency.Tokio.toType(), ) } } @@ -118,33 +131,44 @@ class AwsQueryCompatibleTest { } """.asSmithyModel() - clientIntegrationTest(model) { clientCodegenContext, rustCrate -> - val moduleName = clientCodegenContext.moduleUseName() - rustCrate.integrationTest("should_parse_code_from_payload") { - rust( + clientIntegrationTest(model) { context, rustCrate -> + val operation: OperationShape = context.model.lookup("test#SomeOperation") + rustCrate.withModule(context.symbolProvider.moduleForShape(operation)) { + rustTemplate( """ - ##[test] - fn should_parse_code_from_payload() { - use aws_smithy_http::response::ParseStrictResponse; - - let response = http::Response::builder() - .status(400) - .body( - r##"{ - "__type": "com.amazonaws.sqs##QueueDoesNotExist", - "message": "Some user-visible message" - }"##, - ) - .unwrap(); - let some_operation = $moduleName::operation::some_operation::SomeOperation::new(); - let error = some_operation - .parse(&response.map(bytes::Bytes::from)) - .err() - .unwrap(); + ##[cfg(test)] + ##[#{tokio}::test] + async fn should_parse_code_from_payload() { + use #{smithy_client}::test_connection::infallible_connection_fn; + use aws_smithy_http::body::SdkBody; + + let response = |_: http::Request| { + http::Response::builder() + .status(400) + .body( + SdkBody::from( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"##, + ) + ) + .unwrap() + }; + let client = crate::Client::from_conf( + crate::Config::builder() + .http_connector(infallible_connection_fn(response)) + .endpoint_url("http://localhost:1234") + .build() + ); + let error = dbg!(client.some_operation().send().await).err().unwrap().into_service_error(); assert_eq!(Some("QueueDoesNotExist"), error.meta().code()); assert_eq!(None, error.meta().extra("type")); } """, + "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) + .toDevDependency().withFeature("test-util").toType(), + "tokio" to CargoDependency.Tokio.toType(), ) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt index fbd4ed69dd..d3b7bd7420 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt @@ -338,6 +338,8 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) smithyTypes(runtimeConfig).resolve("config_bag::ConfigBag") fun configBagAccessors(runtimeConfig: RuntimeConfig): RuntimeType = smithyRuntimeApi(runtimeConfig).resolve("client::config_bag_accessors::ConfigBagAccessors") + fun runtimeComponents(runtimeConfig: RuntimeConfig) = + smithyRuntimeApi(runtimeConfig).resolve("client::runtime_components::RuntimeComponents") fun runtimeComponentsBuilder(runtimeConfig: RuntimeConfig) = smithyRuntimeApi(runtimeConfig).resolve("client::runtime_components::RuntimeComponentsBuilder") fun runtimePlugins(runtimeConfig: RuntimeConfig): RuntimeType = From bd3b0a022684fd7167311a3653b76db5d2b3e61d Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 17 Jul 2023 17:35:21 -0700 Subject: [PATCH 06/17] Fix missing prelude scope --- .../software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt index fac47eede2..de6da933e3 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt @@ -37,6 +37,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.withBlock 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.contextName import software.amazon.smithy.rust.codegen.core.util.cloneOperation import software.amazon.smithy.rust.codegen.core.util.expectTrait @@ -370,6 +371,7 @@ class AwsPresignedFluentBuilderMethod( } } """, + *preludeScope, "AlternateSerializer" to alternateSerializer(operationShape), "ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig), "FrozenLayer" to smithyTypes.resolve("config_bag::FrozenLayer"), From 95adcff6abad3a4b274c2474d45bea44513394c2 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 15:23:54 -0700 Subject: [PATCH 07/17] Fix no-default-features test --- .../no-default-features/Cargo.toml | 1 + .../tests/client-construction.rs | 54 ++++++++++++++++--- .../HttpConnectorConfigDecorator.kt | 27 +++++++++- .../src/client/orchestrator.rs | 3 +- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/aws/sdk/integration-tests/no-default-features/Cargo.toml b/aws/sdk/integration-tests/no-default-features/Cargo.toml index 545837f66c..8408ac6694 100644 --- a/aws/sdk/integration-tests/no-default-features/Cargo.toml +++ b/aws/sdk/integration-tests/no-default-features/Cargo.toml @@ -17,6 +17,7 @@ publish = false aws-config = { path = "../../build/aws-sdk/sdk/aws-config", default-features = false } aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", default-features = false } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } +aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } futures = "0.3.25" tokio = { version = "1.23.1", features = ["full", "test-util"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs b/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs index 0ee5c7a5c3..ada59bc6f4 100644 --- a/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs +++ b/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs @@ -3,6 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_sdk_s3::config::{Config, Credentials, SharedAsyncSleep, Sleep}; +use aws_sdk_s3::error::DisplayErrorContext; +use aws_smithy_async::rt::sleep::AsyncSleep; +use std::time::Duration; + // This will fail due to lack of a connector when constructing the SDK Config #[tokio::test] #[should_panic( @@ -13,23 +18,58 @@ async fn test_clients_from_sdk_config() { } // This will fail due to lack of a connector when constructing the service client +#[cfg(not(aws_sdk_middleware_mode))] +#[tokio::test] +async fn test_clients_from_service_config() { + use aws_sdk_s3::config::Region; + + #[derive(Clone, Debug)] + struct StubSleep; + impl AsyncSleep for StubSleep { + fn sleep(&self, _duration: Duration) -> Sleep { + Sleep::new(Box::pin(async { /* no-op */ })) + } + } + + let config = Config::builder() + .region(Region::new("us-east-1")) + .credentials_provider(Credentials::for_tests()) + .sleep_impl(SharedAsyncSleep::new(StubSleep)) + .build(); + // Creating the client shouldn't panic or error since presigning doesn't require a connector + let client = aws_sdk_s3::Client::from_conf(config); + + let err = client + .list_buckets() + .send() + .await + .expect_err("it should fail to send a request because there is no connector"); + let msg = format!("{}", DisplayErrorContext(err)); + assert!( + msg.contains("No HTTP connector was available to send this request. Enable the `rustls` crate feature or set a connector to fix this."), + "expected '{msg}' to contain 'No HTTP connector was available to send this request. Enable the `rustls` crate feature or set a connector to fix this.'" + ); +} + +// TODO(enableNewSmithyRuntimeMode): Remove this test (covered above for orchestrator) +// +// This will fail due to lack of a connector when constructing the service client +#[cfg(aws_sdk_middleware_mode)] #[tokio::test] #[should_panic( expected = "No HTTP connector was available. Enable the `rustls` crate feature or set a connector to fix this." )] -async fn test_clients_from_service_config() { +async fn test_clients_from_service_config_middleware() { #[derive(Clone, Debug)] struct StubSleep; - impl aws_smithy_async::rt::sleep::AsyncSleep for StubSleep { - fn sleep(&self, _duration: std::time::Duration) -> aws_sdk_s3::config::Sleep { + impl AsyncSleep for StubSleep { + fn sleep(&self, _duration: Duration) -> Sleep { todo!() } } - let config = aws_sdk_s3::Config::builder() - .sleep_impl(aws_smithy_async::rt::sleep::SharedAsyncSleep::new( - StubSleep {}, - )) + let config = Config::builder() + .sleep_impl(SharedAsyncSleep::new(StubSleep {})) .build(); // This will panic due to the lack of an HTTP connector aws_sdk_s3::Client::from_conf(config); diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt index e2d71aeae7..b0ba6b1f24 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt @@ -41,7 +41,6 @@ private class HttpConnectorConfigCustomization( *preludeScope, "Connection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::Connection"), "ConnectorSettings" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::ConnectorSettings"), - "default_connector" to RuntimeType.smithyClient(runtimeConfig).resolve("conns::default_connector"), "DynConnectorAdapter" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::connectors::adapter::DynConnectorAdapter"), "HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"), "Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"), @@ -50,6 +49,31 @@ private class HttpConnectorConfigCustomization( "TimeoutConfig" to RuntimeType.smithyTypes(runtimeConfig).resolve("timeout::TimeoutConfig"), ) + private fun defaultConnectorFn(): RuntimeType = RuntimeType.forInlineFun("default_connector", ClientRustModule.config) { + rustTemplate( + """ + ##[cfg(feature = "rustls")] + fn default_connector( + connector_settings: &#{ConnectorSettings}, + sleep_impl: #{Option}<#{SharedAsyncSleep}>, + ) -> #{Option}<#{DynConnector}> { + #{default_connector}(connector_settings, sleep_impl) + } + + ##[cfg(not(feature = "rustls"))] + fn default_connector( + _connector_settings: &#{ConnectorSettings}, + _sleep_impl: #{Option}<#{SharedAsyncSleep}>, + ) -> #{Option}<#{DynConnector}> { + #{None} + } + """, + *codegenScope, + "default_connector" to RuntimeType.smithyClient(runtimeConfig).resolve("conns::default_connector"), + "DynConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("erase::DynConnector"), + ) + } + private fun setConnectorFn(): RuntimeType = RuntimeType.forInlineFun("set_connector", ClientRustModule.config) { rustTemplate( """ @@ -84,6 +108,7 @@ private class HttpConnectorConfigCustomization( } """, *codegenScope, + "default_connector" to defaultConnectorFn(), ) } diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 38b8852acb..30aa1666cb 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -335,7 +335,8 @@ async fn try_attempt( let request = ctx.take_request().expect("set during serialization"); trace!(request = ?request, "transmitting request"); let connector = halt_on_err!([ctx] => runtime_components.connector().ok_or_else(|| - OrchestratorError::other("a connector is required to send requests") + OrchestratorError::other("No HTTP connector was available to send this request. \ + Enable the `rustls` crate feature or set a connector to fix this.") )); connector.call(request).await.map_err(|err| { match err.downcast() { From f672e544f688b6b14a1ac37fad6cbaa662fc9c7f Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 15:37:28 -0700 Subject: [PATCH 08/17] Fix borrow checker errors in config --- .../software/amazon/smithy/rustsdk/CredentialCaches.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt index 41fad244ef..4f826ef35d 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt @@ -195,12 +195,8 @@ class CredentialCacheConfig(codegenContext: ClientCodegenContext) : ConfigCustom rustTemplate( """ match ( - layer - .load::<#{CredentialsCache}>() - .cloned(), - layer - .load::<#{SharedCredentialsProvider}>() - .cloned(), + resolver.config_mut().load::<#{CredentialsCache}>().cloned(), + resolver.config_mut().load::<#{SharedCredentialsProvider}>().cloned(), ) { (#{None}, #{None}) => {} (#{None}, _) => { @@ -213,7 +209,7 @@ class CredentialCacheConfig(codegenContext: ClientCodegenContext) : ConfigCustom #{Some}(credentials_cache), #{Some}(credentials_provider), ) => { - layer.store_put(credentials_cache.create_cache(credentials_provider)); + resolver.config_mut().store_put(credentials_cache.create_cache(credentials_provider)); } } """, From d8100f3340a6dc930045e0830b0cc6464962726e Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 15:48:44 -0700 Subject: [PATCH 09/17] Restore the middleware version of the protocol test generator test --- .../ProtocolTestGeneratorMiddlewareTest.kt | 363 ++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorMiddlewareTest.kt diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorMiddlewareTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorMiddlewareTest.kt new file mode 100644 index 0000000000..daea27a0b8 --- /dev/null +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorMiddlewareTest.kt @@ -0,0 +1,363 @@ +/* + * 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.protocol + +import io.kotest.matchers.string.shouldContain +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import software.amazon.smithy.aws.traits.protocols.RestJson1Trait +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationGenerator +import software.amazon.smithy.rust.codegen.client.smithy.protocols.HttpBoundProtocolTraitImplGenerator +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings +import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest +import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.escape +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.smithy.CodegenContext +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.AdditionalPayloadContext +import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolPayloadGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport +import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol +import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory +import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap +import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.util.CommandError +import software.amazon.smithy.rust.codegen.core.util.dq +import software.amazon.smithy.rust.codegen.core.util.outputShape +import java.nio.file.Path + +private class TestProtocolPayloadGenerator(private val body: String) : ProtocolPayloadGenerator { + override fun payloadMetadata(operationShape: OperationShape, additionalPayloadContext: AdditionalPayloadContext) = + ProtocolPayloadGenerator.PayloadMetadata(takesOwnership = false) + + override fun generatePayload( + writer: RustWriter, + shapeName: String, + operationShape: OperationShape, + additionalPayloadContext: AdditionalPayloadContext, + ) { + writer.writeWithNoFormatting(body) + } +} + +private class TestProtocolTraitImplGenerator( + private val codegenContext: ClientCodegenContext, + private val correctResponse: String, +) : HttpBoundProtocolTraitImplGenerator(codegenContext, RestJson(codegenContext)) { + private val symbolProvider = codegenContext.symbolProvider + + override fun generateTraitImpls( + operationWriter: RustWriter, + operationShape: OperationShape, + customizations: List, + ) { + operationWriter.rustTemplate( + """ + impl #{parse_strict} for ${operationShape.id.name}{ + type Output = Result<#{Output}, #{Error}>; + fn parse(&self, _response: &#{Response}<#{Bytes}>) -> Self::Output { + ${operationWriter.escape(correctResponse)} + } + } + """, + "parse_strict" to RuntimeType.parseStrictResponse(codegenContext.runtimeConfig), + "Output" to symbolProvider.toSymbol(operationShape.outputShape(codegenContext.model)), + "Error" to symbolProvider.symbolForOperationError(operationShape), + "Response" to RuntimeType.HttpResponse, + "Bytes" to RuntimeType.Bytes, + ) + } +} + +private class TestProtocolMakeOperationGenerator( + codegenContext: CodegenContext, + protocol: Protocol, + body: String, + private val httpRequestBuilder: String, +) : MakeOperationGenerator( + codegenContext, + protocol, + TestProtocolPayloadGenerator(body), + public = true, + includeDefaultPayloadHeaders = true, +) { + override fun createHttpRequest(writer: RustWriter, operationShape: OperationShape) { + writer.rust("#T::new()", RuntimeType.HttpRequestBuilder) + writer.writeWithNoFormatting(httpRequestBuilder) + } +} + +// A stubbed test protocol to do enable testing intentionally broken protocols +private class TestProtocolGenerator( + codegenContext: ClientCodegenContext, + protocol: Protocol, + httpRequestBuilder: String, + body: String, + correctResponse: String, +) : OperationGenerator( + codegenContext, + protocol, + TestProtocolMakeOperationGenerator(codegenContext, protocol, body, httpRequestBuilder), + TestProtocolPayloadGenerator(body), + TestProtocolTraitImplGenerator(codegenContext, correctResponse), +) + +private class TestProtocolFactory( + private val httpRequestBuilder: String, + private val body: String, + private val correctResponse: String, +) : ProtocolGeneratorFactory { + override fun protocol(codegenContext: ClientCodegenContext): Protocol = RestJson(codegenContext) + + override fun buildProtocolGenerator(codegenContext: ClientCodegenContext): OperationGenerator { + return TestProtocolGenerator( + codegenContext, + protocol(codegenContext), + httpRequestBuilder, + body, + correctResponse, + ) + } + + override fun support(): ProtocolSupport { + return ProtocolSupport( + requestSerialization = true, + requestBodySerialization = true, + responseDeserialization = true, + errorDeserialization = true, + requestDeserialization = false, + requestBodyDeserialization = false, + responseSerialization = false, + errorSerialization = false, + ) + } +} + +// TODO(enableNewSmithyRuntime): Delete this file/test (replaced by ProtocolTestGeneratorTest for the orchestrator) +class ProtocolTestGeneratorMiddlewareTest { + private val model = """ + namespace com.example + + use aws.protocols#restJson1 + use smithy.test#httpRequestTests + use smithy.test#httpResponseTests + + @restJson1 + service HelloService { + operations: [SayHello], + version: "1" + } + + @http(method: "POST", uri: "/") + @httpRequestTests([ + { + id: "say_hello", + protocol: restJson1, + params: { + "greeting": "Hi", + "name": "Teddy", + "query": "Hello there" + }, + method: "POST", + uri: "/", + queryParams: [ + "Hi=Hello%20there" + ], + forbidQueryParams: [ + "goodbye" + ], + requireQueryParams: ["required"], + headers: { + "X-Greeting": "Hi", + }, + body: "{\"name\": \"Teddy\"}", + bodyMediaType: "application/json" + } + ]) + @httpResponseTests([{ + id: "basic_response_test", + protocol: restJson1, + documentation: "Parses operations with empty JSON bodies", + body: "{\"value\": \"hey there!\"}", + params: {"value": "hey there!"}, + bodyMediaType: "application/json", + headers: {"Content-Type": "application/x-amz-json-1.1"}, + code: 200, + }]) + operation SayHello { + input: SayHelloInput, + output: SayHelloOutput, + errors: [BadRequest] + } + + structure SayHelloOutput { + value: String + } + + @error("client") + structure BadRequest { + message: String + } + + structure SayHelloInput { + @httpHeader("X-Greeting") + greeting: String, + + @httpQuery("Hi") + query: String, + + name: String + } + """.asSmithyModel() + private val correctBody = """{"name": "Teddy"}""" + + /** + * Creates a fake HTTP implementation for SayHello & generates the protocol test + * + * Returns the [Path] the service was generated at, suitable for running `cargo test` + */ + private fun testService( + httpRequestBuilder: String, + body: String = "${correctBody.dq()}.to_string()", + correctResponse: String = """Ok(crate::operation::say_hello::SayHelloOutput::builder().value("hey there!").build())""", + ): Path { + val codegenDecorator = object : ClientCodegenDecorator { + override val name: String = "mock" + override val order: Byte = 0 + override fun classpathDiscoverable(): Boolean = false + override fun protocols( + serviceId: ShapeId, + currentProtocols: ProtocolMap, + ): ProtocolMap = + // Intentionally replace the builtin implementation of RestJson1 with our fake protocol + mapOf(RestJson1Trait.ID to TestProtocolFactory(httpRequestBuilder, body, correctResponse)) + } + return clientIntegrationTest( + model, + TestCodegenSettings.middlewareModeTestParams, + additionalDecorators = listOf(codegenDecorator), + ) + } + + @Test + fun `passing e2e protocol request test`() { + testService( + """ + .uri("/?Hi=Hello%20there&required") + .header("X-Greeting", "Hi") + .method("POST") + """, + ) + } + + @Test + fun `test incorrect response parsing`() { + val err = assertThrows { + testService( + """ + .uri("/?Hi=Hello%20there&required") + .header("X-Greeting", "Hi") + .method("POST") + """, + correctResponse = "Ok(crate::operation::say_hello::SayHelloOutput::builder().build())", + ) + } + + err.message shouldContain "basic_response_test_response ... FAILED" + } + + @Test + fun `test invalid body`() { + val err = assertThrows { + testService( + """ + .uri("/?Hi=Hello%20there&required") + .header("X-Greeting", "Hi") + .method("POST") + """, + """"{}".to_string()""", + ) + } + + err.message shouldContain "say_hello_request ... FAILED" + err.message shouldContain "body did not match" + } + + @Test + fun `test invalid url parameter`() { + val err = assertThrows { + testService( + """ + .uri("/?Hi=INCORRECT&required") + .header("X-Greeting", "Hi") + .method("POST") + """, + ) + } + // Verify the test actually ran + err.message shouldContain "say_hello_request ... FAILED" + err.message shouldContain "missing query param" + } + + @Test + fun `test forbidden url parameter`() { + val err = assertThrows { + testService( + """ + .uri("/?goodbye&Hi=Hello%20there&required") + .header("X-Greeting", "Hi") + .method("POST") + """, + ) + } + // Verify the test actually ran + err.message shouldContain "say_hello_request ... FAILED" + err.message shouldContain "forbidden query param" + } + + @Test + fun `test required url parameter`() { + // Hard coded implementation for this 1 test + val err = assertThrows { + testService( + """ + .uri("/?Hi=Hello%20there") + .header("X-Greeting", "Hi") + .method("POST") + """, + ) + } + + // Verify the test actually ran + err.message shouldContain "say_hello_request ... FAILED" + err.message shouldContain "required query param missing" + } + + @Test + fun `invalid header`() { + val err = assertThrows { + testService( + """ + .uri("/?Hi=Hello%20there&required") + // should be "Hi" + .header("X-Greeting", "Hey") + .method("POST") + """, + ) + } + + err.message shouldContain "say_hello_request ... FAILED" + err.message shouldContain "invalid header value" + } +} From ec46e301812ea41c5b7ad3744ed18264d3db98de Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 15:55:56 -0700 Subject: [PATCH 10/17] Restore middleware versions of AwsQueryCompatibleTest --- .../protocols/AwsQueryCompatibleTest.kt | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt index 332e331cb2..e655777be7 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -7,10 +7,13 @@ package software.amazon.smithy.rust.codegen.client.smithy.protocols import org.junit.jupiter.api.Test import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.rust.codegen.client.testutil.TestCodegenSettings import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +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.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.util.lookup class AwsQueryCompatibleTest { @@ -173,4 +176,144 @@ class AwsQueryCompatibleTest { } } } + + // TODO(enableNewSmithyRuntimeCleanup): Delete this test (replaced above for orchestrator) + @Test + fun `middleware - aws-query-compatible json with aws query error should allow for retrieving error code and type from custom header`() { + val model = """ + namespace test + use aws.protocols#awsJson1_0 + use aws.protocols#awsQueryCompatible + use aws.protocols#awsQueryError + + @awsQueryCompatible + @awsJson1_0 + service TestService { + version: "2023-02-20", + operations: [SomeOperation] + } + + operation SomeOperation { + input: SomeOperationInputOutput, + output: SomeOperationInputOutput, + errors: [InvalidThingException], + } + + structure SomeOperationInputOutput { + a: String, + b: Integer + } + + @awsQueryError( + code: "InvalidThing", + httpResponseCode: 400, + ) + @error("client") + structure InvalidThingException { + message: String + } + """.asSmithyModel() + + clientIntegrationTest(model, TestCodegenSettings.middlewareModeTestParams) { clientCodegenContext, rustCrate -> + val moduleName = clientCodegenContext.moduleUseName() + rustCrate.integrationTest("should_parse_code_and_type_fields") { + rust( + """ + ##[test] + fn should_parse_code_and_type_fields() { + use aws_smithy_http::response::ParseStrictResponse; + + let response = http::Response::builder() + .header( + "x-amzn-query-error", + http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"), + ) + .status(400) + .body( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"##, + ) + .unwrap(); + let some_operation = $moduleName::operation::some_operation::SomeOperation::new(); + let error = some_operation + .parse(&response.map(bytes::Bytes::from)) + .err() + .unwrap(); + assert_eq!( + Some("AWS.SimpleQueueService.NonExistentQueue"), + error.meta().code(), + ); + assert_eq!(Some("Sender"), error.meta().extra("type")); + } + """, + ) + } + } + } + + // TODO(enableNewSmithyRuntimeCleanup): Delete this test (replaced above for orchestrator) + @Test + fun `middleware - aws-query-compatible json without aws query error should allow for retrieving error code from payload`() { + val model = """ + namespace test + use aws.protocols#awsJson1_0 + use aws.protocols#awsQueryCompatible + + @awsQueryCompatible + @awsJson1_0 + service TestService { + version: "2023-02-20", + operations: [SomeOperation] + } + + operation SomeOperation { + input: SomeOperationInputOutput, + output: SomeOperationInputOutput, + errors: [InvalidThingException], + } + + structure SomeOperationInputOutput { + a: String, + b: Integer + } + + @error("client") + structure InvalidThingException { + message: String + } + """.asSmithyModel() + + clientIntegrationTest(model, TestCodegenSettings.middlewareModeTestParams) { clientCodegenContext, rustCrate -> + val moduleName = clientCodegenContext.moduleUseName() + rustCrate.integrationTest("should_parse_code_from_payload") { + rust( + """ + ##[test] + fn should_parse_code_from_payload() { + use aws_smithy_http::response::ParseStrictResponse; + + let response = http::Response::builder() + .status(400) + .body( + r##"{ + "__type": "com.amazonaws.sqs##QueueDoesNotExist", + "message": "Some user-visible message" + }"##, + ) + .unwrap(); + let some_operation = $moduleName::operation::some_operation::SomeOperation::new(); + let error = some_operation + .parse(&response.map(bytes::Bytes::from)) + .err() + .unwrap(); + assert_eq!(Some("QueueDoesNotExist"), error.meta().code()); + assert_eq!(None, error.meta().extra("type")); + } + """, + ) + } + } + } } From f963eb46d4ad1dd856deb51b4754eab1d9bf87da Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 16:16:52 -0700 Subject: [PATCH 11/17] Fix aws-config test failure and improve error messages --- aws/rust-runtime/aws-config/src/connector.rs | 9 ++++++--- .../aws-config/src/http_credential_provider.rs | 5 ++++- aws/rust-runtime/aws-config/src/imds/client.rs | 5 ++++- aws/rust-runtime/aws-config/src/lib.rs | 2 +- aws/rust-runtime/aws-config/src/sso.rs | 1 + aws/rust-runtime/aws-config/src/sts.rs | 5 ++++- aws/rust-runtime/aws-config/src/sts/assume_role.rs | 5 ++++- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/connector.rs b/aws/rust-runtime/aws-config/src/connector.rs index c924a072bf..33c820261f 100644 --- a/aws/rust-runtime/aws-config/src/connector.rs +++ b/aws/rust-runtime/aws-config/src/connector.rs @@ -7,10 +7,13 @@ use aws_smithy_client::erase::DynConnector; -// unused when all crate features are disabled /// Unwrap an [`Option`](aws_smithy_client::erase::DynConnector), and panic with a helpful error message if it's `None` -pub(crate) fn expect_connector(connector: Option) -> DynConnector { - connector.expect("No HTTP connector was available. Enable the `rustls` crate feature or set a connector to fix this.") +pub(crate) fn expect_connector(for_what: &str, connector: Option) -> DynConnector { + if let Some(conn) = connector { + conn + } else { + panic!("{for_what} require(s) a HTTP connector, but none was available. Enable the `rustls` crate feature or set a connector to fix this.") + } } #[cfg(feature = "client-hyper")] diff --git a/aws/rust-runtime/aws-config/src/http_credential_provider.rs b/aws/rust-runtime/aws-config/src/http_credential_provider.rs index e868ffdac4..2568cc435d 100644 --- a/aws/rust-runtime/aws-config/src/http_credential_provider.rs +++ b/aws/rust-runtime/aws-config/src/http_credential_provider.rs @@ -100,7 +100,10 @@ impl Builder { .read_timeout(DEFAULT_READ_TIMEOUT) .build() }); - let connector = expect_connector(provider_config.connector(&connector_settings)); + let connector = expect_connector( + "The HTTP credentials provider", + provider_config.connector(&connector_settings), + ); let mut client_builder = aws_smithy_client::Client::builder() .connector(connector) .middleware(Identity::new()); diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index e73c6454ac..cb77b0759d 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -430,7 +430,10 @@ impl Builder { .read_timeout(self.read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT)) .build(); let connector_settings = ConnectorSettings::from_timeout_config(&timeout_config); - let connector = expect_connector(config.connector(&connector_settings)); + let connector = expect_connector( + "The IMDS credentials provider", + config.connector(&connector_settings), + ); let endpoint_source = self .endpoint .unwrap_or_else(|| EndpointSource::Env(config.clone())); diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 0da03ab2b4..154659377f 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -764,7 +764,7 @@ mod loader { assert_eq!(Some(&app_name), conf.app_name()); } - #[cfg(not(aws_sdk_middleware_mode))] + #[cfg(all(not(aws_sdk_middleware_mode), feature = "rustls"))] #[tokio::test] async fn disable_default_credentials() { let config = from_env().no_credentials().load().await; diff --git a/aws/rust-runtime/aws-config/src/sso.rs b/aws/rust-runtime/aws-config/src/sso.rs index a4b0314b1d..592ed5b6bc 100644 --- a/aws/rust-runtime/aws-config/src/sso.rs +++ b/aws/rust-runtime/aws-config/src/sso.rs @@ -65,6 +65,7 @@ impl SsoCredentialsProvider { let mut sso_config = SsoConfig::builder() .http_connector(expect_connector( + "The SSO credentials provider", provider_config.connector(&Default::default()), )) .retry_config(RetryConfig::standard()); diff --git a/aws/rust-runtime/aws-config/src/sts.rs b/aws/rust-runtime/aws-config/src/sts.rs index aba7a24bfc..028409bfbe 100644 --- a/aws/rust-runtime/aws-config/src/sts.rs +++ b/aws/rust-runtime/aws-config/src/sts.rs @@ -18,7 +18,10 @@ use aws_smithy_types::retry::RetryConfig; impl crate::provider_config::ProviderConfig { pub(crate) fn sts_client_config(&self) -> StsConfigBuilder { let mut builder = aws_sdk_sts::Config::builder() - .http_connector(expect_connector(self.connector(&Default::default()))) + .http_connector(expect_connector( + "The STS features of aws-config", + self.connector(&Default::default()), + )) .retry_config(RetryConfig::standard()) .region(self.region()) .time_source(self.time_source()); diff --git a/aws/rust-runtime/aws-config/src/sts/assume_role.rs b/aws/rust-runtime/aws-config/src/sts/assume_role.rs index 6ae8f0a6ad..50a83b4b6d 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -213,7 +213,10 @@ impl AssumeRoleProviderBuilder { .credentials_provider(provider) .time_source(conf.time_source()) .region(self.region.clone()) - .http_connector(expect_connector(conf.connector(&Default::default()))); + .http_connector(expect_connector( + "The AssumeRole credentials provider", + conf.connector(&Default::default()), + )); config.set_sleep_impl(conf.sleep()); let session_name = self.session_name.unwrap_or_else(|| { From 07180ebd511f39b0e2646197b83a0d4a5a379d21 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 16:31:49 -0700 Subject: [PATCH 12/17] Fix erroneous aws-sig-auth dependency --- .../amazon/smithy/rustsdk/IntegrationTestDependencies.kt | 4 ++-- .../software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt index a091664c1a..38c7d76a83 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt @@ -152,8 +152,8 @@ class S3TestDependencies(private val codegenContext: ClientCodegenContext) : Lib // TODO(enableNewSmithyRuntimeCleanup): These additional dependencies may not be needed anymore when removing this flag // depending on if the sra-test is kept around or not. if (codegenContext.smithyRuntimeMode.generateOrchestrator) { - addDependency(CargoDependency.smithyRuntime(codegenContext.runtimeConfig).toDevDependency()) - addDependency(CargoDependency.smithyRuntimeApi(codegenContext.runtimeConfig).toDevDependency()) + addDependency(smithyRuntime(codegenContext.runtimeConfig).toDevDependency()) + addDependency(smithyRuntimeApi(codegenContext.runtimeConfig).toDevDependency()) } } } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt index 0549ecf525..a34c89f6e5 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt @@ -93,7 +93,7 @@ class SigV4SigningConfig( override fun section(section: ServiceConfig): Writable = writable { when (section) { ServiceConfig.ConfigImpl -> { - if (serviceHasEventStream) { + if (runtimeMode.generateMiddleware && serviceHasEventStream) { // enable the aws-sig-auth `sign-eventstream` feature addDependency(AwsRuntimeType.awsSigAuthEventStream(runtimeConfig).toSymbol()) } From 72ee49903d91f73359a64871c0281f28f453a166 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 18 Jul 2023 17:31:42 -0700 Subject: [PATCH 13/17] Eliminate `getrandom` dependency to fix `wasm-unknown-unknown` --- aws/rust-runtime/aws-runtime/Cargo.toml | 3 +- .../aws-runtime/src/invocation_id.rs | 119 +++++++++++++----- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index aa6fdc1dde..56feac8f85 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -22,10 +22,11 @@ aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } aws-types = { path = "../aws-types" } +fastrand = "1" http = "0.2.3" percent-encoding = "2.1.0" tracing = "0.1" -uuid = { version = "1", features = ["v4", "fast-rng"] } +uuid = { version = "1" } [dev-dependencies] aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] } diff --git a/aws/rust-runtime/aws-runtime/src/invocation_id.rs b/aws/rust-runtime/aws-runtime/src/invocation_id.rs index 55cf9fd9e9..9b449fc3ed 100644 --- a/aws/rust-runtime/aws-runtime/src/invocation_id.rs +++ b/aws/rust-runtime/aws-runtime/src/invocation_id.rs @@ -9,9 +9,10 @@ use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace}; use http::{HeaderName, HeaderValue}; use std::fmt::Debug; -use uuid::Uuid; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use fastrand::Rng; +use std::sync::Mutex; #[cfg(feature = "test-util")] pub use test_util::{NoInvocationIdGenerator, PredefinedInvocationIdGenerator}; @@ -46,10 +47,43 @@ impl Storable for DynInvocationIdGenerator { type Storer = StoreReplace; } +/// An invocation ID generator that uses random UUIDs for the invocation ID. +#[derive(Debug, Default)] +pub struct DefaultInvocationIdGenerator { + rng: Mutex, +} + +impl DefaultInvocationIdGenerator { + /// Creates a new [`DefaultInvocationIdGenerator`]. + pub fn new() -> Self { + Default::default() + } + + /// Creates a [`DefaultInvocationIdGenerator`] with the given seed. + pub fn with_seed(seed: u64) -> Self { + Self { + rng: Mutex::new(Rng::with_seed(seed)), + } + } +} + +impl InvocationIdGenerator for DefaultInvocationIdGenerator { + fn generate(&self) -> Result, BoxError> { + let rng = self.rng.lock().unwrap(); + let mut random_bytes = [0u8; 16]; + rng.fill(&mut random_bytes); + + let id = uuid::Builder::from_random_bytes(random_bytes).into_uuid(); + Ok(Some(InvocationId::new(id.to_string()))) + } +} + /// This interceptor generates a UUID and attaches it to all request attempts made as part of this operation. #[non_exhaustive] #[derive(Debug, Default)] -pub struct InvocationIdInterceptor {} +pub struct InvocationIdInterceptor { + default: DefaultInvocationIdGenerator, +} impl InvocationIdInterceptor { /// Creates a new `InvocationIdInterceptor` @@ -65,13 +99,13 @@ impl Interceptor for InvocationIdInterceptor { _runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let id = cfg + let gen = cfg .load::() - .map(|gen| gen.generate()) - .transpose()? - .flatten(); - cfg.interceptor_state() - .store_put::(id.unwrap_or_default()); + .map(|gen| gen as &dyn InvocationIdGenerator) + .unwrap_or(&self.default); + if let Some(id) = gen.generate()? { + cfg.interceptor_state().store_put::(id); + } Ok(()) } @@ -83,10 +117,9 @@ impl Interceptor for InvocationIdInterceptor { cfg: &mut ConfigBag, ) -> Result<(), BoxError> { let headers = ctx.request_mut().headers_mut(); - let id = cfg - .load::() - .ok_or("Expected an InvocationId in the ConfigBag but none was present")?; - headers.append(AMZ_SDK_INVOCATION_ID, id.0.clone()); + if let Some(id) = cfg.load::() { + headers.append(AMZ_SDK_INVOCATION_ID, id.0.clone()); + } Ok(()) } } @@ -96,21 +129,15 @@ impl Interceptor for InvocationIdInterceptor { pub struct InvocationId(HeaderValue); impl InvocationId { - /// Create a new, random, invocation ID. - pub fn new() -> Self { - Self::default() - } -} - -/// Defaults to a random UUID. -impl Default for InvocationId { - fn default() -> Self { - let id = Uuid::new_v4(); - let id = id - .to_string() - .parse() - .expect("UUIDs always produce a valid header value"); - Self(id) + /// Create an invocation ID with the given value. + /// + /// # Panics + /// This constructor will panic if the given invocation ID is not a valid HTTP header value. + pub fn new(invocation_id: String) -> Self { + Self( + HeaderValue::try_from(invocation_id) + .expect("invocation ID must be a valid HTTP header value"), + ) } } @@ -181,14 +208,14 @@ mod test_util { #[cfg(test)] mod tests { - use crate::invocation_id::{InvocationId, InvocationIdInterceptor}; + use super::*; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime_api::client::interceptors::context::{ BeforeTransmitInterceptorContextMut, InterceptorContext, }; use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; - use aws_smithy_types::config_bag::ConfigBag; + use aws_smithy_types::config_bag::{ConfigBag, Layer}; use aws_smithy_types::type_erasure::TypeErasedBox; use http::HeaderValue; @@ -200,7 +227,7 @@ mod tests { } #[test] - fn test_id_is_generated_and_set() { + fn default_id_generator() { let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); let mut ctx = InterceptorContext::new(TypeErasedBox::doesnt_matter()); ctx.enter_serialization_phase(); @@ -224,4 +251,36 @@ mod tests { // UUID should include 32 chars and 4 dashes assert_eq!(header.len(), 36); } + + #[cfg(feature = "test-util")] + #[test] + fn custom_id_generator() { + let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); + let mut ctx = InterceptorContext::new(TypeErasedBox::doesnt_matter()); + ctx.enter_serialization_phase(); + ctx.set_request(http::Request::builder().body(SdkBody::empty()).unwrap()); + let _ = ctx.take_input(); + ctx.enter_before_transmit_phase(); + + let mut cfg = ConfigBag::base(); + let mut layer = Layer::new("test"); + layer.store_put(DynInvocationIdGenerator::new( + PredefinedInvocationIdGenerator::new(vec![InvocationId::new( + "the-best-invocation-id".into(), + )]), + )); + cfg.push_layer(layer); + + let interceptor = InvocationIdInterceptor::new(); + let mut ctx = Into::into(&mut ctx); + interceptor + .modify_before_retry_loop(&mut ctx, &rc, &mut cfg) + .unwrap(); + interceptor + .modify_before_transmit(&mut ctx, &rc, &mut cfg) + .unwrap(); + + let header = expect_header(&ctx, "amz-sdk-invocation-id"); + assert_eq!("the-best-invocation-id", header); + } } From 0b0d488942498b6ccc060e41bbd761e06818b370 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 19 Jul 2023 10:07:16 -0700 Subject: [PATCH 14/17] Fix `fastrand` versioning issue --- aws/rust-runtime/aws-config/Cargo.toml | 2 +- aws/rust-runtime/aws-credential-types/Cargo.toml | 2 +- aws/rust-runtime/aws-runtime/Cargo.toml | 2 +- aws/rust-runtime/aws-runtime/src/invocation_id.rs | 2 +- aws/sdk/integration-tests/s3/Cargo.toml | 2 +- .../amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt | 2 +- rust-runtime/aws-smithy-client/Cargo.toml | 2 +- rust-runtime/aws-smithy-runtime/Cargo.toml | 2 +- rust-runtime/inlineable/Cargo.toml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 54add78bb8..c19658d9c3 100644 --- a/aws/rust-runtime/aws-config/Cargo.toml +++ b/aws/rust-runtime/aws-config/Cargo.toml @@ -35,7 +35,7 @@ tokio = { version = "1.13.1", features = ["sync"] } tracing = { version = "0.1" } # implementation detail of IMDS credentials provider -fastrand = "1" +fastrand = "2.0.0" bytes = "1.1.0" http = "0.2.4" diff --git a/aws/rust-runtime/aws-credential-types/Cargo.toml b/aws/rust-runtime/aws-credential-types/Cargo.toml index 9970072b9e..cbd1426fe2 100644 --- a/aws/rust-runtime/aws-credential-types/Cargo.toml +++ b/aws/rust-runtime/aws-credential-types/Cargo.toml @@ -14,7 +14,7 @@ test-util = [] [dependencies] aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } -fastrand = "1.4.0" +fastrand = "2.0.0" tokio = { version = "1.23.1", features = ["sync"] } tracing = "0.1" zeroize = "1" diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index 56feac8f85..8ffcade9fc 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -22,7 +22,7 @@ aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } aws-types = { path = "../aws-types" } -fastrand = "1" +fastrand = "2.0.0" http = "0.2.3" percent-encoding = "2.1.0" tracing = "0.1" diff --git a/aws/rust-runtime/aws-runtime/src/invocation_id.rs b/aws/rust-runtime/aws-runtime/src/invocation_id.rs index 9b449fc3ed..3d2a2854ba 100644 --- a/aws/rust-runtime/aws-runtime/src/invocation_id.rs +++ b/aws/rust-runtime/aws-runtime/src/invocation_id.rs @@ -69,7 +69,7 @@ impl DefaultInvocationIdGenerator { impl InvocationIdGenerator for DefaultInvocationIdGenerator { fn generate(&self) -> Result, BoxError> { - let rng = self.rng.lock().unwrap(); + let mut rng = self.rng.lock().unwrap(); let mut random_bytes = [0u8; 16]; rng.fill(&mut random_bytes); diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index c504dba38d..010d73f659 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -26,7 +26,7 @@ aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } bytes = "1" bytes-utils = "0.1.2" -fastrand = "1.8.0" +fastrand = "2.0.0" futures-util = { version = "0.3.16", default-features = false } hdrhistogram = "7.5.2" http = "0.2.3" diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index 8dad31b91c..8b03d9656c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -226,7 +226,7 @@ data class CargoDependency( val Url: CargoDependency = CargoDependency("url", CratesIo("2.3.1")) val Bytes: CargoDependency = CargoDependency("bytes", CratesIo("1.0.0")) val BytesUtils: CargoDependency = CargoDependency("bytes-utils", CratesIo("0.1.0")) - val FastRand: CargoDependency = CargoDependency("fastrand", CratesIo("1.8.0")) + val FastRand: CargoDependency = CargoDependency("fastrand", CratesIo("2.0.0")) val Hex: CargoDependency = CargoDependency("hex", CratesIo("0.4.3")) val Http: CargoDependency = CargoDependency("http", CratesIo("0.2.9")) val HttpBody: CargoDependency = CargoDependency("http-body", CratesIo("0.4.4")) diff --git a/rust-runtime/aws-smithy-client/Cargo.toml b/rust-runtime/aws-smithy-client/Cargo.toml index 8d6ac25eaf..826ce5eb3d 100644 --- a/rust-runtime/aws-smithy-client/Cargo.toml +++ b/rust-runtime/aws-smithy-client/Cargo.toml @@ -23,7 +23,7 @@ aws-smithy-http-tower = { path = "../aws-smithy-http-tower" } aws-smithy-protocol-test = { path = "../aws-smithy-protocol-test", optional = true } aws-smithy-types = { path = "../aws-smithy-types" } bytes = "1" -fastrand = "1.4.0" +fastrand = "2.0.0" http = "0.2.3" http-body = "0.4.4" hyper = { version = "0.14.26", features = ["client", "http2", "http1", "tcp"], optional = true } diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index b481d7ff99..8f5dc864ee 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -21,7 +21,7 @@ aws-smithy-protocol-test = { path = "../aws-smithy-protocol-test", optional = tr aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" } aws-smithy-types = { path = "../aws-smithy-types" } bytes = "1" -fastrand = "1.4" +fastrand = "2.0.0" http = "0.2.8" http-body = "0.4.5" once_cell = "1.18.0" diff --git a/rust-runtime/inlineable/Cargo.toml b/rust-runtime/inlineable/Cargo.toml index 5e7351291f..74bbcdebc0 100644 --- a/rust-runtime/inlineable/Cargo.toml +++ b/rust-runtime/inlineable/Cargo.toml @@ -26,7 +26,7 @@ aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" } aws-smithy-types = { path = "../aws-smithy-types" } aws-smithy-xml = { path = "../aws-smithy-xml" } bytes = "1" -fastrand = "1" +fastrand = "2.0.0" futures-util = "0.3" http = "0.2.1" md-5 = "0.10.0" From acf7de5f5b5ef850aae0172e4c833488c79b408c Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 19 Jul 2023 10:27:46 -0700 Subject: [PATCH 15/17] Fix external types config --- aws/sdk/sdk-external-types.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws/sdk/sdk-external-types.toml b/aws/sdk/sdk-external-types.toml index d2ab675425..b484544c27 100644 --- a/aws/sdk/sdk-external-types.toml +++ b/aws/sdk/sdk-external-types.toml @@ -3,11 +3,13 @@ allowed_external_types = [ "aws_credential_types::*", "aws_endpoint::*", "aws_http::*", - "aws_sig_auth::*", + "aws_runtime::*", "aws_smithy_async::*", "aws_smithy_client::*", "aws_smithy_http::*", "aws_smithy_http_tower::*", + "aws_smithy_runtime::*", + "aws_smithy_runtime_api::*", "aws_smithy_types::*", "aws_types::*", "http::header::map::HeaderMap", From dc6976b02be49fda69ae0d2e5a2d32c6d38f2157 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 19 Jul 2023 10:39:57 -0700 Subject: [PATCH 16/17] Fix mutability error --- aws/rust-runtime/aws-config/src/imds/credentials.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 9cdc47b7c9..c0c5707f24 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -201,7 +201,7 @@ impl ImdsCredentialsProvider { return expiration; } - let rng = fastrand::Rng::with_seed( + let mut rng = fastrand::Rng::with_seed( now.duration_since(SystemTime::UNIX_EPOCH) .expect("now should be after UNIX EPOCH") .as_secs(), From 68d6a9922bf89bfc73843ce99d335864eb164fab Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 19 Jul 2023 12:18:51 -0700 Subject: [PATCH 17/17] Change default for SDK unit tests --- .../src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt index 0dabd0033e..ca5deedba8 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt @@ -40,7 +40,7 @@ fun awsTestCodegenContext(model: Model? = null, settings: ClientRustSettings? = // TODO(enableNewSmithyRuntimeCleanup): Remove defaultToOrchestrator once the runtime switches to the orchestrator fun awsSdkIntegrationTest( model: Model, - defaultToOrchestrator: Boolean = false, + defaultToOrchestrator: Boolean = true, test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> }, ) = clientIntegrationTest(