From 0af7920eba8d8d0dac79035c862e1dde1b377afc Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 26 Sep 2023 12:49:39 -0700 Subject: [PATCH 01/23] Replace `DynConnector` and `HttpConnector` with `HttpClient` This PR removes the last usages of `DynConnector` and `HttpClient` from the `aws_smithy_client` crate. --- aws/rust-runtime/aws-config/Cargo.toml | 15 +- .../aws-config/external-types.toml | 11 +- aws/rust-runtime/aws-config/src/connector.rs | 32 -- .../src/default_provider/app_name.rs | 10 +- .../src/default_provider/credentials.rs | 16 +- aws/rust-runtime/aws-config/src/ecs.rs | 286 ++++++++-------- .../src/http_credential_provider.rs | 136 ++++---- .../aws-config/src/imds/client.rs | 211 ++++++------ .../aws-config/src/imds/client/error.rs | 2 +- .../aws-config/src/imds/credentials.rs | 73 ++--- .../aws-config/src/imds/region.rs | 55 ++-- aws/rust-runtime/aws-config/src/lib.rs | 123 +++---- .../src/profile/credentials/exec.rs | 4 +- .../aws-config/src/profile/region.rs | 6 +- .../aws-config/src/provider_config.rs | 124 +++---- aws/rust-runtime/aws-config/src/sso.rs | 18 +- aws/rust-runtime/aws-config/src/sts.rs | 15 +- .../aws-config/src/sts/assume_role.rs | 48 +-- aws/rust-runtime/aws-config/src/test_case.rs | 51 +-- .../aws-config/src/web_identity_token.rs | 14 +- .../src/cache/lazy_caching.rs | 17 +- .../aws-inlineable/src/endpoint_discovery.rs | 4 +- .../aws-inlineable/src/s3_request_id.rs | 2 +- aws/rust-runtime/aws-types/Cargo.toml | 5 +- aws/rust-runtime/aws-types/src/lib.rs | 2 - aws/rust-runtime/aws-types/src/sdk_config.rs | 100 +++--- .../AwsCustomizableOperationDecorator.kt | 3 +- .../rustsdk/AwsFluentClientDecorator.kt | 5 +- .../amazon/smithy/rustsdk/CredentialCaches.kt | 2 +- .../smithy/rustsdk/SdkConfigDecorator.kt | 2 +- .../endpoints/OperationInputTestGenerator.kt | 13 +- .../rustsdk/EndpointBuiltInsDecoratorTest.kt | 30 +- .../rustsdk/EndpointsCredentialsTest.kt | 8 +- .../rustsdk/InvocationIdDecoratorTest.kt | 4 +- aws/sdk/integration-tests/dynamodb/Cargo.toml | 1 - .../dynamodb/tests/endpoints.rs | 5 +- .../dynamodb/tests/movies.rs | 53 +-- .../dynamodb/tests/paginators.rs | 158 ++++----- .../retries-with-client-rate-limiting.rs | 49 +-- .../dynamodb/tests/shared-config.rs | 5 +- .../dynamodb/tests/timeouts.rs | 14 +- aws/sdk/integration-tests/ec2/Cargo.toml | 5 +- .../integration-tests/ec2/tests/paginators.rs | 65 ++-- aws/sdk/integration-tests/glacier/Cargo.toml | 2 +- .../glacier/tests/custom-headers.rs | 14 +- aws/sdk/integration-tests/iam/Cargo.toml | 2 +- .../iam/tests/resolve-global-endpoint.rs | 6 +- aws/sdk/integration-tests/kms/Cargo.toml | 2 +- .../kms/tests/integration.rs | 45 +-- .../kms/tests/retryable_errors.rs | 6 +- aws/sdk/integration-tests/lambda/Cargo.toml | 4 +- .../lambda/tests/request_id.rs | 6 +- aws/sdk/integration-tests/polly/Cargo.toml | 1 - .../integration-tests/qldbsession/Cargo.toml | 3 +- .../qldbsession/tests/integration.rs | 15 +- aws/sdk/integration-tests/s3/Cargo.toml | 1 - .../s3/tests/alternative-async-runtime.rs | 12 +- .../s3/tests/bucket-required.rs | 6 +- .../integration-tests/s3/tests/checksums.rs | 31 +- .../s3/tests/config-override.rs | 6 +- .../s3/tests/customizable-operation.rs | 7 +- .../integration-tests/s3/tests/endpoints.rs | 6 +- .../s3/tests/ignore-invalid-xml-body-root.rs | 15 +- .../s3/tests/interceptors.rs | 11 +- .../s3/tests/naughty-string-metadata.rs | 6 +- aws/sdk/integration-tests/s3/tests/no_auth.rs | 31 +- .../s3/tests/normalize-uri-path.rs | 6 +- .../query-strings-are-correctly-encoded.rs | 6 +- .../integration-tests/s3/tests/reconnects.rs | 8 +- .../s3/tests/recursion-detection.rs | 6 +- .../integration-tests/s3/tests/request_id.rs | 22 +- .../s3/tests/request_information_headers.rs | 18 +- .../s3/tests/required-query-params.rs | 10 +- .../s3/tests/select-object-content.rs | 6 +- ...erride.rs => service_timeout_overrides.rs} | 41 --- .../integration-tests/s3/tests/signing-it.rs | 13 +- .../s3/tests/status-200-errors.rs | 7 +- .../integration-tests/s3/tests/timeouts.rs | 4 +- .../s3/tests/user-agent-app-name.rs | 6 +- .../integration-tests/s3control/Cargo.toml | 3 +- .../s3control/tests/signing-it.rs | 13 +- aws/sdk/integration-tests/sts/Cargo.toml | 2 +- .../integration-tests/sts/tests/signing-it.rs | 18 +- .../timestreamquery/Cargo.toml | 1 - .../timestreamquery/tests/endpoint_disco.rs | 3 +- .../transcribestreaming/Cargo.toml | 2 +- .../transcribestreaming/tests/test.rs | 8 +- .../integration-tests/webassembly/Cargo.toml | 3 +- .../webassembly/src/adapter/mod.rs | 44 --- .../webassembly/src/default_config.rs | 5 +- .../src/{adapter/http_client.rs => http.rs} | 33 ++ .../integration-tests/webassembly/src/lib.rs | 2 +- .../ConnectionPoisoningConfigCustomization.kt | 2 +- .../HttpConnectorConfigDecorator.kt | 139 ++------ .../ResiliencyConfigCustomization.kt | 5 +- .../customizations/TimeSourceCustomization.kt | 5 +- .../customize/RequiredCustomizations.kt | 8 +- .../client/FluentClientDecorator.kt | 2 +- .../client/FluentClientGenerator.kt | 3 + .../protocol/ProtocolTestGenerator.kt | 15 +- .../customizations/HttpAuthDecoratorTest.kt | 164 ++++++---- .../MetadataCustomizationTest.kt | 4 +- .../SensitiveOutputDecoratorTest.kt | 8 +- .../smithy/endpoint/EndpointsDecoratorTest.kt | 15 +- ...onfigOverrideRuntimePluginGeneratorTest.kt | 16 +- .../generators/EndpointTraitBindingsTest.kt | 10 +- .../CustomizableOperationGeneratorTest.kt | 11 +- .../client/FluentClientGeneratorTest.kt | 23 +- .../protocols/AwsQueryCompatibleTest.kt | 14 +- .../codegen/core/rustlang/CargoDependency.kt | 1 + .../rust/codegen/core/smithy/RuntimeType.kt | 4 +- examples/pokemon-service-common/Cargo.toml | 4 +- examples/pokemon-service-common/src/lib.rs | 6 +- .../tests/plugins_execution_order.rs | 6 +- examples/pokemon-service-tls/Cargo.toml | 2 +- .../pokemon-service-tls/tests/common/mod.rs | 6 +- .../aws-smithy-runtime-api/src/client.rs | 4 +- .../src/client/connectors.rs | 122 ------- .../aws-smithy-runtime-api/src/client/dns.rs | 78 +++++ .../aws-smithy-runtime-api/src/client/http.rs | 305 ++++++++++++++++++ .../src/client/runtime_components.rs | 64 ++-- .../src/client/runtime_plugin.rs | 21 +- .../aws-smithy-runtime-api/src/shared.rs | 12 +- rust-runtime/aws-smithy-runtime/Cargo.toml | 6 +- .../aws-smithy-runtime/external-types.toml | 6 +- rust-runtime/aws-smithy-runtime/src/client.rs | 10 +- .../src/client/connectors.rs | 52 --- .../src/client/connectors/test_util/never.rs | 42 --- .../aws-smithy-runtime/src/client/dns.rs | 45 +++ .../aws-smithy-runtime/src/client/http.rs | 38 +++ .../connection_poisoning.rs | 0 .../hyper_connector.rs => http/hyper_014.rs} | 265 +++++++++++++-- .../client/{connectors => http}/test_util.rs | 25 +- .../test_util/capture_request.rs | 20 +- .../{connectors => http}/test_util/dvr.rs | 12 +- .../test_util/dvr/record.rs | 36 ++- .../test_util/dvr/replay.rs | 38 ++- .../test_util/event.rs} | 86 ++++- .../test_util/infallible.rs | 39 ++- .../src/client/http/test_util/never.rs | 176 ++++++++++ .../src/client/orchestrator.rs | 25 +- .../src/client/orchestrator/operation.rs | 53 +-- .../aws-smithy-runtime/src/client/timeout.rs | 2 +- tools/ci-resources/tls-stub/Cargo.toml | 2 +- tools/ci-resources/tls-stub/src/main.rs | 7 +- 145 files changed, 2496 insertions(+), 1898 deletions(-) delete mode 100644 aws/rust-runtime/aws-config/src/connector.rs rename aws/sdk/integration-tests/s3/tests/{make-connector-override.rs => service_timeout_overrides.rs} (58%) delete mode 100644 aws/sdk/integration-tests/webassembly/src/adapter/mod.rs rename aws/sdk/integration-tests/webassembly/src/{adapter/http_client.rs => http.rs} (50%) delete mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs create mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/dns.rs create mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/http.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/dns.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http.rs rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/connection_poisoning.rs (100%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors/hyper_connector.rs => http/hyper_014.rs} (76%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util.rs (65%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/capture_request.rs (83%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr.rs (94%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr/record.rs (87%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr/replay.rs (91%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors/test_util/event_connector.rs => http/test_util/event.rs} (66%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/infallible.rs (51%) create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 2e9d00f08a..3c43472b1d 100644 --- a/aws/rust-runtime/aws-config/Cargo.toml +++ b/aws/rust-runtime/aws-config/Cargo.toml @@ -9,11 +9,10 @@ license = "Apache-2.0" repository = "https://github.com/awslabs/smithy-rs" [features] -client-hyper = ["aws-smithy-client/client-hyper", "aws-smithy-runtime/connector-hyper"] -rustls = ["aws-smithy-client/rustls", "client-hyper"] -native-tls = [] +client-hyper = ["aws-smithy-runtime/connector-hyper-0-14-x"] +rustls = ["aws-smithy-runtime/tls-rustls", "client-hyper"] allow-compilation = [] # our tests use `cargo test --all-features` and native-tls breaks CI -rt-tokio = ["aws-smithy-async/rt-tokio", "tokio/rt"] +rt-tokio = ["aws-smithy-async/rt-tokio", "aws-smithy-runtime/rt-tokio", "tokio/rt"] credentials-sso = ["dep:aws-sdk-sso", "dep:ring", "dep:hex", "dep:zeroize"] default = ["client-hyper", "rustls", "rt-tokio", "credentials-sso"] @@ -23,9 +22,7 @@ aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-type aws-http = { path = "../../sdk/build/aws-sdk/sdk/aws-http" } aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sdk/sts", default-features = false } aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" } -aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", default-features = false } aws-smithy-http = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-http" } -aws-smithy-http-tower = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-http-tower" } aws-smithy-json = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-json" } aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client"] } aws-smithy-runtime-api = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } @@ -42,7 +39,6 @@ fastrand = "2.0.0" bytes = "1.1.0" http = "0.2.4" -tower = { version = "0.4.8" } # implementation detail of SSO credential caching aws-sdk-sso = { path = "../../sdk/build/aws-sdk/sdk/sso", default-features = false, optional = true } @@ -51,7 +47,7 @@ hex = { version = "0.4.3", optional = true } zeroize = { version = "1", optional = true } [dev-dependencies] -aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } +aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x", "test-util"] } futures-util = { version = "0.3.16", default-features = false } tracing-test = "0.2.1" tracing-subscriber = { version = "0.3.16", features = ["fmt", "json"] } @@ -66,11 +62,10 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } -aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rt-tokio", "client-hyper"] } # used for a usage example hyper-rustls = { version = "0.24", features = ["webpki-tokio", "http2", "http1"] } -aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } +aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["rt-tokio", "test-util"] } [package.metadata.docs.rs] all-features = true diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index b90a84885c..38d857870c 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -9,17 +9,16 @@ allowed_external_types = [ "aws_credential_types::provider::SharedCredentialsProvider", "aws_sdk_sts::types::_policy_descriptor_type::PolicyDescriptorType", "aws_smithy_async::rt::sleep::AsyncSleep", + "aws_smithy_async::rt::sleep::SharedAsyncSleep", + "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", - "aws_smithy_client::bounds::SmithyConnector", - "aws_smithy_client::conns::default_connector::default_connector", - "aws_smithy_client::erase::DynConnector", - "aws_smithy_client::erase::boxclone::BoxCloneService", - "aws_smithy_client::http_connector::ConnectorSettings", - "aws_smithy_client::http_connector::HttpConnector", "aws_smithy_http::body::SdkBody", "aws_smithy_http::endpoint", "aws_smithy_http::endpoint::error::InvalidEndpointError", "aws_smithy_http::result::SdkError", + "aws_smithy_runtime_api::client::dns::SharedDnsResolver", + "aws_smithy_runtime_api::client::http::SharedHttpClient", + "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::retry", "aws_smithy_types::retry::*", "aws_smithy_types::timeout", diff --git a/aws/rust-runtime/aws-config/src/connector.rs b/aws/rust-runtime/aws-config/src/connector.rs deleted file mode 100644 index 33c820261f..0000000000 --- a/aws/rust-runtime/aws-config/src/connector.rs +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Functionality related to creating new HTTP Connectors - -use aws_smithy_client::erase::DynConnector; - -/// Unwrap an [`Option`](aws_smithy_client::erase::DynConnector), and panic with a helpful error message if it's `None` -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")] -pub use aws_smithy_client::conns::default_connector; - -#[cfg(all(feature = "native-tls", not(feature = "allow-compilation")))] -compile_error!("Feature native-tls has been removed. For upgrade instructions, see: https://awslabs.github.io/smithy-rs/design/transport/connector.html"); - -/// Given `ConnectorSettings` and a [`SharedAsyncSleep`](aws_smithy_async::rt::sleep::SharedAsyncSleep), create a `DynConnector` from defaults depending on what cargo features are activated. -#[cfg(not(feature = "client-hyper"))] -pub fn default_connector( - _settings: &aws_smithy_client::http_connector::ConnectorSettings, - _sleep: Option, -) -> Option { - None -} diff --git a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs index 041d0f8261..55e87f82bc 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs @@ -90,7 +90,7 @@ mod tests { use super::*; use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; use crate::provider_config::ProviderConfig; - use crate::test_case::{no_traffic_connector, InstantSleep}; + use crate::test_case::{no_traffic_client, InstantSleep}; use aws_types::os_shim_internal::{Env, Fs}; #[tokio::test] @@ -105,7 +105,7 @@ mod tests { &ProviderConfig::no_configuration() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; @@ -120,7 +120,7 @@ mod tests { let conf = crate::from_env() .sleep_impl(InstantSleep) .fs(fs) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) .profile_name("custom") .profile_files( ProfileFiles::builder() @@ -141,7 +141,7 @@ mod tests { &ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; @@ -158,7 +158,7 @@ mod tests { &ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; diff --git a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs index a5e1fb0dc5..82e3280dc1 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs @@ -226,7 +226,7 @@ mod test { /// make_test!(live: test_name) /// ``` macro_rules! make_test { - ($name: ident $(#[$m:meta])*) => { + ($name:ident $(#[$m:meta])*) => { make_test!($name, execute, $(#[$m])*); }; (update: $name:ident) => { @@ -235,13 +235,13 @@ mod test { (live: $name:ident) => { make_test!($name, execute_from_live_traffic); }; - ($name: ident, $func: ident, $(#[$m:meta])*) => { + ($name:ident, $func:ident, $(#[$m:meta])*) => { make_test!($name, $func, std::convert::identity $(, #[$m])*); }; - ($name: ident, builder: $provider_config_builder: expr) => { + ($name:ident, builder: $provider_config_builder:expr) => { make_test!($name, execute, $provider_config_builder); }; - ($name: ident, $func: ident, $provider_config_builder: expr $(, #[$m:meta])*) => { + ($name:ident, $func:ident, $provider_config_builder:expr $(, #[$m:meta])*) => { $(#[$m])* #[tokio::test] async fn $name() { @@ -332,14 +332,14 @@ mod test { use crate::provider_config::ProviderConfig; use aws_credential_types::provider::error::CredentialsError; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::boxclone::BoxCloneService; - use aws_smithy_client::never::NeverConnected; + use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; + use aws_smithy_runtime::client::http::test_util::NeverTcpConnector; tokio::time::pause(); let conf = ProviderConfig::no_configuration() - .with_tcp_connector(BoxCloneService::new(NeverConnected::new())) + .with_http_client(HyperClientBuilder::new().build(NeverTcpConnector::new())) .with_time_source(StaticTimeSource::new(UNIX_EPOCH)) - .with_sleep(TokioSleep::new()); + .with_sleep_impl(TokioSleep::new()); let provider = DefaultCredentialsChain::builder() .configure(conf) .build() diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index f5ca10e54c..6b4d6acf4b 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -46,24 +46,22 @@ //! } //! ``` -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::io; -use std::net::IpAddr; - +use crate::http_credential_provider::HttpCredentialProvider; +use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; -use aws_smithy_client::erase::boxclone::BoxCloneService; use aws_smithy_http::endpoint::apply_endpoint; +use aws_smithy_runtime_api::box_error::BoxError; +use aws_smithy_runtime_api::client::dns::{DnsResolver, SharedDnsResolver}; +use aws_smithy_runtime_api::client::http::HttpConnectorSettings; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; -use http::uri::{InvalidUri, PathAndQuery, Scheme}; -use http::{HeaderValue, Uri}; -use tower::{Service, ServiceExt}; - -use crate::http_credential_provider::HttpCredentialProvider; -use crate::provider_config::ProviderConfig; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_types::os_shim_internal::Env; use http::header::InvalidHeaderValue; +use http::uri::{InvalidUri, PathAndQuery, Scheme}; +use http::{HeaderValue, Uri}; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::net::IpAddr; use std::time::Duration; use tokio::sync::OnceCell; @@ -143,14 +141,14 @@ enum Provider { } impl Provider { - async fn uri(env: Env, dns: Option) -> Result { + async fn uri(env: Env, dns: Option) -> Result { let relative_uri = env.get(ENV_RELATIVE_URI).ok(); let full_uri = env.get(ENV_FULL_URI).ok(); if let Some(relative_uri) = relative_uri { Self::build_full_uri(relative_uri) } else if let Some(full_uri) = full_uri { - let mut dns = dns.or_else(tokio_dns); - validate_full_uri(&full_uri, dns.as_mut()) + let dns = dns.or_else(default_dns); + validate_full_uri(&full_uri, dns) .await .map_err(|err| EcsConfigurationError::InvalidFullUri { err, uri: full_uri }) } else { @@ -177,8 +175,8 @@ impl Provider { let http_provider = HttpCredentialProvider::builder() .configure(&provider_config) - .connector_settings( - ConnectorSettings::builder() + .http_connector_settings( + HttpConnectorSettings::builder() .connect_timeout(DEFAULT_CONNECT_TIMEOUT) .read_timeout(DEFAULT_READ_TIMEOUT) .build(), @@ -261,7 +259,7 @@ impl Error for EcsConfigurationError { #[derive(Default, Debug, Clone)] pub struct Builder { provider_config: Option, - dns: Option, + dns: Option, connect_timeout: Option, read_timeout: Option, } @@ -275,10 +273,10 @@ impl Builder { /// Override the DNS resolver used to validate URIs /// - /// URIs must refer to loopback addresses. The `DnsService` is used to retrieve IP addresses for - /// a given domain. - pub fn dns(mut self, dns: DnsService) -> Self { - self.dns = Some(dns); + /// URIs must refer to loopback addresses. The [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) + /// is used to retrieve IP addresses for a given domain. + pub fn dns(mut self, dns: impl IntoShared) -> Self { + self.dns = Some(dns.into_shared()); self } @@ -319,9 +317,9 @@ enum InvalidFullUriErrorKind { #[non_exhaustive] InvalidUri(InvalidUri), - /// No Dns service was provided + /// No Dns resolver was provided #[non_exhaustive] - NoDnsService, + NoDnsResolver, /// The URI did not specify a host #[non_exhaustive] @@ -332,7 +330,7 @@ enum InvalidFullUriErrorKind { NotLoopback, /// DNS lookup failed when attempting to resolve the host to an IP Address for validation. - DnsLookupFailed(io::Error), + DnsLookupFailed(BoxError), } /// Invalid Full URI @@ -358,7 +356,7 @@ impl Display for InvalidFullUriError { "failed to perform DNS lookup while validating URI" ) } - NoDnsService => write!(f, "no DNS service was provided. Enable `rt-tokio` or provide a `dns` service to the builder.") + NoDnsResolver => write!(f, "no DNS resolver was provided. Enable `rt-tokio` or provide a `dns` resolver to the builder.") } } } @@ -368,7 +366,7 @@ impl Error for InvalidFullUriError { use InvalidFullUriErrorKind::*; match &self.kind { InvalidUri(err) => Some(err), - DnsLookupFailed(err) => Some(err), + DnsLookupFailed(err) => Some(&**err as _), _ => None, } } @@ -380,9 +378,6 @@ impl From for InvalidFullUriError { } } -/// Dns resolver interface -pub type DnsService = BoxCloneService, io::Error>; - /// Validate that `uri` is valid to be used as a full provider URI /// Either: /// 1. The URL is uses `https` @@ -391,7 +386,7 @@ pub type DnsService = BoxCloneService, io::Error>; /// the credentials provider will return `CredentialsError::InvalidConfiguration` async fn validate_full_uri( uri: &str, - dns: Option<&mut DnsService>, + dns: Option, ) -> Result { let uri = uri .parse::() @@ -404,12 +399,11 @@ async fn validate_full_uri( let is_loopback = match host.parse::() { Ok(addr) => addr.is_loopback(), Err(_domain_name) => { - let dns = dns.ok_or(InvalidFullUriErrorKind::NoDnsService)?; - dns.ready().await.map_err(InvalidFullUriErrorKind::DnsLookupFailed)? - .call(host.to_owned()) - .await - .map_err(InvalidFullUriErrorKind::DnsLookupFailed)? - .iter() + let dns = dns.ok_or(InvalidFullUriErrorKind::NoDnsResolver)?; + dns.resolve_dns(host.to_owned()) + .await + .map_err(InvalidFullUriErrorKind::DnsLookupFailed)? + .iter() .all(|addr| { if !addr.is_loopback() { tracing::warn!( @@ -427,87 +421,52 @@ async fn validate_full_uri( } } +/// Default DNS resolver impl +/// +/// DNS resolution is required to validate that provided URIs point to the loopback interface #[cfg(any(not(feature = "rt-tokio"), target_family = "wasm"))] -fn tokio_dns() -> Option { +fn default_dns() -> Option { None } - -/// DNS resolver that uses tokio::spawn_blocking -/// -/// DNS resolution is required to validate that provided URIs point to the loopback interface #[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] -fn tokio_dns() -> Option { - use aws_smithy_client::erase::boxclone::BoxFuture; - use std::io::ErrorKind; - use std::net::ToSocketAddrs; - use std::task::{Context, Poll}; - - #[derive(Clone)] - struct TokioDns; - impl Service for TokioDns { - type Response = Vec; - type Error = io::Error; - type Future = BoxFuture; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: String) -> Self::Future { - Box::pin(async move { - let result = tokio::task::spawn_blocking(move || (req, 0).to_socket_addrs()).await; - match result { - Err(join_failure) => Err(io::Error::new(ErrorKind::Other, join_failure)), - Ok(Ok(dns_result)) => { - Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) - } - Ok(Err(dns_failure)) => Err(dns_failure), - } - }) - } - } - Some(BoxCloneService::new(TokioDns)) +fn default_dns() -> Option { + use aws_smithy_runtime::client::dns::TokioDnsResolver; + Some(TokioDnsResolver::new().into_shared()) } #[cfg(test)] mod test { - use aws_smithy_client::erase::boxclone::BoxCloneService; - use aws_smithy_client::never::NeverService; - use futures_util::FutureExt; - use http::Uri; - use serde::Deserialize; - use tracing_test::traced_test; - - use crate::ecs::{ - tokio_dns, validate_full_uri, Builder, EcsCredentialsProvider, InvalidFullUriError, - InvalidFullUriErrorKind, Provider, - }; + use super::*; use crate::provider_config::ProviderConfig; use crate::test_case::GenericTestResult; - use aws_credential_types::provider::ProvideCredentials; use aws_credential_types::Credentials; - use aws_types::os_shim_internal::Env; - + use aws_smithy_async::future::never::Never; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime_api::client::dns::DnsFuture; + use aws_smithy_runtime_api::shared::IntoShared; + use aws_types::os_shim_internal::Env; + use aws_types::sdk_config::SharedHttpClient; + use futures_util::FutureExt; use http::header::AUTHORIZATION; + use http::Uri; + use serde::Deserialize; use std::collections::HashMap; use std::error::Error; - use std::future::Ready; - use std::io; use std::net::IpAddr; - use std::task::{Context, Poll}; use std::time::{Duration, UNIX_EPOCH}; - use tower::Service; + use tracing_test::traced_test; - fn provider(env: Env, connector: DynConnector) -> EcsCredentialsProvider { + fn provider( + env: Env, + http_client: impl IntoShared, + ) -> EcsCredentialsProvider { let provider_config = ProviderConfig::empty() .with_env(env) - .with_http_connector(connector) - .with_sleep(TokioSleep::new()); + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()); Builder::default().configure(&provider_config).build() } @@ -520,7 +479,7 @@ mod test { impl EcsUriTest { async fn check(&self) { let env = Env::from(self.env.clone()); - let uri = Provider::uri(env, Some(BoxCloneService::new(TestDns::default()))) + let uri = Provider::uri(env, Some(TestDns::default().into_shared())) .await .map(|uri| uri.to_string()); self.result.assert_matches(uri); @@ -546,8 +505,7 @@ mod test { #[test] fn validate_uri_https() { // over HTTPs, any URI is fine - let never = NeverService::new(); - let mut dns = Some(BoxCloneService::new(never)); + let dns = Some(NeverDns.into_shared()); assert_eq!( validate_full_uri("https://amazon.com", None) .now_or_never() @@ -557,7 +515,7 @@ mod test { ); // over HTTP, it will try to lookup assert!( - validate_full_uri("http://amazon.com", dns.as_mut()) + validate_full_uri("http://amazon.com", dns) .now_or_never() .is_none(), "DNS lookup should occur, but it will never return" @@ -571,7 +529,7 @@ mod test { matches!( no_dns_error, InvalidFullUriError { - kind: InvalidFullUriErrorKind::NoDnsService + kind: InvalidFullUriErrorKind::NoDnsResolver } ), "expected no dns service, got: {}", @@ -603,12 +561,14 @@ mod test { #[test] fn all_addrs_local() { - let svc = TestDns::with_fallback(vec![ - "127.0.0.1".parse().unwrap(), - "127.0.0.2".parse().unwrap(), - ]); - let mut svc = Some(BoxCloneService::new(svc)); - let resp = validate_full_uri("http://localhost:8888", svc.as_mut()) + let dns = Some( + TestDns::with_fallback(vec![ + "127.0.0.1".parse().unwrap(), + "127.0.0.2".parse().unwrap(), + ]) + .into_shared(), + ); + let resp = validate_full_uri("http://localhost:8888", dns) .now_or_never() .unwrap(); assert!(resp.is_ok(), "Should be valid: {:?}", resp); @@ -616,12 +576,14 @@ mod test { #[test] fn all_addrs_not_local() { - let svc = TestDns::with_fallback(vec![ - "127.0.0.1".parse().unwrap(), - "192.168.0.1".parse().unwrap(), - ]); - let mut svc = Some(BoxCloneService::new(svc)); - let resp = validate_full_uri("http://localhost:8888", svc.as_mut()) + let dns = Some( + TestDns::with_fallback(vec![ + "127.0.0.1".parse().unwrap(), + "192.168.0.1".parse().unwrap(), + ]) + .into_shared(), + ); + let resp = validate_full_uri("http://localhost:8888", dns) .now_or_never() .unwrap(); assert!( @@ -675,37 +637,43 @@ mod test { ("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials"), ("AWS_CONTAINER_AUTHORIZATION_TOKEN", "Basic password"), ]); - let connector = TestConnection::new(vec![( - creds_request("http://169.254.170.2/credentials", Some("Basic password")), - ok_creds_response(), - )]); - let provider = provider(env, DynConnector::new(connector.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", Some("Basic password")), + ok_creds_response(), + )], + TokioSleep::new(), + ); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await .expect("valid credentials"); assert_correct(creds); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_5xx() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let connector = TestConnection::new(vec![ - ( - creds_request("http://169.254.170.2/credentials", None), - http::Response::builder() - .status(500) - .body(SdkBody::empty()) - .unwrap(), - ), - ( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - ), - ]); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), + ), + ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + ), + ], + TokioSleep::new(), + ); tokio::time::pause(); - let provider = provider(env, DynConnector::new(connector.clone())); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await @@ -716,17 +684,20 @@ mod test { #[tokio::test] async fn load_valid_creds_no_auth() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let connector = TestConnection::new(vec![( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - )]); - let provider = provider(env, DynConnector::new(connector.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + )], + TokioSleep::new(), + ); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await .expect("valid credentials"); assert_correct(creds); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } // ignored by default because it relies on actual DNS resolution @@ -735,8 +706,12 @@ mod test { #[traced_test] #[ignore] async fn real_dns_lookup() { - let mut dns = Some(tokio_dns().expect("feature must be enabled")); - let err = validate_full_uri("http://www.amazon.com/creds", dns.as_mut()) + let dns = Some( + default_dns() + .expect("feature must be enabled") + .into_shared(), + ); + let err = validate_full_uri("http://www.amazon.com/creds", dns.clone()) .await .expect_err("not a loopback"); assert!( @@ -752,13 +727,13 @@ mod test { assert!(logs_contain( "Address does not resolve to the loopback interface" )); - validate_full_uri("http://localhost:8888/creds", dns.as_mut()) + validate_full_uri("http://localhost:8888/creds", dns) .await .expect("localhost is the loopback interface"); } - /// TestService which always returns the same IP addresses - #[derive(Clone)] + /// Always returns the same IP addresses + #[derive(Clone, Debug)] struct TestDns { addrs: HashMap>, fallback: Vec, @@ -789,17 +764,20 @@ mod test { } } - impl Service for TestDns { - type Response = Vec; - type Error = io::Error; - type Future = Ready>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) + impl DnsResolver for TestDns { + fn resolve_dns(&self, name: String) -> DnsFuture { + DnsFuture::ready(Ok(self.addrs.get(&name).unwrap_or(&self.fallback).clone())) } + } - fn call(&mut self, _req: String) -> Self::Future { - std::future::ready(Ok(self.addrs.get(&_req).unwrap_or(&self.fallback).clone())) + #[derive(Debug)] + struct NeverDns; + impl DnsResolver for NeverDns { + fn resolve_dns(&self, _name: String) -> DnsFuture { + DnsFuture::new(async { + Never::new().await; + unreachable!() + }) } } } 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 87950ea17c..ea5fce0603 100644 --- a/aws/rust-runtime/aws-config/src/http_credential_provider.rs +++ b/aws/rust-runtime/aws-config/src/http_credential_provider.rs @@ -8,20 +8,17 @@ //! //! Future work will stabilize this interface and enable it to be used directly. -use crate::connector::expect_connector; use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials}; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError}; use aws_credential_types::Credentials; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::SdkError; -use aws_smithy_runtime::client::connectors::adapter::DynConnectorAdapter; use aws_smithy_runtime::client::orchestrator::operation::Operation; use aws_smithy_runtime::client::retries::classifier::{ HttpStatusCodeClassifier, SmithyErrorClassifier, }; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; +use aws_smithy_runtime_api::client::http::HttpConnectorSettings; use aws_smithy_runtime_api::client::interceptors::context::{Error, InterceptorContext}; use aws_smithy_runtime_api::client::orchestrator::{ HttpResponse, OrchestratorError, SensitiveOutput, @@ -30,6 +27,7 @@ use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryClassifiers, R use aws_smithy_runtime_api::client::runtime_plugin::StaticRuntimePlugin; use aws_smithy_types::config_bag::Layer; use aws_smithy_types::retry::{ErrorKind, RetryConfig}; +use aws_smithy_types::timeout::TimeoutConfig; use http::header::{ACCEPT, AUTHORIZATION}; use http::{HeaderValue, Response}; use std::time::Duration; @@ -65,7 +63,7 @@ impl HttpCredentialProvider { #[derive(Default)] pub(crate) struct Builder { provider_config: Option, - connector_settings: Option, + http_connector_settings: Option, } impl Builder { @@ -74,8 +72,11 @@ impl Builder { self } - pub(crate) fn connector_settings(mut self, connector_settings: ConnectorSettings) -> Self { - self.connector_settings = Some(connector_settings); + pub(crate) fn http_connector_settings( + mut self, + http_connector_settings: HttpConnectorSettings, + ) -> Self { + self.http_connector_settings = Some(http_connector_settings); self } @@ -86,16 +87,6 @@ impl Builder { path: impl Into, ) -> HttpCredentialProvider { let provider_config = self.provider_config.unwrap_or_default(); - let connector_settings = self.connector_settings.unwrap_or_else(|| { - ConnectorSettings::builder() - .connect_timeout(DEFAULT_CONNECT_TIMEOUT) - .read_timeout(DEFAULT_READ_TIMEOUT) - .build() - }); - let connector = expect_connector( - "The HTTP credentials provider", - provider_config.connector(&connector_settings), - ); // The following errors are retryable: // - Socket errors @@ -112,17 +103,23 @@ impl Builder { let mut builder = Operation::builder() .service_name("HttpCredentialProvider") .operation_name("LoadCredentials") - .http_connector(SharedHttpConnector::new(DynConnectorAdapter::new( - connector, - ))) .endpoint_url(endpoint) .no_auth() + .timeout_config( + TimeoutConfig::builder() + .connect_timeout(DEFAULT_CONNECT_TIMEOUT) + .read_timeout(DEFAULT_READ_TIMEOUT) + .build(), + ) .runtime_plugin(StaticRuntimePlugin::new().with_config({ let mut layer = Layer::new("SensitiveOutput"); layer.store_put(SensitiveOutput); layer.freeze() })); - if let Some(sleep_impl) = provider_config.sleep() { + if let Some(http_client) = provider_config.http_client() { + builder = builder.http_client(http_client); + } + if let Some(sleep_impl) = provider_config.sleep_impl() { builder = builder .standard_retry(&RetryConfig::standard()) .retry_classifiers(retry_classifiers) @@ -220,24 +217,22 @@ impl ClassifyRetry for HttpCredentialRetryClassifier { mod test { use super::*; use aws_credential_types::provider::error::CredentialsError; - use aws_smithy_client::test_connection::TestConnection; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime_api::client::orchestrator::HttpRequest; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use http::{Request, Response, Uri}; use std::time::SystemTime; - async fn provide_creds( - connector: TestConnection, - ) -> Result { - let provider_config = ProviderConfig::default().with_http_connector(connector.clone()); + async fn provide_creds(http_client: EventClient) -> Result { + let provider_config = ProviderConfig::default().with_http_client(http_client.clone()); let provider = HttpCredentialProvider::builder() .configure(&provider_config) .build("test", "http://localhost:1234/", "/some-creds"); provider.credentials(None).await } - fn successful_req_resp() -> (HttpRequest, HttpResponse) { - ( + fn successful_req_resp() -> ConnectionEvent { + ConnectionEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -258,8 +253,8 @@ mod test { #[tokio::test] async fn successful_response() { - let connector = TestConnection::new(vec![successful_req_resp()]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new(vec![successful_req_resp()], TokioSleep::new()); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); assert_eq!("/7PC5om....", creds.secret_access_key()); assert_eq!(Some("AQoDY....="), creds.session_token()); @@ -267,52 +262,59 @@ mod test { Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1456380211)), creds.expiry() ); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_nonparseable_response() { - let connector = TestConnection::new(vec![ - ( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(200) - .body(SdkBody::from(r#"not json"#)) - .unwrap(), - ), - successful_req_resp(), - ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(200) + .body(SdkBody::from(r#"not json"#)) + .unwrap(), + ), + successful_req_resp(), + ], + TokioSleep::new(), + ); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_error_code() { - let connector = TestConnection::new(vec![ - ( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(500) - .body(SdkBody::from(r#"it broke"#)) - .unwrap(), - ), - successful_req_resp(), - ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(500) + .body(SdkBody::from(r#"it broke"#)) + .unwrap(), + ), + successful_req_resp(), + ], + TokioSleep::new(), + ); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn explicit_error_not_retriable() { - let connector = TestConnection::new(vec![( + let http_client = EventClient::new( + vec![ConnectionEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -323,14 +325,16 @@ mod test { r#"{ "Code": "Error", "Message": "There was a problem, it was your fault" }"#, )) .unwrap(), - )]); - let err = provide_creds(connector.clone()) + )], + TokioSleep::new(), + ); + let err = provide_creds(http_client.clone()) .await .expect_err("it should fail"); assert!( matches!(err, CredentialsError::ProviderError { .. }), "should be CredentialsError::ProviderError: {err}", ); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } } diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index ed76fc8660..245c020118 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -7,7 +7,6 @@ //! //! Client for direct access to IMDSv2. -use crate::connector::expect_connector; use crate::imds::client::error::{BuildError, ImdsError, InnerImdsError, InvalidEndpointMode}; use crate::imds::client::token::TokenRuntimePlugin; use crate::provider_config::ProviderConfig; @@ -16,27 +15,18 @@ use aws_http::user_agent::{ApiMetadata, AwsUserAgent}; use aws_runtime::user_agent::UserAgentInterceptor; use aws_smithy_async::rt::sleep::SharedAsyncSleep; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::http_connector::ConnectorSettings; -use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; -use aws_smithy_runtime::client::connectors::adapter::DynConnectorAdapter; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime::client::orchestrator::operation::Operation; use aws_smithy_runtime::client::retries::strategy::StandardRetryStrategy; use aws_smithy_runtime_api::client::auth::AuthSchemeOptionResolverParams; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; -use aws_smithy_runtime_api::client::endpoint::{ - EndpointResolver, EndpointResolverParams, SharedEndpointResolver, -}; +use aws_smithy_runtime_api::client::endpoint::{EndpointResolver, EndpointResolverParams}; use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext; -use aws_smithy_runtime_api::client::interceptors::SharedInterceptor; use aws_smithy_runtime_api::client::orchestrator::{ Future, HttpResponse, OrchestratorError, SensitiveOutput, }; -use aws_smithy_runtime_api::client::retries::{ - ClassifyRetry, RetryClassifiers, RetryReason, SharedRetryStrategy, -}; +use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryClassifiers, RetryReason}; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin}; use aws_smithy_types::config_bag::{FrozenLayer, Layer}; @@ -44,6 +34,7 @@ use aws_smithy_types::endpoint::Endpoint; use aws_smithy_types::retry::{ErrorKind, RetryConfig}; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::os_shim_internal::Env; +use aws_types::sdk_config::SharedHttpClient; use http::Uri; use std::borrow::Cow; use std::error::Error as _; @@ -244,7 +235,7 @@ struct ImdsCommonRuntimePlugin { impl ImdsCommonRuntimePlugin { fn new( - connector: DynConnector, + http_client: Option, endpoint_resolver: ImdsEndpointResolver, retry_config: &RetryConfig, timeout_config: TimeoutConfig, @@ -261,17 +252,13 @@ impl ImdsCommonRuntimePlugin { Self { config: layer.freeze(), components: RuntimeComponentsBuilder::new("ImdsCommonRuntimePlugin") - .with_http_connector(Some(SharedHttpConnector::new(DynConnectorAdapter::new( - connector, - )))) - .with_endpoint_resolver(Some(SharedEndpointResolver::new(endpoint_resolver))) - .with_interceptor(SharedInterceptor::new(UserAgentInterceptor::new())) + .with_http_client(http_client) + .with_endpoint_resolver(Some(endpoint_resolver)) + .with_interceptor(UserAgentInterceptor::new()) .with_retry_classifiers(Some( RetryClassifiers::new().with_classifier(ImdsResponseRetryClassifier), )) - .with_retry_strategy(Some(SharedRetryStrategy::new(StandardRetryStrategy::new( - retry_config, - )))) + .with_retry_strategy(Some(StandardRetryStrategy::new(retry_config))) .with_time_source(Some(time_source)) .with_sleep_impl(sleep_impl), } @@ -427,11 +414,6 @@ impl Builder { .connect_timeout(self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT)) .read_timeout(self.read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT)) .build(); - let connector_settings = ConnectorSettings::from_timeout_config(&timeout_config); - let connector = expect_connector( - "The IMDS credentials provider", - config.connector(&connector_settings), - ); let endpoint_source = self .endpoint .unwrap_or_else(|| EndpointSource::Env(config.clone())); @@ -442,12 +424,12 @@ impl Builder { let retry_config = RetryConfig::standard() .with_max_attempts(self.max_attempts.unwrap_or(DEFAULT_ATTEMPTS)); let common_plugin = SharedRuntimePlugin::new(ImdsCommonRuntimePlugin::new( - connector, + config.http_client(), endpoint_resolver, &retry_config, timeout_config, config.time_source(), - config.sleep(), + config.sleep_impl(), )); let operation = Operation::builder() .service_name("imds") @@ -608,15 +590,18 @@ pub(crate) mod test { use crate::provider_config::ProviderConfig; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; + use aws_smithy_runtime::client::http::test_util::{ + capture_request, ConnectionEvent, EventClient, + }; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_runtime_api::client::interceptors::context::{ Input, InterceptorContext, Output, }; - use aws_smithy_runtime_api::client::orchestrator::OrchestratorError; + use aws_smithy_runtime_api::client::orchestrator::{ + HttpRequest, HttpResponse, OrchestratorError, + }; use aws_smithy_runtime_api::client::retries::ClassifyRetry; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; @@ -647,7 +632,7 @@ pub(crate) mod test { const TOKEN_A: &str = "AQAEAFTNrA4eEGx0AQgJ1arIq_Cc-t4tWt3fB0Hd8RKhXlKc5ccvhg=="; const TOKEN_B: &str = "alternatetoken=="; - pub(crate) fn token_request(base: &str, ttl: u32) -> http::Request { + pub(crate) fn token_request(base: &str, ttl: u32) -> HttpRequest { http::Request::builder() .uri(format!("{}/latest/api/token", base)) .header("x-aws-ec2-metadata-token-ttl-seconds", ttl) @@ -656,15 +641,15 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn token_response(ttl: u32, token: &'static str) -> http::Response<&'static str> { + pub(crate) fn token_response(ttl: u32, token: &'static str) -> HttpResponse { http::Response::builder() .status(200) .header("X-aws-ec2-metadata-token-ttl-seconds", ttl) - .body(token) + .body(SdkBody::from(token)) .unwrap() } - pub(crate) fn imds_request(path: &'static str, token: &str) -> http::Request { + pub(crate) fn imds_request(path: &'static str, token: &str) -> HttpRequest { http::Request::builder() .uri(Uri::from_static(path)) .method("GET") @@ -673,67 +658,71 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn imds_response(body: &'static str) -> http::Response<&'static str> { - http::Response::builder().status(200).body(body).unwrap() + pub(crate) fn imds_response(body: &'static str) -> HttpResponse { + http::Response::builder() + .status(200) + .body(SdkBody::from(body)) + .unwrap() } - pub(crate) fn make_client(conn: &TestConnection) -> super::Client - where - SdkBody: From, - T: Send + 'static, - { + pub(crate) fn make_client(http_client: &EventClient) -> super::Client { tokio::time::pause(); super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_sleep(TokioSleep::new()) - .with_http_connector(DynConnector::new(conn.clone())), + .with_sleep_impl(TokioSleep::new()) + .with_http_client(http_client.clone()), ) .build() } + fn event_client(events: Vec) -> (Client, EventClient) { + let http_client = EventClient::new(events, TokioSleep::new()); + let client = make_client(&http_client); + (client, http_client) + } + #[tokio::test] async fn client_caches_token() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata2", TOKEN_A), imds_response("output2"), ), ]); - let client = make_client(&connection); // load once let metadata = client.get("/latest/metadata").await.expect("failed"); assert_eq!("test-imds-output", metadata.as_ref()); // load again: the cached token should be used let metadata = client.get("/latest/metadata2").await.expect("failed"); assert_eq!("output2", metadata.as_ref()); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn token_can_expire() { - let connection = TestConnection::new(vec![ - ( + let (_, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), - ( + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output2"#), ), @@ -742,9 +731,9 @@ pub(crate) mod test { let client = super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) + .with_http_client(http_client.clone()) .with_time_source(time_source.clone()) - .with_sleep(sleep), + .with_sleep_impl(sleep), ) .endpoint_mode(EndpointMode::IpV6) .token_ttl(Duration::from_secs(600)) @@ -754,7 +743,7 @@ pub(crate) mod test { // now the cached credential has expired time_source.advance(Duration::from_secs(600)); let resp2 = client.get("/latest/metadata").await.expect("success"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); assert_eq!("test-imds-output1", resp1.as_ref()); assert_eq!("test-imds-output2", resp2.as_ref()); } @@ -762,27 +751,27 @@ pub(crate) mod test { /// Tokens are refreshed up to 120 seconds early to avoid using an expired token. #[tokio::test] async fn token_refresh_buffer() { - let connection = TestConnection::new(vec![ - ( + let (_, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), // t = 0 - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), // t = 400 (no refresh) - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output2"#), ), // t = 550 (within buffer) - ( + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output3"#), ), @@ -791,8 +780,8 @@ pub(crate) mod test { let client = super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_sleep(sleep) - .with_http_connector(DynConnector::new(connection.clone())) + .with_sleep_impl(sleep) + .with_http_client(http_client.clone()) .with_time_source(time_source.clone()), ) .endpoint_mode(EndpointMode::IpV6) @@ -805,7 +794,7 @@ pub(crate) mod test { let resp2 = client.get("/latest/metadata").await.expect("success"); time_source.advance(Duration::from_secs(150)); let resp3 = client.get("/latest/metadata").await.expect("success"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); assert_eq!("test-imds-output1", resp1.as_ref()); assert_eq!("test-imds-output2", resp2.as_ref()); assert_eq!("test-imds-output3", resp3.as_ref()); @@ -815,21 +804,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_500() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -838,11 +829,11 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // all requests should have a user agent header - for request in connection.requests().iter() { - assert!(request.actual.headers().get(USER_AGENT).is_some()); + for request in http_client.actual_requests() { + assert!(request.headers().get(USER_AGENT).is_some()); } } @@ -850,21 +841,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_token_failure() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -873,32 +866,34 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// 401 error during metadata retrieval must be retried #[tokio::test] #[traced_test] async fn retry_metadata_401() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(0, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), - http::Response::builder().status(401).body("").unwrap(), + http::Response::builder() + .status(401) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_B), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -907,21 +902,23 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// 403 responses from IMDS during token acquisition MUST NOT be retried #[tokio::test] #[traced_test] async fn no_403_retry() { - let connection = TestConnection::new(vec![( + let (client, http_client) = event_client(vec![ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - http::Response::builder().status(403).body("").unwrap(), + http::Response::builder() + .status(403) + .body(SdkBody::empty()) + .unwrap(), )]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "forbidden"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// Successful responses should classify as `RetryKind::Unnecessary` @@ -944,24 +941,23 @@ pub(crate) mod test { // since tokens are sent as headers, the tokens need to be valid header values #[tokio::test] async fn invalid_token() { - let connection = TestConnection::new(vec![( + let (client, http_client) = event_client(vec![ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - token_response(21600, "replaced").map(|_| vec![1, 0]), + token_response(21600, "invalid\nheader\nvalue\0"), )]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "invalid token"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn non_utf8_response() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A).map(SdkBody::from), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(200) @@ -969,10 +965,9 @@ pub(crate) mod test { .unwrap(), ), ]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "invalid UTF-8"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// Verify that the end-to-end real client has a 1-second connect timeout @@ -1042,12 +1037,12 @@ pub(crate) mod test { } async fn check(test_case: ImdsConfigTest) { - let (server, watcher) = capture_request(None); + let (http_client, watcher) = capture_request(None); let provider_config = ProviderConfig::no_configuration() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_env(Env::from(test_case.env)) .with_fs(Fs::from_map(test_case.fs)) - .with_http_connector(DynConnector::new(server)); + .with_http_client(http_client); let mut imds_client = Client::builder().configure(&provider_config); if let Some(endpoint_override) = test_case.endpoint_override { imds_client = imds_client.endpoint(endpoint_override.parse::().unwrap()); diff --git a/aws/rust-runtime/aws-config/src/imds/client/error.rs b/aws/rust-runtime/aws-config/src/imds/client/error.rs index 4d32aee012..a97c3961c7 100644 --- a/aws/rust-runtime/aws-config/src/imds/client/error.rs +++ b/aws/rust-runtime/aws-config/src/imds/client/error.rs @@ -5,9 +5,9 @@ //! Error types for [`ImdsClient`](crate::imds::client::Client) -use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; use aws_smithy_http::endpoint::error::InvalidEndpointError; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use std::error::Error; use std::fmt; diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 3bde8a4510..2dd5693b1b 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -288,9 +288,10 @@ mod test { }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; + use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; @@ -298,55 +299,55 @@ mod test { #[tokio::test] async fn profile_is_not_cached() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"different-profile"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST2\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); let client = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_client(&http_client)) .build(); let creds1 = client.provide_credentials().await.expect("valid creds"); let creds2 = client.provide_credentials().await.expect("valid creds"); assert_eq!(creds1.access_key_id(), "ASIARTEST"); assert_eq!(creds2.access_key_id(), "ASIARTEST2"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] #[traced_test] async fn credentials_not_stale_should_be_used_as_they_are() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); // set to 2021-09-21T04:16:50Z that makes returned credentials' expiry (2021-09-21T04:16:53Z) // not stale @@ -354,8 +355,8 @@ mod test { let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials); let provider_config = ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) - .with_sleep(sleep) + .with_http_client(http_client.clone()) + .with_sleep_impl(sleep) .with_time_source(time_source); let client = crate::imds::Client::builder() .configure(&provider_config) @@ -370,7 +371,7 @@ mod test { creds.expiry(), UNIX_EPOCH.checked_add(Duration::from_secs(1632197813)) ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // There should not be logs indicating credentials are extended for stability. assert!(!logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY)); @@ -378,28 +379,28 @@ mod test { #[tokio::test] #[traced_test] async fn expired_credentials_should_be_extended() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); // set to 2021-09-21T17:41:25Z that renders fetched credentials already expired (2021-09-21T04:16:53Z) let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632246085); let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials); let provider_config = ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) - .with_sleep(sleep) + .with_http_client(http_client.clone()) + .with_sleep_impl(sleep) .with_time_source(time_source); let client = crate::imds::Client::builder() .configure(&provider_config) @@ -410,7 +411,7 @@ mod test { .build(); let creds = provider.provide_credentials().await.expect("valid creds"); assert!(creds.expiry().unwrap() > time_of_request_to_fetch_credentials); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // We should inform customers that expired credentials are being used for stability. assert!(logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY)); @@ -486,36 +487,36 @@ mod test { #[tokio::test] async fn fallback_credentials_should_be_used_when_imds_returns_500_during_credentials_refresh() { - let connection = TestConnection::new(vec![ + let http_client = EventClient::new(vec![ // The next three request/response pairs will correspond to the first call to `provide_credentials`. // During the call, it populates last_retrieved_credentials. - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), // The following request/response pair corresponds to the second call to `provide_credentials`. // During the call, IMDS returns response code 500. - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder().status(500).body(SdkBody::empty()).unwrap(), ), - ]); + ], TokioSleep::new()); let provider = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_client(&http_client)) .build(); let creds1 = provider.provide_credentials().await.expect("valid creds"); assert_eq!(creds1.access_key_id(), "ASIARTEST"); // `creds1` should be returned as fallback credentials and assigned to `creds2` let creds2 = provider.provide_credentials().await.expect("valid creds"); assert_eq!(creds1, creds2); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } } diff --git a/aws/rust-runtime/aws-config/src/imds/region.rs b/aws/rust-runtime/aws-config/src/imds/region.rs index 072dc97a87..4f48448e99 100644 --- a/aws/rust-runtime/aws-config/src/imds/region.rs +++ b/aws/rust-runtime/aws-config/src/imds/region.rs @@ -112,31 +112,33 @@ mod test { use crate::provider_config::ProviderConfig; use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use tracing_test::traced_test; #[tokio::test] async fn load_region() { - let conn = TestConnection::new(vec![ - ( - token_request("http://169.254.169.254", 21600), - token_response(21600, "token"), - ), - ( - imds_request( - "http://169.254.169.254/latest/meta-data/placement/region", - "token", + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, "token"), + ), + ConnectionEvent::new( + imds_request( + "http://169.254.169.254/latest/meta-data/placement/region", + "token", + ), + imds_response("eu-west-1"), ), - imds_response("eu-west-1"), - ), - ]); + ], + TokioSleep::new(), + ); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(conn)) - .with_sleep(TokioSleep::new()), + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()), ) .build(); assert_eq!( @@ -148,18 +150,21 @@ mod test { #[traced_test] #[tokio::test] async fn no_region_imds_disabled() { - let conn = TestConnection::new(vec![( - token_request("http://169.254.169.254", 21600), - http::Response::builder() - .status(403) - .body(SdkBody::empty()) - .unwrap(), - )]); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + token_request("http://169.254.169.254", 21600), + http::Response::builder() + .status(403) + .body(SdkBody::empty()) + .unwrap(), + )], + TokioSleep::new(), + ); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(conn)) - .with_sleep(TokioSleep::new()), + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()), ) .build(); assert_eq!(provider.region().await, None); diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 18fc00e46a..ebf80afdfb 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -112,7 +112,6 @@ mod fs_util; mod http_credential_provider; mod json_credentials; -pub mod connector; pub mod credential_process; pub mod default_provider; pub mod ecs; @@ -150,28 +149,25 @@ pub async fn load_from_env() -> aws_types::SdkConfig { } mod loader { - use std::sync::Arc; - + use crate::default_provider::use_dual_stack::use_dual_stack_provider; + use crate::default_provider::use_fips::use_fips_provider; + use crate::default_provider::{app_name, credentials, region, retry_config, timeout_config}; + use crate::meta::region::ProvideRegion; + use crate::profile::profile_file::ProfileFiles; + use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; - use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; - use aws_smithy_async::time::{SharedTimeSource, TimeSource}; - use aws_smithy_client::http_connector::HttpConnector; + use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; + use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::app_name::AppName; use aws_types::docs_for; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::sdk_config::SharedHttpClient; use aws_types::SdkConfig; - use crate::connector::default_connector; - use crate::default_provider::use_dual_stack::use_dual_stack_provider; - use crate::default_provider::use_fips::use_fips_provider; - use crate::default_provider::{app_name, credentials, region, retry_config, timeout_config}; - use crate::meta::region::ProvideRegion; - use crate::profile::profile_file::ProfileFiles; - use crate::provider_config::ProviderConfig; - #[derive(Default, Debug)] enum CredentialsProviderOption { /// No provider was set by the user. We can set up the default credentials provider chain. @@ -200,7 +196,7 @@ mod loader { sleep: Option, timeout_config: Option, provider_config: Option, - http_connector: Option, + http_client: Option, profile_name_override: Option, profile_files_override: Option, use_fips: Option, @@ -272,61 +268,50 @@ mod loader { /// Override the sleep implementation for this [`ConfigLoader`]. The sleep implementation /// is used to create timeout futures. - pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self { + pub fn sleep_impl(mut self, sleep: impl IntoShared) -> Self { // it's possible that we could wrapping an `Arc in an `Arc` and that's OK - self.sleep = Some(SharedAsyncSleep::new(sleep)); + self.sleep = Some(sleep.into_shared()); self } /// Set the time source used for tasks like signing requests - pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { - self.time_source = Some(SharedTimeSource::new(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.time_source = Some(time_source.into_shared()); self } - /// Override the [`HttpConnector`] for this [`ConfigLoader`]. The connector will be used for - /// both AWS services and credential providers. When [`HttpConnector::ConnectorFn`] is used, - /// the connector will be lazily instantiated as needed based on the provided settings. + /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this + /// [`ConfigLoader`]. The HTTP client will be used for both AWS services and credentials providers. /// - /// **Note**: In order to take advantage of late-configured timeout settings, you MUST use - /// [`HttpConnector::ConnectorFn`] - /// when configuring this connector. + /// If you wish to use a separate HTTP client for credentials providers when creating clients, + /// then override the HTTP client set with this function on the client-specific `Config`s. /// - /// If you wish to use a separate connector when creating clients, use the client-specific config. /// ## Examples + /// /// ```no_run /// # use aws_smithy_async::rt::sleep::SharedAsyncSleep; - /// use aws_smithy_client::http_connector::HttpConnector; /// #[cfg(feature = "client-hyper")] /// # async fn create_config() { /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// - /// let connector_fn = |settings: &ConnectorSettings, sleep: Option| { - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() - /// .with_webpki_roots() - /// // NOTE: setting `https_only()` will not allow this connector to work with IMDS. - /// .https_only() - /// .enable_http1() - /// .enable_http2() - /// .build(); - /// let mut smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings(settings.clone()); - /// smithy_connector.set_sleep_impl(sleep); - /// Some(DynConnector::new(smithy_connector.build(https_connector))) - /// }; - /// let connector = HttpConnector::ConnectorFn(std::sync::Arc::new(connector_fn)); + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; + /// + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// .with_webpki_roots() + /// // NOTE: setting `https_only()` will not allow this connector to work with IMDS. + /// .https_only() + /// .enable_http1() + /// .enable_http2() + /// .build(); + /// + /// let hyper_client = HyperClientBuilder::new().build(tls_connector); /// let sdk_config = aws_config::from_env() - /// .http_connector(connector) + /// .http_client(hyper_client) /// .load() /// .await; /// # } /// ``` - pub fn http_connector(mut self, http_connector: impl Into) -> Self { - self.http_connector = Some(http_connector.into()); + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + self.http_client = Some(http_client.into_shared()); self } @@ -559,10 +544,6 @@ mod loader { /// This means that if you provide a region provider that does not return a region, no region will /// be set in the resulting [`SdkConfig`](aws_types::SdkConfig) pub async fn load(self) -> SdkConfig { - let http_connector = self - .http_connector - .unwrap_or_else(|| HttpConnector::ConnectorFn(Arc::new(default_connector))); - let time_source = self.time_source.unwrap_or_default(); let sleep_impl = if self.sleep.is_some() { @@ -583,10 +564,13 @@ mod loader { let conf = self .provider_config .unwrap_or_else(|| { - ProviderConfig::init(time_source.clone(), sleep_impl.clone()) + let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone()) .with_fs(self.fs.unwrap_or_default()) - .with_env(self.env.unwrap_or_default()) - .with_http_connector(http_connector.clone()) + .with_env(self.env.unwrap_or_default()); + if let Some(http_client) = self.http_client.clone() { + config = config.with_http_client(http_client); + } + config }) .with_profile_config(self.profile_files_override, self.profile_name_override); let region = if let Some(provider) = self.region { @@ -641,7 +625,7 @@ mod loader { Some(self.credentials_cache.unwrap_or_else(|| { let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source()); - builder.set_sleep(conf.sleep()); + builder.set_sleep_impl(conf.sleep_impl()); builder.into_credentials_cache() })) } else { @@ -664,9 +648,9 @@ mod loader { .region(region) .retry_config(retry_config) .timeout_config(timeout_config) - .time_source(time_source) - .http_connector(http_connector); + .time_source(time_source); + builder.set_http_client(self.http_client); builder.set_app_name(app_name); builder.set_credentials_cache(credentials_cache); builder.set_credentials_provider(credentials_provider); @@ -693,12 +677,13 @@ mod loader { #[cfg(test)] mod test { + use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; + use crate::test_case::{no_traffic_client, InstantSleep}; + use crate::{from_env, ConfigLoader}; use aws_credential_types::provider::ProvideCredentials; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::time::{StaticTimeSource, TimeSource}; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::never::NeverConnector; - use aws_smithy_client::test_connection::infallible_connection_fn; + use aws_smithy_runtime::client::http::test_util::{infallible_client_fn, NeverClient}; use aws_types::app_name::AppName; use aws_types::os_shim_internal::{Env, Fs}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -706,10 +691,6 @@ mod loader { use std::time::{SystemTime, UNIX_EPOCH}; use tracing_test::traced_test; - use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; - use crate::test_case::{no_traffic_connector, InstantSleep}; - use crate::{from_env, ConfigLoader}; - #[tokio::test] #[traced_test] async fn provider_config_used() { @@ -725,7 +706,7 @@ mod loader { .sleep_impl(TokioSleep::new()) .env(env) .fs(fs) - .http_connector(DynConnector::new(NeverConnector::new())) + .http_client(NeverClient::new()) .profile_name("custom") .profile_files( ProfileFiles::builder() @@ -767,7 +748,7 @@ mod loader { fn base_conf() -> ConfigLoader { from_env() .sleep_impl(InstantSleep) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) } #[tokio::test] @@ -804,11 +785,11 @@ mod loader { async fn connector_is_shared() { let num_requests = Arc::new(AtomicUsize::new(0)); let movable = num_requests.clone(); - let conn = infallible_connection_fn(move |_req| { + let http_client = infallible_client_fn(move |_req| { movable.fetch_add(1, Ordering::Relaxed); http::Response::new("ok!") }); - let config = from_env().http_connector(conn.clone()).load().await; + let config = from_env().http_client(http_client.clone()).load().await; config .credentials_provider() .unwrap() @@ -831,7 +812,7 @@ mod loader { let config = from_env() .sleep_impl(InstantSleep) .time_source(StaticTimeSource::new(UNIX_EPOCH)) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) .load() .await; // assert that the innards contain the customized fields diff --git a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs index 838007ad1e..cf7978d275 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs @@ -195,7 +195,7 @@ mod test { use crate::profile::credentials::exec::ProviderChain; use crate::profile::credentials::repr::{BaseProvider, ProfileChain}; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use aws_credential_types::Credentials; use std::collections::HashMap; @@ -219,7 +219,7 @@ mod test { fn error_on_unknown_provider() { let factory = NamedProviderFactory::new(HashMap::new()); let chain = ProviderChain::from_repr( - &ProviderConfig::empty().with_http_connector(no_traffic_connector()), + &ProviderConfig::empty().with_http_client(no_traffic_client()), ProfileChain { base: BaseProvider::NamedSource("floozle"), chain: vec![], diff --git a/aws/rust-runtime/aws-config/src/profile/region.rs b/aws/rust-runtime/aws-config/src/profile/region.rs index 3cdcf8f7e4..7b04ad9592 100644 --- a/aws/rust-runtime/aws-config/src/profile/region.rs +++ b/aws/rust-runtime/aws-config/src/profile/region.rs @@ -157,7 +157,7 @@ impl ProvideRegion for ProfileFileRegionProvider { mod test { use crate::profile::ProfileFileRegionProvider; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use aws_sdk_sts::config::Region; use aws_types::os_shim_internal::{Env, Fs}; use futures_util::FutureExt; @@ -169,7 +169,7 @@ mod test { ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()) + .with_http_client(no_traffic_client()) } #[traced_test] @@ -244,7 +244,7 @@ role_arn = arn:aws:iam::123456789012:role/test let provider_config = ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()); + .with_http_client(no_traffic_client()); assert_eq!( Some(Region::new("us-east-1")), diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index f1caa75e51..da36ddb628 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -5,27 +5,21 @@ //! Configuration Options for Credential Providers -use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; +use crate::profile; +use crate::profile::profile_file::ProfileFiles; +use crate::profile::{ProfileFileLoadError, ProfileSet}; +use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; -use aws_types::{ - http_connector::{ConnectorSettings, HttpConnector}, - region::Region, -}; +use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; use std::borrow::Cow; - use std::fmt::{Debug, Formatter}; use std::sync::Arc; use tokio::sync::OnceCell; -use crate::connector::default_connector; -use crate::profile; - -use crate::profile::profile_file::ProfileFiles; -use crate::profile::{ProfileFileLoadError, ProfileSet}; - /// Configuration options for Credential Providers /// /// Most credential providers builders offer a `configure` method which applies general provider configuration @@ -39,8 +33,8 @@ pub struct ProviderConfig { env: Env, fs: Fs, time_source: SharedTimeSource, - connector: HttpConnector, - sleep: Option, + http_client: Option, + sleep_impl: Option, region: Option, /// An AWS profile created from `ProfileFiles` and a `profile_name` parsed_profile: Arc>>, @@ -55,26 +49,23 @@ impl Debug for ProviderConfig { f.debug_struct("ProviderConfig") .field("env", &self.env) .field("fs", &self.fs) - .field("sleep", &self.sleep) + .field("time_source", &self.time_source) + .field("http_client", &self.http_client) + .field("sleep_impl", &self.sleep_impl) .field("region", &self.region) + .field("profile_name_override", &self.profile_name_override) .finish() } } impl Default for ProviderConfig { fn default() -> Self { - let connector = HttpConnector::ConnectorFn(Arc::new( - |settings: &ConnectorSettings, sleep: Option| { - default_connector(settings, sleep) - }, - )); - Self { env: Env::default(), fs: Fs::default(), time_source: SharedTimeSource::default(), - connector, - sleep: default_async_sleep(), + http_client: None, + sleep_impl: default_async_sleep(), region: None, parsed_profile: Default::default(), profile_files: ProfileFiles::default(), @@ -101,8 +92,8 @@ impl ProviderConfig { env, fs, time_source: SharedTimeSource::new(StaticTimeSource::new(UNIX_EPOCH)), - connector: HttpConnector::Prebuilt(None), - sleep: None, + http_client: None, + sleep_impl: None, region: None, profile_name_override: None, } @@ -141,8 +132,8 @@ impl ProviderConfig { env: Env::default(), fs: Fs::default(), time_source: SharedTimeSource::default(), - connector: HttpConnector::Prebuilt(None), - sleep: None, + http_client: None, + sleep_impl: None, region: None, parsed_profile: Default::default(), profile_files: ProfileFiles::default(), @@ -151,15 +142,18 @@ impl ProviderConfig { } /// Initializer for ConfigBag to avoid possibly setting incorrect defaults. - pub(crate) fn init(time_source: SharedTimeSource, sleep: Option) -> Self { + pub(crate) fn init( + time_source: SharedTimeSource, + sleep_impl: Option, + ) -> Self { Self { parsed_profile: Default::default(), profile_files: ProfileFiles::default(), env: Env::default(), fs: Fs::default(), time_source, - connector: HttpConnector::Prebuilt(None), - sleep, + http_client: None, + sleep_impl, region: None, profile_name_override: None, } @@ -199,19 +193,13 @@ impl ProviderConfig { } #[allow(dead_code)] - pub(crate) fn default_connector(&self) -> Option { - self.connector - .connector(&Default::default(), self.sleep.clone()) - } - - #[allow(dead_code)] - pub(crate) fn connector(&self, settings: &ConnectorSettings) -> Option { - self.connector.connector(settings, self.sleep.clone()) + pub(crate) fn http_client(&self) -> Option { + self.http_client.clone() } #[allow(dead_code)] - pub(crate) fn sleep(&self) -> Option { - self.sleep.clone() + pub(crate) fn sleep_impl(&self) -> Option { + self.sleep_impl.clone() } #[allow(dead_code)] @@ -302,65 +290,25 @@ impl ProviderConfig { } /// Override the time source for this configuration - pub fn with_time_source( - self, - time_source: impl aws_smithy_async::time::TimeSource + 'static, - ) -> Self { - ProviderConfig { - time_source: SharedTimeSource::new(time_source), - ..self - } - } - - /// Override the HTTPS connector for this configuration - /// - /// **Note**: In order to take advantage of late-configured timeout settings, use [`HttpConnector::ConnectorFn`] - /// when configuring this connector. - pub fn with_http_connector(self, connector: impl Into) -> Self { + pub fn with_time_source(self, time_source: impl IntoShared) -> Self { ProviderConfig { - connector: connector.into(), + time_source: time_source.into_shared(), ..self } } - /// Override the TCP connector for this configuration - /// - /// This connector MUST provide an HTTPS encrypted connection. - /// - /// # Stability - /// This method may change to support HTTP configuration. - #[cfg(feature = "client-hyper")] - pub fn with_tcp_connector(self, connector: C) -> Self - where - C: Clone + Send + Sync + 'static, - C: tower::Service, - C::Response: hyper::client::connect::Connection - + tokio::io::AsyncRead - + tokio::io::AsyncWrite - + Send - + Unpin - + 'static, - C::Future: Unpin + Send + 'static, - C::Error: Into>, - { - let connector_fn = move |settings: &ConnectorSettings, sleep: Option| { - let mut builder = aws_smithy_client::hyper_ext::Adapter::builder() - .connector_settings(settings.clone()); - if let Some(sleep) = sleep { - builder = builder.sleep_impl(sleep); - }; - Some(DynConnector::new(builder.build(connector.clone()))) - }; + /// Override the HTTP client for this configuration + pub fn with_http_client(self, http_client: impl IntoShared) -> Self { ProviderConfig { - connector: HttpConnector::ConnectorFn(Arc::new(connector_fn)), + http_client: Some(http_client.into_shared()), ..self } } /// Override the sleep implementation for this configuration - pub fn with_sleep(self, sleep: impl AsyncSleep + 'static) -> Self { + pub fn with_sleep_impl(self, sleep_impl: impl IntoShared) -> Self { ProviderConfig { - sleep: Some(SharedAsyncSleep::new(sleep)), + sleep_impl: Some(sleep_impl.into_shared()), ..self } } diff --git a/aws/rust-runtime/aws-config/src/sso.rs b/aws/rust-runtime/aws-config/src/sso.rs index 592ed5b6bc..8a14d71008 100644 --- a/aws/rust-runtime/aws-config/src/sso.rs +++ b/aws/rust-runtime/aws-config/src/sso.rs @@ -13,7 +13,6 @@ use crate::fs_util::{home_dir, Os}; use crate::json_credentials::{json_parse_loop, InvalidJsonCredentials}; use crate::provider_config::ProviderConfig; - use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_credential_types::Credentials; @@ -21,19 +20,16 @@ use aws_sdk_sso::types::RoleCredentials; use aws_sdk_sso::{config::Builder as SsoConfigBuilder, Client as SsoClient, Config as SsoConfig}; use aws_smithy_json::deserialize::Token; use aws_smithy_types::date_time::Format; +use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::DateTime; use aws_types::os_shim_internal::{Env, Fs}; use aws_types::region::Region; - +use ring::digest; use std::convert::TryInto; use std::error::Error; use std::fmt::{Display, Formatter}; use std::io; use std::path::PathBuf; - -use crate::connector::expect_connector; -use aws_smithy_types::retry::RetryConfig; -use ring::digest; use zeroize::Zeroizing; /// SSO Credentials Provider @@ -63,13 +59,9 @@ impl SsoCredentialsProvider { let fs = provider_config.fs(); let env = provider_config.env(); - let mut sso_config = SsoConfig::builder() - .http_connector(expect_connector( - "The SSO credentials provider", - provider_config.connector(&Default::default()), - )) - .retry_config(RetryConfig::standard()); - sso_config.set_sleep_impl(provider_config.sleep()); + let mut sso_config = SsoConfig::builder().retry_config(RetryConfig::standard()); + sso_config.set_http_client(provider_config.http_client()); + sso_config.set_sleep_impl(provider_config.sleep_impl()); SsoCredentialsProvider { fs, diff --git a/aws/rust-runtime/aws-config/src/sts.rs b/aws/rust-runtime/aws-config/src/sts.rs index 028409bfbe..323ae45e0d 100644 --- a/aws/rust-runtime/aws-config/src/sts.rs +++ b/aws/rust-runtime/aws-config/src/sts.rs @@ -5,27 +5,22 @@ //! Credential provider augmentation through the AWS Security Token Service (STS). -pub(crate) mod util; +use aws_sdk_sts::config::Builder as StsConfigBuilder; +use aws_smithy_types::retry::RetryConfig; pub use assume_role::{AssumeRoleProvider, AssumeRoleProviderBuilder}; mod assume_role; - -use crate::connector::expect_connector; -use aws_sdk_sts::config::Builder as StsConfigBuilder; -use aws_smithy_types::retry::RetryConfig; +pub(crate) mod util; 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( - "The STS features of aws-config", - self.connector(&Default::default()), - )) .retry_config(RetryConfig::standard()) .region(self.region()) .time_source(self.time_source()); - builder.set_sleep_impl(self.sleep()); + builder.set_http_client(self.http_client()); + builder.set_sleep_impl(self.sleep_impl()); builder } } 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 90cd91195f..6cf1f0ae27 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -5,7 +5,6 @@ //! Assume credentials for a role through the AWS Security Token Service (STS). -use crate::connector::expect_connector; use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; @@ -13,10 +12,11 @@ use aws_sdk_sts::operation::assume_role::builders::AssumeRoleFluentBuilder; use aws_sdk_sts::operation::assume_role::AssumeRoleError; use aws_sdk_sts::types::PolicyDescriptorType; use aws_sdk_sts::Client as StsClient; -use aws_smithy_client::erase::DynConnector; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; use std::time::Duration; use tracing::Instrument; @@ -168,12 +168,14 @@ impl AssumeRoleProviderBuilder { self } - /// If the `rustls` or `nativetls` features are enabled, this field is optional and a default - /// backing connection will be provided. - pub fn connection(mut self, conn: impl aws_smithy_client::bounds::SmithyConnector) -> Self { + /// Set the HTTP client to use. + /// + /// If the `rustls` feature is enabled, this field is optional and a default + /// backing HTTP client will be provided. + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { let conf = match self.conf { - Some(conf) => conf.with_http_connector(DynConnector::new(conn)), - None => ProviderConfig::default().with_http_connector(DynConnector::new(conn)), + Some(conf) => conf.with_http_client(http_client), + None => ProviderConfig::default().with_http_client(http_client), }; self.conf = Some(conf); self @@ -212,12 +214,9 @@ impl AssumeRoleProviderBuilder { .credentials_cache(credentials_cache) .credentials_provider(provider) .time_source(conf.time_source()) - .region(self.region.clone()) - .http_connector(expect_connector( - "The AssumeRole credentials provider", - conf.connector(&Default::default()), - )); - config.set_sleep_impl(conf.sleep()); + .region(self.region.clone()); + config.set_sleep_impl(conf.sleep_impl()); + config.set_http_client(conf.http_client()); let session_name = self.session_name.unwrap_or_else(|| { super::util::default_session_name("assume-role-provider", conf.time_source().now()) @@ -295,21 +294,22 @@ mod test { use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_async::time::StaticTimeSource; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ + capture_request, ConnectionEvent, EventClient, + }; use aws_types::region::Region; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn configures_session_length() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let provider_conf = ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_time_source(StaticTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), )) - .with_http_connector(DynConnector::new(server)); + .with_http_client(http_client); let provider = AssumeRoleProvider::builder("myrole") .configure(&provider_conf) .region(Region::new("us-east-1")) @@ -325,25 +325,25 @@ mod test { #[tokio::test] async fn provider_does_not_cache_credentials_by_default() { - let conn = TestConnection::new(vec![ - (http::Request::new(SdkBody::from("request body")), + let http_client = EventClient::new(vec![ + ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n" )).unwrap()), - (http::Request::new(SdkBody::from("request body")), + ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n TESTSECRET\n tokencorrect\n 2009-02-13T23:33:30Z\n \n \n \n c2e971c2-702d-4124-9b1f-1670febbea18\n \n\n" )).unwrap()), - ]); + ], TokioSleep::new()); let (testing_time_source, sleep) = instant_time_and_sleep( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), // 1234567890 since UNIX_EPOCH is 2009-02-13T23:31:30Z ); let provider_conf = ProviderConfig::empty() - .with_sleep(sleep) + .with_sleep_impl(sleep) .with_time_source(testing_time_source.clone()) - .with_http_connector(DynConnector::new(conn)); + .with_http_client(http_client); let credentials_list = std::sync::Arc::new(std::sync::Mutex::new(vec![ Credentials::new( "test", diff --git a/aws/rust-runtime/aws-config/src/test_case.rs b/aws/rust-runtime/aws-config/src/test_case.rs index 14859d57ef..630511ffa7 100644 --- a/aws/rust-runtime/aws-config/src/test_case.rs +++ b/aws/rust-runtime/aws-config/src/test_case.rs @@ -4,17 +4,16 @@ */ use crate::provider_config::ProviderConfig; - use aws_credential_types::provider::{self, ProvideCredentials}; use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep, TokioSleep}; -use aws_smithy_client::dvr::{NetworkTraffic, RecordingConnection, ReplayingConnection}; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_runtime::client::http::test_util::dvr::{ + NetworkTraffic, RecordingClient, ReplayingClient, +}; +use aws_smithy_runtime_api::shared::IntoShared; +use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; - +use aws_types::sdk_config::SharedHttpClient; use serde::Deserialize; - -use crate::connector::default_connector; -use aws_smithy_types::error::display::DisplayErrorContext; use std::collections::HashMap; use std::env; use std::error::Error; @@ -68,18 +67,18 @@ impl From for Credentials { /// A credentials test environment is a directory containing: /// - an `fs` directory. This is loaded into the test as if it was mounted at `/` /// - an `env.json` file containing environment variables -/// - an `http-traffic.json` file containing an http traffic log from [`dvr`](aws_smithy_client::dvr) +/// - an `http-traffic.json` file containing an http traffic log from [`dvr`](aws_smithy_runtime::client::http::test_utils::dvr) /// - a `test-case.json` file defining the expected output of the test pub(crate) struct TestEnvironment { metadata: Metadata, base_dir: PathBuf, - connector: ReplayingConnection, + http_client: ReplayingClient, provider_config: ProviderConfig, } /// Connector which expects no traffic -pub(crate) fn no_traffic_connector() -> DynConnector { - DynConnector::new(ReplayingConnection::new(vec![])) +pub(crate) fn no_traffic_client() -> SharedHttpClient { + ReplayingClient::new(Vec::new()).into_shared() } #[derive(Debug)] @@ -228,18 +227,18 @@ impl TestEnvironment { &std::fs::read_to_string(dir.join("test-case.json")) .map_err(|e| format!("failed to load test case: {}", e))?, )?; - let connector = ReplayingConnection::new(network_traffic.events().clone()); + let http_client = ReplayingClient::new(network_traffic.events().clone()); let provider_config = ProviderConfig::empty() .with_fs(fs.clone()) .with_env(env.clone()) - .with_http_connector(DynConnector::new(connector.clone())) - .with_sleep(TokioSleep::new()) + .with_http_client(http_client.clone()) + .with_sleep_impl(TokioSleep::new()) .load_default_region() .await; Ok(TestEnvironment { base_dir: dir.into(), metadata, - connector, + http_client, provider_config, }) } @@ -257,6 +256,7 @@ impl TestEnvironment { } #[allow(unused)] + #[cfg(all(feature = "client-hyper", feature = "rustls"))] /// Record a test case from live (remote) HTTPS traffic /// /// The `default_connector()` from the crate will be used @@ -268,18 +268,21 @@ impl TestEnvironment { P: ProvideCredentials, { // swap out the connector generated from `http-traffic.json` for a real connector: - let live_connector = - default_connector(&Default::default(), self.provider_config.sleep()).unwrap(); - let live_connector = RecordingConnection::new(live_connector); + let live_connector = aws_smithy_runtime::client::http::hyper_014::default_connector( + &Default::default(), + self.provider_config.sleep_impl(), + ) + .expect("feature gate on this function makes this always return Some"); + let live_client = RecordingClient::new(live_connector); let config = self .provider_config .clone() - .with_http_connector(DynConnector::new(live_connector.clone())); + .with_http_client(live_client.clone()); let provider = make_provider(config).await; let result = provider.provide_credentials().await; std::fs::write( self.base_dir.join("http-traffic-recorded.json"), - serde_json::to_string(&live_connector.network_traffic()).unwrap(), + serde_json::to_string(&live_client.network_traffic()).unwrap(), ) .unwrap(); self.check_results(result); @@ -295,16 +298,16 @@ impl TestEnvironment { F: Future, P: ProvideCredentials, { - let recording_connector = RecordingConnection::new(self.connector.clone()); + let recording_client = RecordingClient::new(self.http_client.clone()); let config = self .provider_config .clone() - .with_http_connector(DynConnector::new(recording_connector.clone())); + .with_http_client(recording_client.clone()); let provider = make_provider(config).await; let result = provider.provide_credentials().await; std::fs::write( self.base_dir.join("http-traffic-recorded.json"), - serde_json::to_string(&recording_connector.network_traffic()).unwrap(), + serde_json::to_string(&recording_client.network_traffic()).unwrap(), ) .unwrap(); self.check_results(result); @@ -341,7 +344,7 @@ impl TestEnvironment { self.check_results(result); // todo: validate bodies match self - .connector + .http_client .clone() .validate( &["CONTENT-TYPE", "x-aws-ec2-metadata-token"], diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs index 7ea55fdf26..b380c64ccf 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -241,7 +241,7 @@ async fn load_credentials( #[cfg(test)] mod test { use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use crate::web_identity_token::{ Builder, ENV_VAR_ROLE_ARN, ENV_VAR_SESSION_NAME, ENV_VAR_TOKEN_FILE, }; @@ -256,9 +256,9 @@ mod test { async fn unloaded_provider() { // empty environment let conf = ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_env(Env::from_slice(&[])) - .with_http_connector(no_traffic_connector()) + .with_http_client(no_traffic_client()) .with_region(Some(Region::from_static("us-east-1"))); let provider = Builder::default().configure(&conf).build(); @@ -279,10 +279,10 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_region(region) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .build(); let err = provider @@ -311,8 +311,8 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() - .with_sleep(TokioSleep::new()) - .with_http_connector(no_traffic_connector()) + .with_sleep_impl(TokioSleep::new()) + .with_http_client(no_traffic_client()) .with_region(Some(Region::new("us-east-1"))) .with_env(env) .with_fs(fs), diff --git a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs index 6169c2b930..8d09a13a8e 100644 --- a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs @@ -143,6 +143,7 @@ mod builder { LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_BUFFER_TIME_JITTER_FRACTION, DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT, }; + use aws_smithy_runtime_api::shared::IntoShared; /// Builder for constructing a `LazyCredentialsCache`. /// @@ -158,7 +159,7 @@ mod builder { /// `build` to create a `LazyCredentialsCache`. #[derive(Clone, Debug, Default)] pub struct Builder { - sleep: Option, + sleep_impl: Option, time_source: Option, load_timeout: Option, buffer_time: Option, @@ -177,8 +178,8 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn sleep(mut self, sleep: SharedAsyncSleep) -> Self { - self.set_sleep(Some(sleep)); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -187,14 +188,14 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn set_sleep(&mut self, sleep: Option) -> &mut Self { - self.sleep = sleep; + pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { + self.sleep_impl = sleep_impl; self } #[doc(hidden)] // because they only exist for tests - pub fn time_source(mut self, time_source: SharedTimeSource) -> Self { - self.set_time_source(Some(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.set_time_source(Some(time_source.into_shared())); self } @@ -326,7 +327,7 @@ mod builder { ); LazyCredentialsCache::new( self.time_source.unwrap_or_default(), - self.sleep.unwrap_or_else(|| { + self.sleep_impl.unwrap_or_else(|| { default_async_sleep().expect("no default sleep implementation available") }), provider, diff --git a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs index 498844f1b3..4196730f09 100644 --- a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs +++ b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs @@ -7,16 +7,18 @@ use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::boxclone::BoxFuture; use aws_smithy_http::endpoint::{ResolveEndpoint, ResolveEndpointError}; use aws_smithy_types::endpoint::Endpoint; use std::fmt::{Debug, Formatter}; use std::future::Future; +use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{Receiver, Sender}; +type BoxFuture = Pin> + Send>>; + /// Endpoint reloader #[must_use] pub struct ReloadEndpoint { diff --git a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs index f19ad434d1..057b659c2e 100644 --- a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs +++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_smithy_client::SdkError; use aws_smithy_http::http::HttpHeaders; use aws_smithy_http::operation; +use aws_smithy_http::result::SdkError; use aws_smithy_types::error::metadata::{ Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata, }; diff --git a/aws/rust-runtime/aws-types/Cargo.toml b/aws/rust-runtime/aws-types/Cargo.toml index eb0b527f42..2570ccff73 100644 --- a/aws/rust-runtime/aws-types/Cargo.toml +++ b/aws/rust-runtime/aws-types/Cargo.toml @@ -9,13 +9,14 @@ repository = "https://github.com/awslabs/smithy-rs" [features] # This feature is to be used only for doc comments -examples = ["dep:hyper-rustls", "aws-smithy-client/client-hyper", "aws-smithy-client/rustls"] +examples = ["dep:hyper-rustls", "aws-smithy-runtime/client", "aws-smithy-runtime/connector-hyper-0-14-x", "aws-smithy-runtime/tls-rustls"] [dependencies] aws-credential-types = { path = "../aws-credential-types" } aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } +aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", optional = true } +aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } tracing = "0.1" http = "0.2.6" diff --git a/aws/rust-runtime/aws-types/src/lib.rs b/aws/rust-runtime/aws-types/src/lib.rs index bf54671e15..e6a0a69dcd 100644 --- a/aws/rust-runtime/aws-types/src/lib.rs +++ b/aws/rust-runtime/aws-types/src/lib.rs @@ -21,8 +21,6 @@ pub mod endpoint_config; pub mod os_shim_internal; pub mod region; pub mod sdk_config; - -pub use aws_smithy_client::http_connector; pub use sdk_config::SdkConfig; use aws_smithy_types::config_bag::{Storable, StoreReplace}; diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index 08e49469ba..bc9d3c13a4 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -9,18 +9,19 @@ //! //! This module contains an shared configuration representation that is agnostic from a specific service. -use aws_credential_types::cache::CredentialsCache; -use aws_credential_types::provider::SharedCredentialsProvider; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::{SharedTimeSource, TimeSource}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_types::retry::RetryConfig; -use aws_smithy_types::timeout::TimeoutConfig; - use crate::app_name::AppName; use crate::docs_for; use crate::region::Region; +pub use aws_credential_types::cache::CredentialsCache; +pub use aws_credential_types::provider::SharedCredentialsProvider; +pub use aws_smithy_async::rt::sleep::SharedAsyncSleep; +pub use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +pub use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::shared::IntoShared; +pub use aws_smithy_types::retry::RetryConfig; +pub use aws_smithy_types::timeout::TimeoutConfig; + #[doc(hidden)] /// Unified docstrings to keep crates in sync. Not intended for public use pub mod unified_docs { @@ -56,7 +57,7 @@ pub struct SdkConfig { sleep_impl: Option, time_source: Option, timeout_config: Option, - http_connector: Option, + http_client: Option, use_fips: Option, use_dual_stack: Option, } @@ -77,7 +78,7 @@ pub struct Builder { sleep_impl: Option, time_source: Option, timeout_config: Option, - http_connector: Option, + http_client: Option, use_fips: Option, use_dual_stack: Option, } @@ -254,8 +255,8 @@ impl Builder { /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = SdkConfig::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: SharedAsyncSleep) -> Self { - self.set_sleep_impl(Some(sleep_impl)); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -399,81 +400,76 @@ impl Builder { self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] /// # fn example() { + /// use aws_types::sdk_config::{SdkConfig, TimeoutConfig}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// use aws_types::SdkConfig; /// - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// // Create a connector that will be used to establish TLS connections + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() + /// // Create a HTTP client that uses the TLS connector. This client is + /// // responsible for creating and caching a HttpConnector when given HttpConnectorSettings. + /// // This hyper client will create HttpConnectors backed by hyper and the tls_connector. + /// let http_client = HyperClientBuilder::new().build(tls_connector); + /// let sdk_config = SdkConfig::builder() + /// .http_client(http_client) + /// // Connect/read timeouts are passed to the HTTP client when servicing a request + /// .timeout_config( + /// TimeoutConfig::builder() /// .connect_timeout(Duration::from_secs(5)) /// .build() /// ) - /// .build(https_connector); - /// let sdk_config = SdkConfig::builder() - /// .http_connector(smithy_connector) /// .build(); /// # } /// ``` - pub fn http_connector(mut self, http_connector: impl Into) -> Self { - self.set_http_connector(Some(http_connector)); + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + self.set_http_client(Some(http_client.into_shared())); self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] /// # fn example() { + /// use aws_types::sdk_config::{Builder, SdkConfig, TimeoutConfig}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// use std::time::Duration; - /// use aws_smithy_client::hyper_ext; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// use aws_types::sdk_config::{Builder, SdkConfig}; /// - /// fn override_http_connector(builder: &mut Builder) { - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// fn override_http_client(builder: &mut Builder) { + /// // Create a connector that will be used to establish TLS connections + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); - /// builder.set_http_connector(Some(smithy_connector)); + /// // Create a HTTP client that uses the TLS connector. This client is + /// // responsible for creating and caching a HttpConnector when given HttpConnectorSettings. + /// // This hyper client will create HttpConnectors backed by hyper and the tls_connector. + /// let http_client = HyperClientBuilder::new().build(tls_connector); + /// + /// builder.set_http_client(Some(http_client)); /// } /// /// let mut builder = SdkConfig::builder(); - /// override_http_connector(&mut builder); + /// override_http_client(&mut builder); /// let config = builder.build(); /// # } /// ``` - pub fn set_http_connector( - &mut self, - http_connector: Option>, - ) -> &mut Self { - self.http_connector = http_connector.map(|inner| inner.into()); + pub fn set_http_client(&mut self, http_client: Option) -> &mut Self { + self.http_client = http_client; self } @@ -524,7 +520,7 @@ impl Builder { retry_config: self.retry_config, sleep_impl: self.sleep_impl, timeout_config: self.timeout_config, - http_connector: self.http_connector, + http_client: self.http_client, use_fips: self.use_fips, use_dual_stack: self.use_dual_stack, time_source: self.time_source, @@ -579,9 +575,9 @@ impl SdkConfig { self.app_name.as_ref() } - /// Configured HTTP Connector - pub fn http_connector(&self) -> Option<&HttpConnector> { - self.http_connector.as_ref() + /// Configured HTTP client + pub fn http_client(&self) -> Option { + self.http_client.clone() } /// Use FIPS endpoints diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt index cd8fe1bef1..878c4a851f 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt @@ -26,7 +26,6 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : "InterceptorContext" to RuntimeType.interceptorContext(runtimeConfig), "RuntimeComponentsBuilder" to RuntimeType.runtimeComponentsBuilder(runtimeConfig), "SharedInterceptor" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::interceptors::SharedInterceptor"), - "SharedTimeSource" to CargoDependency.smithyAsync(runtimeConfig).toType().resolve("time::SharedTimeSource"), "StaticRuntimePlugin" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::runtime_plugin::StaticRuntimePlugin"), "StaticTimeSource" to CargoDependency.smithyAsync(runtimeConfig).toType().resolve("time::StaticTimeSource"), "TestParamsSetterInterceptor" to testParamsSetterInterceptor(), @@ -94,7 +93,7 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : #{StaticRuntimePlugin}::new() .with_runtime_components( #{RuntimeComponentsBuilder}::new("request_time_for_tests") - .with_time_source(Some(#{SharedTimeSource}::new(#{StaticTimeSource}::new(request_time)))) + .with_time_source(Some(#{StaticTimeSource}::new(request_time))) ) ) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index 7b69c5c72a..fb4b5fa58e 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -66,8 +66,7 @@ class AwsFluentClientDecorator : ClientCodegenDecorator { rustCrate.withModule(ClientRustModule.client) { AwsFluentClientExtensions(codegenContext, types).render(this) } - val awsSmithyClient = "aws-smithy-client" - rustCrate.mergeFeature(Feature("rustls", default = true, listOf("$awsSmithyClient/rustls"))) + rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-runtime/tls-rustls"))) } override fun libRsCustomizations( @@ -99,7 +98,7 @@ class AwsFluentClientDecorator : ClientCodegenDecorator { let mut ${params.configBuilderName} = ${params.configBuilderName}; ${params.configBuilderName}.set_region(Some(crate::config::Region::new("us-east-1"))); - let config = ${params.configBuilderName}.http_connector(${params.connectorName}).build(); + let config = ${params.configBuilderName}.http_client(${params.httpClientName}).build(); let ${params.clientName} = #{Client}::from_conf(config); """, "Client" to ClientRustModule.root.toType().resolve("Client"), 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 198d1b8bc4..7c4ffd0543 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 @@ -99,7 +99,7 @@ class CredentialCacheConfig(codegenContext: ClientCodegenContext) : ConfigCustom || match sleep { Some(sleep) => { #{CredentialsCache}::lazy_builder() - .sleep(sleep) + .sleep_impl(sleep) .into_credentials_cache() } None => #{CredentialsCache}::lazy(), diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt index 7ad1e41246..7167910c08 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt @@ -76,7 +76,7 @@ class GenericSmithySdkConfigSettings : ClientCodegenDecorator { ${section.serviceConfigBuilder}.set_timeout_config(${section.sdkConfig}.timeout_config().cloned()); ${section.serviceConfigBuilder}.set_sleep_impl(${section.sdkConfig}.sleep_impl()); - ${section.serviceConfigBuilder}.set_http_connector(${section.sdkConfig}.http_connector().cloned()); + ${section.serviceConfigBuilder}.set_http_client(${section.sdkConfig}.http_client()); ${section.serviceConfigBuilder}.set_time_source(${section.sdkConfig}.time_source()); """, ) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt index b0ec8aac31..43210766e0 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt @@ -83,12 +83,12 @@ fun usesDeprecatedBuiltIns(testOperationInput: EndpointTestOperationInput): Bool * "AWS::S3::UseArnRegion": false * } */ * /* clientParams: {} */ - * let (conn, rcvr) = aws_smithy_client::test_connection::capture_request(None); + * let (http_client, rcvr) = aws_smithy_client::test_connection::capture_request(None); * let conf = { * #[allow(unused_mut)] * let mut builder = aws_sdk_s3::Config::builder() * .with_test_defaults() - * .http_connector(conn); + * .http_client(http_client); * let builder = builder.region(aws_types::region::Region::new("us-west-2")); * let builder = builder.use_arn_region(false); * builder.build() @@ -122,11 +122,6 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: private val model = ctx.model private val instantiator = ClientInstantiator(ctx) - /** the Rust SDK doesn't support SigV4a — search endpoint.properties.authSchemes[].name */ - private fun EndpointTestCase.isSigV4a() = - expect.endpoint.orNull()?.properties?.get("authSchemes")?.asArrayNode()?.orNull() - ?.map { it.expectObjectNode().expectStringMember("name").value }?.contains("sigv4a") == true - fun generateInput(testOperationInput: EndpointTestOperationInput) = writable { val operationName = testOperationInput.operationName.toSnakeCase() tokioTest(safeName("operation_input_test_$operationName")) { @@ -134,7 +129,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: """ /* builtIns: ${escape(Node.prettyPrintJson(testOperationInput.builtInParams))} */ /* clientParams: ${escape(Node.prettyPrintJson(testOperationInput.clientParams))} */ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = #{conf}; let client = $moduleName::Client::from_conf(conf); let _result = dbg!(#{invoke_operation}); @@ -192,7 +187,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: private fun config(operationInput: EndpointTestOperationInput) = writable { rustBlock("") { Attribute.AllowUnusedMut.render(this) - rust("let mut builder = $moduleName::Config::builder().with_test_defaults().http_connector(conn);") + rust("let mut builder = $moduleName::Config::builder().with_test_defaults().http_client(http_client);") operationInput.builtInParams.members.forEach { (builtIn, value) -> val setter = endpointCustomizations.firstNotNullOfOrNull { it.setBuiltInOnServiceConfig( diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt index 9c1a29ecae..3fe721b239 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt @@ -87,28 +87,34 @@ class EndpointBuiltInsDecoratorTest { ##[#{tokio}::test] async fn endpoint_url_built_in_works() { - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("https://RIGHT/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("https://RIGHT/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap() + )], + #{TokioSleep}::new(), + ); let config = Config::builder() - .http_connector(connector.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .endpoint_url("https://RIGHT") .build(); let client = Client::from_conf(config); dbg!(client.some_operation().send().await).expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, "tokio" to CargoDependency.Tokio.toDevDependency().withFeature("rt").withFeature("macros").toType(), - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), + "EventClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::EventClient"), + "ConnectionEvent" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::ConnectionEvent"), "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "TokioSleep" to RuntimeType.smithyAsync(codegenContext.runtimeConfig) + .resolve("rt::sleep::TokioSleep"), ) } } diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt index 0708035541..19ad56d116 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt @@ -84,9 +84,9 @@ class EndpointsCredentialsTest { tokioTest("default_auth") { rustTemplate( """ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .region(#{Region}::new("us-west-2")) .credentials_provider(#{Credentials}::for_tests()) .build(); @@ -107,9 +107,9 @@ class EndpointsCredentialsTest { tokioTest("custom_auth") { rustTemplate( """ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .region(#{Region}::new("us-west-2")) .credentials_provider(#{Credentials}::for_tests()) .build(); diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt index 3a792e2a0a..be0861a045 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt @@ -32,9 +32,9 @@ class InvocationIdDecoratorTest { } } - let (conn, rx) = #{capture_request}(None); + let (http_client, rx) = #{capture_request}(None); let config = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .invocation_id_generator(TestIdGen) .build(); assert!(config.invocation_id_generator().is_some()); diff --git a/aws/sdk/integration-tests/dynamodb/Cargo.toml b/aws/sdk/integration-tests/dynamodb/Cargo.toml index 61663dbbed..2a9d1725af 100644 --- a/aws/sdk/integration-tests/dynamodb/Cargo.toml +++ b/aws/sdk/integration-tests/dynamodb/Cargo.toml @@ -17,7 +17,6 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-dynamodb = { path = "../../build/aws-sdk/sdk/dynamodb" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"]} diff --git a/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs b/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs index 25d65bb94c..2f0180b449 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs @@ -4,6 +4,7 @@ */ use aws_sdk_dynamodb::config::{self, Credentials, Region}; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_types::SdkConfig; use http::Uri; @@ -12,11 +13,11 @@ async fn expect_uri( uri: &'static str, customize: fn(config::Builder) -> config::Builder, ) { - let (conn, request) = aws_smithy_client::test_connection::capture_request(None); + let (http_client, request) = capture_request(None); let conf = customize( aws_sdk_dynamodb::config::Builder::from(&conf) .credentials_provider(Credentials::for_tests()) - .http_connector(conn), + .http_client(http_client), ) .build(); let svc = aws_sdk_dynamodb::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs index 7b045c6f5b..85edad45bc 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/movies.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -4,8 +4,9 @@ */ use aws_sdk_dynamodb as dynamodb; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use dynamodb::config::{Credentials, Region}; use dynamodb::operation::query::QueryOutput; use dynamodb::types::{ @@ -151,10 +152,10 @@ async fn movies_it() { let table_name = "Movies-5"; // The waiter will retry 5 times tokio::time::pause(); - let conn = movies_it_test_connection(); // RecordingConnection::https(); + let http_client = movies_it_test_connection(); // RecordingConnection::https(); let conf = dynamodb::Config::builder() .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(Credentials::for_tests()) .build(); let client = Client::from_conf(conf); @@ -194,14 +195,14 @@ async fn movies_it() { ] ); - conn.assert_requests_match(&[AUTHORIZATION, HeaderName::from_static("x-amz-date")]); + http_client.assert_requests_match(&[AUTHORIZATION, HeaderName::from_static("x-amz-date")]); } /// Test connection for the movies IT /// headers are signed with actual creds, at some point we could replace them with verifiable test /// credentials, but there are plenty of other tests that target signing -fn movies_it_test_connection() -> TestConnection<&'static str> { - TestConnection::new(vec![( +fn movies_it_test_connection() -> EventClient { + EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.CreateTable") @@ -219,8 +220,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "3715137447") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.DescribeTable") .header("content-length", "24") @@ -237,8 +238,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "46742265") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.DescribeTable") .header("content-length", "24") @@ -254,8 +255,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "46742265") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.DescribeTable") .header("content-length", "24") @@ -272,8 +273,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "46742265") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.DescribeTable") .header("content-length", "24") @@ -290,8 +291,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "46742265") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.DescribeTable") .header("content-length", "24") @@ -308,8 +309,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "24113616") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.PutItem") .header("content-length", "619") @@ -326,8 +327,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "2745614147") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.PutItem") .header("content-length", "636") @@ -344,8 +345,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "2745614147") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.Query") .header("content-length", "156") @@ -362,8 +363,8 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "3413411624") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Count":0,"Items":[],"ScannedCount":0}"#).unwrap()), - (http::Request::builder() + .body(SdkBody::from(r#"{"Count":0,"Items":[],"ScannedCount":0}"#)).unwrap()), + ConnectionEvent::new(http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "DynamoDB_20120810.Query") .header("content-length", "156") @@ -380,6 +381,6 @@ fn movies_it_test_connection() -> TestConnection<&'static str> { .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") .header("x-amz-crc32", "624725176") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#).unwrap()) - ]) + .body(SdkBody::from(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#)).unwrap()) + ], TokioSleep::new()) } diff --git a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs index a3d0c62473..53bbc4ebbf 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs @@ -9,25 +9,27 @@ use std::iter::FromIterator; use aws_credential_types::Credentials; use aws_sdk_dynamodb::types::AttributeValue; use aws_sdk_dynamodb::{Client, Config}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_client::test_connection::{capture_request, TestConnection}; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; +use aws_smithy_runtime::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; +use aws_smithy_runtime_api::shared::IntoShared; use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; -fn stub_config(conn: impl Into) -> Config { +fn stub_config(http_client: impl IntoShared) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build() } /// Validate that arguments are passed on to the paginator #[tokio::test] async fn paginators_pass_args() { - let (conn, request) = capture_request(None); - let client = Client::from_conf(stub_config(conn)); + let (http_client, request) = capture_request(None); + let client = Client::from_conf(stub_config(http_client)); let mut paginator = client .scan() .table_name("test-table") @@ -57,11 +59,12 @@ fn mk_response(body: &'static str) -> http::Response { #[tokio::test(flavor = "current_thread")] async fn paginators_loop_until_completion() { - let conn = TestConnection::new(vec![ - ( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response( - r#"{ + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response( + r#"{ "Count": 1, "Items": [{ "PostedBy": { @@ -72,14 +75,14 @@ async fn paginators_loop_until_completion() { "PostedBy": { "S": "joe@example.com" } } }"#, + ), ), - ), - ( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, - ), - mk_response( - r#"{ + ConnectionEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ), + mk_response( + r#"{ "Count": 1, "Items": [{ "PostedBy": { @@ -87,17 +90,19 @@ async fn paginators_loop_until_completion() { } }] }"#, + ), ), - ), - ]); - let client = Client::from_conf(stub_config(conn.clone())); + ], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let mut paginator = client .scan() .table_name("test-table") .into_paginator() .page_size(32) .send(); - assert_eq!(conn.requests().len(), 0); + assert_eq!(http_client.actual_requests().count(), 0); let first_page = paginator .try_next() .await @@ -110,7 +115,7 @@ async fn paginators_loop_until_completion() { AttributeValue::S("joe@example.com".to_string()) )])] ); - assert_eq!(conn.requests().len(), 1); + assert_eq!(http_client.actual_requests().count(), 1); let second_page = paginator .try_next() .await @@ -123,36 +128,39 @@ async fn paginators_loop_until_completion() { AttributeValue::S("jack@example.com".to_string()) )])] ); - assert_eq!(conn.requests().len(), 2); + assert_eq!(http_client.actual_requests().count(), 2); assert!( paginator.next().await.is_none(), "no more pages should exist" ); // we shouldn't make another request, we know we're at the end - assert_eq!(conn.requests().len(), 2); - conn.assert_requests_match(&[]); + assert_eq!(http_client.actual_requests().count(), 2); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn paginators_handle_errors() { // LastEvaluatedKey is set but there is only one response in the test connection - let conn = TestConnection::new(vec![( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response( - r#"{ - "Count": 1, - "Items": [{ - "PostedBy": { - "S": "joe@example.com" - } - }], - "LastEvaluatedKey": { - "PostedBy": { "S": "joe@example.com" } - } - }"#, - ), - )]); - let client = Client::from_conf(stub_config(conn.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response( + r#"{ + "Count": 1, + "Items": [{ + "PostedBy": { + "S": "joe@example.com" + } + }], + "LastEvaluatedKey": { + "PostedBy": { "S": "joe@example.com" } + } + }"#, + ), + )], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") @@ -186,19 +194,22 @@ async fn paginators_stop_on_duplicate_token_by_default() { } }"#; // send the same response twice with the same pagination token - let conn = TestConnection::new(vec![ - ( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response(response), - ), - ( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response(response), ), - mk_response(response), - ), - ]); - let client = Client::from_conf(stub_config(conn.clone())); + ConnectionEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ), + mk_response(response), + ), + ], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") @@ -239,25 +250,28 @@ async fn paginators_can_continue_on_duplicate_token() { } }"#; // send the same response twice with the same pagination token - let conn = TestConnection::new(vec![ - ( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response(response), - ), - ( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response(response), + ), + ConnectionEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ), + mk_response(response), ), - mk_response(response), - ), - ( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ConnectionEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ), + mk_response(response), ), - mk_response(response), - ), - ]); - let client = Client::from_conf(stub_config(conn.clone())); + ], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") 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 211e8630a0..caf2fe2093 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 @@ -5,10 +5,11 @@ use aws_sdk_dynamodb::config::{Credentials, Region, SharedAsyncSleep}; use aws_sdk_dynamodb::{config::retry::RetryConfig, error::ProvideErrorMetadata}; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use aws_smithy_runtime::client::retries::RetryPartition; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; use std::time::{Duration, SystemTime}; @@ -51,20 +52,20 @@ async fn test_adaptive_retries_with_no_throttling_errors() { let events = vec![ // First operation - (req(), err()), - (req(), err()), - (req(), ok()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), ok()), // Second operation - (req(), err()), - (req(), ok()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), ok()), // Third operation will fail, only errors - (req(), err()), - (req(), err()), - (req(), err()), - (req(), err()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), err()), ]; - let conn = TestConnection::new(events); + let http_client = EventClient::new(events, TokioSleep::new()); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) @@ -78,7 +79,7 @@ async fn test_adaptive_retries_with_no_throttling_errors() { .retry_partition(RetryPartition::new( "test_adaptive_retries_with_no_throttling_errors", )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let expected_table_names = vec!["Test".to_owned()]; @@ -88,21 +89,21 @@ async fn test_adaptive_retries_with_no_throttling_errors() { assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success - assert_eq!(conn.requests().len(), 3); + assert_eq!(http_client.actual_requests().count(), 3); let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3 + 1)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) - assert_eq!(conn.requests().len(), 5); + assert_eq!(http_client.actual_requests().count(), 5); let client = aws_sdk_dynamodb::Client::from_conf(config); let err = client.list_tables().send().await.unwrap_err(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3 + 1 + 7),); assert_eq!(err.code(), Some("InternalServerError")); // four requests should have been made, all failing (plus previous requests) - assert_eq!(conn.requests().len(), 9); + assert_eq!(http_client.actual_requests().count(), 9); } #[tokio::test] @@ -111,15 +112,15 @@ async fn test_adaptive_retries_with_throttling_errors() { let events = vec![ // First operation - (req(), throttling_err()), - (req(), throttling_err()), - (req(), ok()), + ConnectionEvent::new(req(), throttling_err()), + ConnectionEvent::new(req(), throttling_err()), + ConnectionEvent::new(req(), ok()), // Second operation - (req(), err()), - (req(), ok()), + ConnectionEvent::new(req(), err()), + ConnectionEvent::new(req(), ok()), ]; - let conn = TestConnection::new(events); + let http_client = EventClient::new(events, TokioSleep::new()); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) @@ -133,7 +134,7 @@ async fn test_adaptive_retries_with_throttling_errors() { .retry_partition(RetryPartition::new( "test_adaptive_retries_with_throttling_errors", )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let expected_table_names = vec!["Test".to_owned()]; @@ -143,7 +144,7 @@ async fn test_adaptive_retries_with_throttling_errors() { assert_eq!(sleep_impl.total_duration(), Duration::from_secs(40)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success - assert_eq!(conn.requests().len(), 3); + assert_eq!(http_client.actual_requests().count(), 3); let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); @@ -151,5 +152,5 @@ async fn test_adaptive_retries_with_throttling_errors() { assert!(Duration::from_secs(49) > sleep_impl.total_duration()); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) - assert_eq!(conn.requests().len(), 5); + assert_eq!(http_client.actual_requests().count(), 5); } diff --git a/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs index 3d5edf8cb2..0ce9d0c9de 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs @@ -4,6 +4,7 @@ */ use aws_sdk_dynamodb::config::{Credentials, Region}; +use aws_smithy_runtime::client::http::test_util::capture_request; use http::Uri; /// Iterative test of loading clients from shared configuration @@ -12,10 +13,10 @@ async fn shared_config_testbed() { let shared_config = aws_types::SdkConfig::builder() .region(Region::new("us-east-4")) .build(); - let (conn, request) = aws_smithy_client::test_connection::capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_dynamodb::config::Builder::from(&shared_config) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .endpoint_url("http://localhost:8000") .build(); let svc = aws_sdk_dynamodb::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs index d1a9b9369e..abd63673a5 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs @@ -9,7 +9,7 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_dynamodb::error::SdkError; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::never::NeverConnector; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; @@ -25,10 +25,10 @@ impl AsyncSleep for InstantSleep { #[tokio::test] async fn api_call_timeout_retries() { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() @@ -45,7 +45,7 @@ async fn api_call_timeout_retries() { .await .expect_err("call should fail"); assert_eq!( - conn.num_calls(), + http_client.num_calls(), 3, "client level timeouts should be retried" ); @@ -58,10 +58,10 @@ async fn api_call_timeout_retries() { #[tokio::test] async fn no_retries_on_operation_timeout() { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() @@ -78,7 +78,7 @@ async fn no_retries_on_operation_timeout() { .await .expect_err("call should fail"); assert_eq!( - conn.num_calls(), + http_client.num_calls(), 1, "operation level timeouts should not be retried" ); diff --git a/aws/sdk/integration-tests/ec2/Cargo.toml b/aws/sdk/integration-tests/ec2/Cargo.toml index 853b1b594f..9e2757bea2 100644 --- a/aws/sdk/integration-tests/ec2/Cargo.toml +++ b/aws/sdk/integration-tests/ec2/Cargo.toml @@ -8,8 +8,11 @@ publish = false [dev-dependencies] aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } +aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } +aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } aws-sdk-ec2 = { path = "../../build/aws-sdk/sdk/ec2" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util"]} tokio = { version = "1.23.1", features = ["full"]} http = "0.2.0" tokio-stream = "0.1.5" diff --git a/aws/sdk/integration-tests/ec2/tests/paginators.rs b/aws/sdk/integration-tests/ec2/tests/paginators.rs index d070971a4f..19da3d85c1 100644 --- a/aws/sdk/integration-tests/ec2/tests/paginators.rs +++ b/aws/sdk/integration-tests/ec2/tests/paginators.rs @@ -4,14 +4,17 @@ */ use aws_sdk_ec2::{config::Credentials, config::Region, types::InstanceType, Client, Config}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; +use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::shared::IntoShared; -fn stub_config(conn: impl Into) -> Config { +fn stub_config(http_client: impl IntoShared) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build() } @@ -27,17 +30,20 @@ async fn paginators_handle_empty_tokens() { "#; - let conn = TestConnection::<&str>::new(vec![( - http::Request::builder() - .uri("https://ec2.us-east-1.amazonaws.com/") - .body(request.into()) - .unwrap(), - http::Response::builder() - .status(200) - .body(response) - .unwrap(), - )]); - let client = Client::from_conf(stub_config(conn.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + http::Request::builder() + .uri("https://ec2.us-east-1.amazonaws.com/") + .body(request.into()) + .unwrap(), + http::Response::builder() + .status(200) + .body(SdkBody::from(response)) + .unwrap(), + )], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client .describe_spot_price_history() @@ -49,7 +55,7 @@ async fn paginators_handle_empty_tokens() { .send(); let first_item = paginator.try_next().await.expect("success"); assert_eq!(first_item, None); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// See https://github.com/awslabs/aws-sdk-rust/issues/405 @@ -63,17 +69,20 @@ async fn paginators_handle_unset_tokens() { edf3e86c-4baf-47c1-9228-9a5ea09542e8 "#; - let conn = TestConnection::<&str>::new(vec![( - http::Request::builder() - .uri("https://ec2.us-east-1.amazonaws.com/") - .body(request.into()) - .unwrap(), - http::Response::builder() - .status(200) - .body(response) - .unwrap(), - )]); - let client = Client::from_conf(stub_config(conn.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + http::Request::builder() + .uri("https://ec2.us-east-1.amazonaws.com/") + .body(request.into()) + .unwrap(), + http::Response::builder() + .status(200) + .body(SdkBody::from(response)) + .unwrap(), + )], + TokioSleep::new(), + ); + let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client .describe_spot_price_history() @@ -85,5 +94,5 @@ async fn paginators_handle_unset_tokens() { .send(); let first_item = paginator.try_next().await.expect("success"); assert_eq!(first_item, None); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/glacier/Cargo.toml b/aws/sdk/integration-tests/glacier/Cargo.toml index 4c4ce34887..ca08a6e349 100644 --- a/aws/sdk/integration-tests/glacier/Cargo.toml +++ b/aws/sdk/integration-tests/glacier/Cargo.toml @@ -14,8 +14,8 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-glacier = { path = "../../build/aws-sdk/sdk/glacier" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test"} +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } bytes = "1.0.0" http = "0.2.0" tokio = { version = "1.23.1", features = ["full", "test-util"]} diff --git a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs index 941ed0a999..c2cd3384ec 100644 --- a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs +++ b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs @@ -5,16 +5,16 @@ use aws_sdk_glacier::config::{Credentials, Region}; use aws_sdk_glacier::primitives::ByteStream; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_protocol_test::{assert_ok, validate_headers}; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn set_correct_headers() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); @@ -42,11 +42,11 @@ async fn set_correct_headers() { #[tokio::test] async fn autofill_account_id() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); @@ -65,11 +65,11 @@ async fn autofill_account_id() { #[tokio::test] async fn api_version_set() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/iam/Cargo.toml b/aws/sdk/integration-tests/iam/Cargo.toml index 9c7b6b7464..e1d358ea44 100644 --- a/aws/sdk/integration-tests/iam/Cargo.toml +++ b/aws/sdk/integration-tests/iam/Cargo.toml @@ -15,7 +15,7 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-endpoint = { path = "../../build/aws-sdk/sdk/aws-endpoint"} aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-iam = { path = "../../build/aws-sdk/sdk/iam" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs b/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs index 923bf568f6..6ef167ba0a 100644 --- a/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs +++ b/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs @@ -4,18 +4,18 @@ */ use aws_sdk_iam::config::{Credentials, Region}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; // this test is ignored because pseudoregions have been removed. This test should be re-enabled // once FIPS support is added in aws-config #[tokio::test] #[ignore] async fn correct_endpoint_resolver() { - let (conn, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_iam::Config::builder() .region(Region::from_static("iam-fips")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_iam::Client::from_conf(conf); let _ = client.list_roles().send().await; diff --git a/aws/sdk/integration-tests/kms/Cargo.toml b/aws/sdk/integration-tests/kms/Cargo.toml index 2c76644e94..a26e2dd672 100644 --- a/aws/sdk/integration-tests/kms/Cargo.toml +++ b/aws/sdk/integration-tests/kms/Cargo.toml @@ -15,10 +15,10 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime" } aws-sdk-kms = { path = "../../build/aws-sdk/sdk/kms" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs index 9125ec12e9..1db0bb1331 100644 --- a/aws/sdk/integration-tests/kms/tests/integration.rs +++ b/aws/sdk/integration-tests/kms/tests/integration.rs @@ -5,9 +5,10 @@ use aws_sdk_kms as kms; use aws_sdk_kms::operation::RequestId; -use aws_smithy_client::test_connection::TestConnection; -use aws_smithy_client::SdkError; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use http::header::AUTHORIZATION; use http::Uri; use kms::config::{Config, Credentials, Region}; @@ -20,16 +21,16 @@ use std::time::{Duration, UNIX_EPOCH}; /// Validate that for CN regions we set the URI correctly #[tokio::test] async fn generate_random_cn() { - let conn = TestConnection::new(vec![( + let http_client= EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .uri(Uri::from_static("https://kms.cn-north-1.amazonaws.com.cn/")) .body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#).unwrap()) - ]); + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) + ], TokioSleep::new()); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("cn-north-1")) .credentials_provider(Credentials::for_tests()) .build(); @@ -41,13 +42,13 @@ async fn generate_random_cn() { .await .expect("success"); - assert_eq!(conn.requests().len(), 1); - conn.assert_requests_match(&[]); + assert_eq!(http_client.actual_requests().count(), 1); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn generate_random() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -61,10 +62,10 @@ async fn generate_random() { .body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#).unwrap()) - ]); + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) + ], TokioSleep::new()); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -94,20 +95,20 @@ async fn generate_random() { .sum::(), 8562 ); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn generate_random_malformed_response() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder().body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) // last `}` replaced with a space, invalid JSON - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA==" "#).unwrap()) - ]); + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA==" "#)).unwrap()) + ], TokioSleep::new()); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) .build(); @@ -122,7 +123,7 @@ async fn generate_random_malformed_response() { #[tokio::test] async fn generate_random_keystore_not_found() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -143,10 +144,10 @@ async fn generate_random_keystore_not_found() { .header("date", "Fri, 05 Mar 2021 15:01:40 GMT") .header("content-type", "application/x-amz-json-1.1") .header("content-length", "44") - .body(r#"{"__type":"CustomKeyStoreNotFoundException"}"#).unwrap()) - ]); + .body(SdkBody::from(r#"{"__type":"CustomKeyStoreNotFoundException"}"#)).unwrap()) + ], TokioSleep::new()); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -174,5 +175,5 @@ async fn generate_random_keystore_not_found() { inner.request_id(), Some("bfe81a0a-9a08-4e71-9910-cdb5ab6ea3b6") ); - conn.assert_requests_match(&[AUTHORIZATION]); + http_client.assert_requests_match(&[AUTHORIZATION]); } diff --git a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs index 5eee47d9d2..1b932e4be4 100644 --- a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs +++ b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs @@ -6,8 +6,8 @@ use aws_credential_types::Credentials; use aws_runtime::retries::classifier::AwsErrorCodeClassifier; use aws_sdk_kms as kms; -use aws_smithy_client::test_connection::infallible_connection_fn; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, InterceptorContext}; use aws_smithy_runtime_api::client::orchestrator::{HttpResponse, OrchestratorError}; use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryReason}; @@ -18,9 +18,9 @@ use kms::operation::create_alias::CreateAliasError; async fn make_err( response: impl Fn() -> http::Response + Send + Sync + 'static, ) -> SdkError { - let conn = infallible_connection_fn(move |_| response()); + let http_client = infallible_client_fn(move |_| response()); let conf = kms::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(kms::config::Region::from_static("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/lambda/Cargo.toml b/aws/sdk/integration-tests/lambda/Cargo.toml index 6e51d089f5..5f6cc8acbd 100644 --- a/aws/sdk/integration-tests/lambda/Cargo.toml +++ b/aws/sdk/integration-tests/lambda/Cargo.toml @@ -9,12 +9,12 @@ publish = false [dev-dependencies] async-stream = "0.3.0" -aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-lambda = { path = "../../build/aws-sdk/sdk/lambda" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-eventstream = { path = "../../build/aws-sdk/sdk/aws-smithy-eventstream" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } base64 = "0.13.0" bytes = "1.0.0" futures-core = "0.3.14" diff --git a/aws/sdk/integration-tests/lambda/tests/request_id.rs b/aws/sdk/integration-tests/lambda/tests/request_id.rs index b4204b0888..6a6e1384ae 100644 --- a/aws/sdk/integration-tests/lambda/tests/request_id.rs +++ b/aws/sdk/integration-tests/lambda/tests/request_id.rs @@ -7,15 +7,15 @@ use aws_sdk_lambda::config::{Credentials, Region}; use aws_sdk_lambda::operation::list_functions::ListFunctionsError; use aws_sdk_lambda::operation::RequestId; use aws_sdk_lambda::{Client, Config}; -use aws_smithy_client::test_connection::infallible_connection_fn; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; async fn run_test( response: impl Fn() -> http::Response<&'static str> + Send + Sync + 'static, expect_error: bool, ) { - let conn = infallible_connection_fn(move |_| response()); + let http_client = infallible_client_fn(move |_| response()); let conf = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::from_static("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/polly/Cargo.toml b/aws/sdk/integration-tests/polly/Cargo.toml index 5412a3dcaf..444c65beaf 100644 --- a/aws/sdk/integration-tests/polly/Cargo.toml +++ b/aws/sdk/integration-tests/polly/Cargo.toml @@ -14,7 +14,6 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-polly = { path = "../../build/aws-sdk/sdk/polly" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/qldbsession/Cargo.toml b/aws/sdk/integration-tests/qldbsession/Cargo.toml index ef09721a84..59256fb6cd 100644 --- a/aws/sdk/integration-tests/qldbsession/Cargo.toml +++ b/aws/sdk/integration-tests/qldbsession/Cargo.toml @@ -14,8 +14,9 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-qldbsession = { path = "../../build/aws-sdk/sdk/qldbsession" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } http = "0.2.0" tokio = { version = "1.23.1", features = ["full"]} diff --git a/aws/sdk/integration-tests/qldbsession/tests/integration.rs b/aws/sdk/integration-tests/qldbsession/tests/integration.rs index 816f3cd8fb..47d241904a 100644 --- a/aws/sdk/integration-tests/qldbsession/tests/integration.rs +++ b/aws/sdk/integration-tests/qldbsession/tests/integration.rs @@ -6,20 +6,21 @@ use aws_sdk_qldbsession::config::{Config, Credentials, Region}; use aws_sdk_qldbsession::types::StartSessionRequest; use aws_sdk_qldbsession::Client; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use http::Uri; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn signv4_use_correct_service_name() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "QLDBSession.SendCommand") .header("content-length", "49") .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210305/us-east-1/qldb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-security-token;x-amz-target;x-amz-user-agent, Signature=350f957e9b736ac3f636d16c59c0a3cee8c2780b0ffadc99bbca841b7f15bee4") - // qldbsession uses the signing name 'qldb' in signature ____________________________________^^^^ + // qldbsession uses the signing name 'qldb' in signature _________________________^^^^ .header("x-amz-date", "20210305T134922Z") .header("x-amz-security-token", "notarealsessiontoken") .header("user-agent", "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0") @@ -27,10 +28,10 @@ async fn signv4_use_correct_service_name() { .body(SdkBody::from(r#"{"StartSession":{"LedgerName":"not-real-ledger"}}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), - ]); + .body(SdkBody::from(r#"{}"#)).unwrap()), + ], TokioSleep::new()); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -58,5 +59,5 @@ async fn signv4_use_correct_service_name() { .await .expect("request should succeed"); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index 74453e21e6..a435389577 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -19,7 +19,6 @@ aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime", features = ["test- aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3" } aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util", "rt-tokio"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "wiremock"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } 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 a83e9f38d9..7aee8af904 100644 --- a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -13,8 +13,8 @@ use aws_sdk_s3::types::{ use aws_sdk_s3::{Client, Config}; use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::never::NeverConnector; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::timeout::TimeoutConfig; @@ -87,14 +87,14 @@ fn test_async_std_runtime_retry() { } async fn timeout_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box> { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let region = Region::from_static("us-east-2"); let timeout_config = TimeoutConfig::builder() .operation_timeout(Duration::from_secs_f32(0.5)) .build(); let config = Config::builder() .region(region) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(Credentials::for_tests()) .timeout_config(timeout_config) .sleep_impl(sleep_impl) @@ -141,10 +141,10 @@ async fn timeout_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box Result<(), Box> { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = aws_types::SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .retry_config(RetryConfig::standard().with_max_attempts(3)) .timeout_config( @@ -167,7 +167,7 @@ async fn retry_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box TestConnection<&'static str> { - TestConnection::new(vec![ - (http::Request::builder() +) -> EventClient { + EventClient::new(vec![ + ConnectionEvent::new(http::Request::builder() .header("x-amz-checksum-mode", "ENABLED") .header("user-agent", "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -45,15 +46,15 @@ fn new_checksum_validated_response_test_connection( .header("x-amz-id-2", "kPl+IVVZAwsN8ePUyQJZ40WD9dzaqtr4eNESArqE68GSKtVvuvCTDe+SxhTT+JTUqXB1HL4OxNM=") .header("accept-ranges", "bytes") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"Hello world"#).unwrap()), - ]) + .body(SdkBody::from(r#"Hello world"#)).unwrap()), + ], TokioSleep::new()) } async fn test_checksum_on_streaming_response( checksum_header_name: &'static str, checksum_header_value: &'static str, ) -> GetObjectOutput { - let conn = new_checksum_validated_response_test_connection( + let http_client = new_checksum_validated_response_test_connection( checksum_header_name, checksum_header_value, ); @@ -61,7 +62,7 @@ async fn test_checksum_on_streaming_response( .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .time_source(UNIX_EPOCH + Duration::from_secs(1624036048)) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -79,7 +80,7 @@ async fn test_checksum_on_streaming_response( .await .unwrap(); - conn.assert_requests_match(&[ + http_client.assert_requests_match(&[ http::header::HeaderName::from_static("x-amz-checksum-mode"), AUTHORIZATION, ]); @@ -149,11 +150,11 @@ async fn test_checksum_on_streaming_request<'a>( expected_encoded_content_length: &'a str, expected_aws_chunked_encoded_body: &'a str, ) { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -330,7 +331,7 @@ async fn collect_body_into_string(mut body: aws_smithy_http::body::SdkBody) -> S #[traced_test] async fn test_get_multipart_upload_part_checksum_validation() { let expected_checksum = "cpjwid==-12"; - let (conn, rcvr) = capture_request(Some( + let (http_client, rcvr) = capture_request(Some( http::Response::builder() .header("etag", "\"3e25960a79dbc69b674cd4ec67a72c62\"") .header("x-amz-checksum-crc32", expected_checksum) @@ -340,7 +341,7 @@ async fn test_get_multipart_upload_part_checksum_validation() { let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -376,7 +377,7 @@ async fn test_get_multipart_upload_part_checksum_validation() { #[traced_test] async fn test_response_checksum_ignores_invalid_base64() { let expected_checksum = "{}{!!#{})!{)@$(}"; - let (conn, rcvr) = capture_request(Some( + let (http_client, rcvr) = capture_request(Some( http::Response::builder() .header("etag", "\"3e25960a79dbc69b674cd4ec67a72c62\"") .header("x-amz-checksum-crc32", expected_checksum) @@ -386,7 +387,7 @@ async fn test_response_checksum_ignores_invalid_base64() { let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/config-override.rs b/aws/sdk/integration-tests/s3/tests/config-override.rs index 28092f37ce..44c7006888 100644 --- a/aws/sdk/integration-tests/s3/tests/config-override.rs +++ b/aws/sdk/integration-tests/s3/tests/config-override.rs @@ -6,15 +6,15 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; +use aws_smithy_runtime::client::http::test_util::{capture_request, CaptureRequestReceiver}; use aws_types::SdkConfig; fn test_client() -> (CaptureRequestReceiver, Client) { - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-2")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::new(&sdk_config); (captured_request, client) diff --git a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs index 7621393eca..6bd55816c0 100644 --- a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs +++ b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs @@ -7,19 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; - +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_ops_are_customizable() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/endpoints.rs b/aws/sdk/integration-tests/s3/tests/endpoints.rs index 7a76b24087..5ac1ac7ad8 100644 --- a/aws/sdk/integration-tests/s3/tests/endpoints.rs +++ b/aws/sdk/integration-tests/s3/tests/endpoints.rs @@ -8,15 +8,15 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::Builder; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; +use aws_smithy_runtime::client::http::test_util::{capture_request, CaptureRequestReceiver}; use std::time::{Duration, UNIX_EPOCH}; fn test_client(update_builder: fn(Builder) -> Builder) -> (CaptureRequestReceiver, Client) { - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-4")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::from_conf(update_builder(Builder::from(&sdk_config)).build()); (captured_request, client) diff --git a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs index 07bf543b8c..a0d5d8c370 100644 --- a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs +++ b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs @@ -5,8 +5,9 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{config::Credentials, config::Region, types::ObjectAttributes, Client}; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use aws_types::SdkConfig; use http::header::AUTHORIZATION; use std::time::{Duration, UNIX_EPOCH}; @@ -15,8 +16,8 @@ const RESPONSE_BODY_XML: &[u8] = b"\n< #[tokio::test] async fn ignore_invalid_xml_body_root() { - let conn = TestConnection::new(vec![ - (http::Request::builder() + let http_client = EventClient::new(vec![ + ConnectionEvent::new(http::Request::builder() .header("x-amz-object-attributes", "Checksum") .header("x-amz-user-agent", "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -37,16 +38,16 @@ async fn ignore_invalid_xml_body_root() { .header("server", "AmazonS3") .header("content-length", "224") .status(200) - .body(RESPONSE_BODY_XML) + .body(SdkBody::from(RESPONSE_BODY_XML)) .unwrap()) - ]); + ], TokioSleep::new()); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -64,5 +65,5 @@ async fn ignore_invalid_xml_body_root() { .await .unwrap(); - conn.assert_requests_match(&[AUTHORIZATION]); + http_client.assert_requests_match(&[AUTHORIZATION]); } diff --git a/aws/sdk/integration-tests/s3/tests/interceptors.rs b/aws/sdk/integration-tests/s3/tests/interceptors.rs index 825f895fac..62bc29b039 100644 --- a/aws/sdk/integration-tests/s3/tests/interceptors.rs +++ b/aws/sdk/integration-tests/s3/tests/interceptors.rs @@ -6,8 +6,7 @@ use aws_sdk_s3::config::interceptors::BeforeTransmitInterceptorContextMut; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -57,13 +56,13 @@ async fn interceptor_priority() { } } - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); // The first `TestInterceptor` will put `value1` into config let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn)) + .http_client(http_client) .interceptor(TestInterceptor("value1")) .build(); let client = Client::from_conf(config); @@ -89,12 +88,12 @@ async fn interceptor_priority() { #[tokio::test] async fn set_test_user_agent_through_request_mutation() { - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn.clone())) + .http_client(http_client.clone()) .build(); let client = Client::from_conf(config); diff --git a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs index e67da16c27..209fd6bcde 100644 --- a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs +++ b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs @@ -5,7 +5,7 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{config::Credentials, config::Region, primitives::ByteStream, Client}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_types::SdkConfig; use http::HeaderValue; use std::time::{Duration, UNIX_EPOCH}; @@ -48,13 +48,13 @@ const NAUGHTY_STRINGS: &str = include_str!("blns/blns.txt"); #[tokio::test] async fn test_s3_signer_with_naughty_string_metadata() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let config = aws_sdk_s3::config::Builder::from(&sdk_config) .force_path_style(true) diff --git a/aws/sdk/integration-tests/s3/tests/no_auth.rs b/aws/sdk/integration-tests/s3/tests/no_auth.rs index b558a90494..670d85268c 100644 --- a/aws/sdk/integration-tests/s3/tests/no_auth.rs +++ b/aws/sdk/integration-tests/s3/tests/no_auth.rs @@ -3,17 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_smithy_client::dvr::ReplayingConnection; use aws_smithy_protocol_test::MediaType; +use aws_smithy_runtime::client::http::test_util::dvr::ReplayingClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; #[tokio::test] async fn list_objects() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/list-objects.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/list-objects.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -33,7 +33,8 @@ async fn list_objects() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -42,9 +43,10 @@ async fn list_objects() { async fn list_objects_v2() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/list-objects-v2.json").unwrap(); + let http_client = + ReplayingClient::from_file("tests/data/no_auth/list-objects-v2.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -64,7 +66,8 @@ async fn list_objects_v2() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -73,9 +76,9 @@ async fn list_objects_v2() { async fn head_object() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/head-object.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/head-object.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -95,7 +98,8 @@ async fn head_object() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -104,9 +108,9 @@ async fn head_object() { async fn get_object() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/get-object.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/get-object.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -126,7 +130,8 @@ async fn get_object() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } diff --git a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs index d2b6f4a000..e91af82846 100644 --- a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs +++ b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::{config::Credentials, config::Region, Client}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_operation_should_not_normalize_uri_path() { - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs index bc67936924..67557f6e05 100644 --- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs +++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_signer_query_string_with_all_valid_chars() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/reconnects.rs b/aws/sdk/integration-tests/s3/tests/reconnects.rs index cb29bb2a66..83583395dd 100644 --- a/aws/sdk/integration-tests/s3/tests/reconnects.rs +++ b/aws/sdk/integration-tests/s3/tests/reconnects.rs @@ -6,10 +6,6 @@ use aws_sdk_s3::config::retry::{ReconnectMode, RetryConfig}; use aws_sdk_s3::config::{Credentials, Region, SharedAsyncSleep}; use aws_smithy_async::rt::sleep::TokioSleep; -use aws_smithy_client::test_connection::wire_mock::{ - check_matches, ReplayedEvent, WireLevelTestConnection, -}; -use aws_smithy_client::{ev, match_events}; #[tokio::test] async fn test_disable_reconnect_on_503() { @@ -25,7 +21,7 @@ async fn test_disable_reconnect_on_503() { .credentials_provider(Credentials::for_tests()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .endpoint_url(mock.endpoint_url()) - .http_connector(mock.http_connector()) + .http_client(mock.http_client()) .retry_config( RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReuseAllConnections), ) @@ -65,7 +61,7 @@ async fn test_enabling_reconnect_on_503() { .credentials_provider(Credentials::for_tests()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .endpoint_url(mock.endpoint_url()) - .http_connector(mock.http_connector()) + .http_client(mock.http_client()) .retry_config( RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReconnectOnTransientError), ) diff --git a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs index f0aa974d8e..5bd7b26522 100644 --- a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs +++ b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use http::HeaderValue; #[tokio::test] async fn recursion_detection_applied() { std::env::set_var("AWS_LAMBDA_FUNCTION_NAME", "some-function"); std::env::set_var("_X_AMZN_TRACE_ID", "traceid"); - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); let _ = client.list_objects_v2().bucket("test-bucket").send().await; diff --git a/aws/sdk/integration-tests/s3/tests/request_id.rs b/aws/sdk/integration-tests/s3/tests/request_id.rs index 1df48038f2..d46bfe66da 100644 --- a/aws/sdk/integration-tests/s3/tests/request_id.rs +++ b/aws/sdk/integration-tests/s3/tests/request_id.rs @@ -6,12 +6,12 @@ use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::operation::{RequestId, RequestIdExt}; use aws_sdk_s3::{config::Credentials, config::Region, Client, Config}; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn get_request_id_from_modeled_error() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -28,7 +28,7 @@ async fn get_request_id_from_modeled_error() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -60,7 +60,7 @@ async fn get_request_id_from_modeled_error() { #[tokio::test] async fn get_request_id_from_unmodeled_error() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -77,7 +77,7 @@ async fn get_request_id_from_unmodeled_error() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -106,7 +106,7 @@ async fn get_request_id_from_unmodeled_error() { #[tokio::test] async fn get_request_id_from_successful_nonstreaming_response() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -121,7 +121,7 @@ async fn get_request_id_from_successful_nonstreaming_response() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -141,7 +141,7 @@ async fn get_request_id_from_successful_nonstreaming_response() { #[tokio::test] async fn get_request_id_from_successful_streaming_response() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -150,7 +150,7 @@ async fn get_request_id_from_successful_streaming_response() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -173,7 +173,7 @@ async fn get_request_id_from_successful_streaming_response() { // Verify that the conversion from operation error to the top-level service error maintains the request ID #[tokio::test] async fn conversion_to_service_error_maintains_request_id() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -190,7 +190,7 @@ async fn conversion_to_service_error_maintains_request_id() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs index 8c47e8b367..ab0e8d651d 100644 --- a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs +++ b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs @@ -15,9 +15,8 @@ use aws_sdk_s3::Client; use aws_smithy_async::test_util::InstantSleep; use aws_smithy_async::test_util::ManualTimeSource; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::dvr; -use aws_smithy_client::dvr::MediaType; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_protocol_test::MediaType; +use aws_smithy_runtime::client::http::test_util::dvr::ReplayingClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -72,11 +71,11 @@ async fn three_retries_and_then_success() { let time_source = ManualTimeSource::new(UNIX_EPOCH + Duration::from_secs(1559347200)); let path = "tests/data/request-information-headers/three-retries_and-then-success.json"; - let conn = dvr::ReplayingConnection::from_file(path).unwrap(); + let http_client = ReplayingClient::from_file(path).unwrap(); let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests_with_session_token()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn.clone())) + .http_client(http_client.clone()) .time_source(SharedTimeSource::new(time_source.clone())) .sleep_impl(SharedAsyncSleep::new(InstantSleep::new(Default::default()))) .retry_config(RetryConfig::standard()) @@ -104,7 +103,10 @@ async fn three_retries_and_then_success() { let resp = resp.expect("valid e2e test"); assert_eq!(resp.name(), Some("test-bucket")); - conn.full_validate(MediaType::Xml).await.expect("failed") + http_client + .full_validate(MediaType::Xml) + .await + .expect("failed") } // // // # Client makes 3 separate SDK operation invocations @@ -168,7 +170,7 @@ async fn three_retries_and_then_success() { // let config = aws_sdk_s3::Config::builder() // .credentials_provider(Credentials::for_tests()) // .region(Region::new("us-east-1")) -// .http_connector(DynConnector::new(conn.clone())) +// .http_client(DynConnector::new(conn.clone())) // .build(); // let client = Client::from_conf(config); // let fixup = FixupPlugin { @@ -259,7 +261,7 @@ async fn three_retries_and_then_success() { // let config = aws_sdk_s3::Config::builder() // .credentials_provider(Credentials::for_tests()) // .region(Region::new("us-east-1")) -// .http_connector(DynConnector::new(conn.clone())) +// .http_client(DynConnector::new(conn.clone())) // .build(); // let client = Client::from_conf(config); // let fixup = FixupPlugin { diff --git a/aws/sdk/integration-tests/s3/tests/required-query-params.rs b/aws/sdk/integration-tests/s3/tests/required-query-params.rs index b5fede83a0..1df7f44e4e 100644 --- a/aws/sdk/integration-tests/s3/tests/required-query-params.rs +++ b/aws/sdk/integration-tests/s3/tests/required-query-params.rs @@ -6,14 +6,14 @@ use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::error::DisplayErrorContext; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_http::operation::error::BuildError; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn test_error_when_required_query_param_is_unset() { - let (conn, _request) = capture_request(None); + let (http_client, _request) = capture_request(None); let config = aws_sdk_s3::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -36,9 +36,9 @@ async fn test_error_when_required_query_param_is_unset() { #[tokio::test] async fn test_error_when_required_query_param_is_set_but_empty() { - let (conn, _request) = capture_request(None); + let (http_client, _request) = capture_request(None); let config = aws_sdk_s3::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/select-object-content.rs b/aws/sdk/integration-tests/s3/tests/select-object-content.rs index eb4d7a055c..4ba1e49163 100644 --- a/aws/sdk/integration-tests/s3/tests/select-object-content.rs +++ b/aws/sdk/integration-tests/s3/tests/select-object-content.rs @@ -11,19 +11,19 @@ use aws_sdk_s3::types::{ OutputSerialization, SelectObjectContentEventStream, }; use aws_sdk_s3::Client; -use aws_smithy_client::dvr::{Event, ReplayingConnection}; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; +use aws_smithy_runtime::client::http::test_util::dvr::{Event, ReplayingClient}; use std::error::Error; #[tokio::test] async fn test_success() { let events: Vec = serde_json::from_str(include_str!("select-object-content.json")).unwrap(); - let replayer = ReplayingConnection::new(events); + let replayer = ReplayingClient::new(events); let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .http_connector(replayer.clone()) + .http_client(replayer.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/make-connector-override.rs b/aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs similarity index 58% rename from aws/sdk/integration-tests/s3/tests/make-connector-override.rs rename to aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs index 90eb79d70c..ba014b0d7f 100644 --- a/aws/sdk/integration-tests/s3/tests/make-connector-override.rs +++ b/aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs @@ -6,54 +6,13 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; - -use aws_smithy_client::http_connector::{ConnectorSettings, HttpConnector}; -use aws_smithy_client::test_connection; - use aws_smithy_http::result::SdkError; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; use aws_types::SdkConfig; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; use std::time::Duration; use tokio::time::Instant; -/// Verify that `make_connector_fn` isn't called per request -#[tokio::test] -async fn make_connector_fn_test() { - let sentinel = Arc::new(AtomicUsize::new(0)); - let connector_sentinel = sentinel.clone(); - let connector_with_counter = HttpConnector::ConnectorFn(Arc::new( - move |_settings: &ConnectorSettings, _sleep: Option| { - connector_sentinel.fetch_add(1, Ordering::Relaxed); - Some(test_connection::infallible_connection_fn(|_req| { - http::Response::builder().status(200).body("ok!").unwrap() - })) - }, - )); - let sdk_config = SdkConfig::builder() - .http_connector(connector_with_counter) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .region(Region::from_static("us-east-1")) - .build(); - let client = aws_sdk_s3::Client::new(&sdk_config); - assert_eq!(sentinel.load(Ordering::Relaxed), 1); - for _ in 0..10 { - let _ = client - .get_object() - .bucket("foo") - .key("bar") - .send() - .await - .expect("test connector replies with 200"); - } - assert_eq!(sentinel.load(Ordering::Relaxed), 1); - // but creating another client creates another connector - let _client_2 = aws_sdk_s3::Client::new(&sdk_config); - assert_eq!(sentinel.load(Ordering::Relaxed), 2); -} - /// Use a 5 second operation timeout on SdkConfig and a 0ms connect timeout on the service config #[tokio::test] async fn timeouts_can_be_set_by_service() { diff --git a/aws/sdk/integration-tests/s3/tests/signing-it.rs b/aws/sdk/integration-tests/s3/tests/signing-it.rs index 46e1859f0a..4c0c9fbcc4 100644 --- a/aws/sdk/integration-tests/s3/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3/tests/signing-it.rs @@ -7,26 +7,27 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210618/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=ae78f74d26b6b0c3a403d9e8cc7ec3829d6264a2b33db672bf2b151bbb901786") .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=prefix~") .body(SdkBody::empty()) .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), + )], TokioSleep::new()); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); let _ = client @@ -41,5 +42,5 @@ async fn test_signer() { .send() .await; - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/s3/tests/status-200-errors.rs b/aws/sdk/integration-tests/s3/tests/status-200-errors.rs index 5fee498ec6..ad53f1fedb 100644 --- a/aws/sdk/integration-tests/s3/tests/status-200-errors.rs +++ b/aws/sdk/integration-tests/s3/tests/status-200-errors.rs @@ -6,8 +6,8 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::infallible_connection_fn; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; use aws_smithy_types::error::metadata::ProvideErrorMetadata; use aws_types::region::Region; use aws_types::SdkConfig; @@ -23,11 +23,12 @@ const ERROR_RESPONSE: &str = r#" #[tokio::test] async fn status_200_errors() { - let conn = infallible_connection_fn(|_req| http::Response::new(SdkBody::from(ERROR_RESPONSE))); + let http_client = + infallible_client_fn(|_req| http::Response::new(SdkBody::from(ERROR_RESPONSE))); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-4")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::new(&sdk_config); let error = client diff --git a/aws/sdk/integration-tests/s3/tests/timeouts.rs b/aws/sdk/integration-tests/s3/tests/timeouts.rs index 59359ad20b..da324e80fa 100644 --- a/aws/sdk/integration-tests/s3/tests/timeouts.rs +++ b/aws/sdk/integration-tests/s3/tests/timeouts.rs @@ -13,7 +13,7 @@ use aws_sdk_s3::types::{ use aws_sdk_s3::Client; use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep, TokioSleep}; -use aws_smithy_client::never::NeverConnector; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::timeout::TimeoutConfig; use std::future::Future; @@ -27,7 +27,7 @@ async fn test_timeout_service_ends_request_that_never_completes() { let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .http_connector(NeverConnector::new()) + .http_client(NeverClient::new()) .timeout_config( TimeoutConfig::builder() .operation_timeout(Duration::from_secs_f32(0.5)) diff --git a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs index 2dfea8d37b..c9cb041bbd 100644 --- a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs +++ b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs @@ -7,15 +7,15 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{AppName, Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn user_agent_app_name() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .app_name(AppName::new("test-app-name").expect("valid app name")) // set app name in config .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3control/Cargo.toml b/aws/sdk/integration-tests/s3control/Cargo.toml index beeb8e9177..723f8c964a 100644 --- a/aws/sdk/integration-tests/s3control/Cargo.toml +++ b/aws/sdk/integration-tests/s3control/Cargo.toml @@ -14,7 +14,8 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-s3control = { path = "../../build/aws-sdk/sdk/s3control" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } bytes = "1.0.0" diff --git a/aws/sdk/integration-tests/s3control/tests/signing-it.rs b/aws/sdk/integration-tests/s3control/tests/signing-it.rs index 4b207f9835..4db551c7b3 100644 --- a/aws/sdk/integration-tests/s3control/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3control/tests/signing-it.rs @@ -6,14 +6,15 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3control::config::{Credentials, Region}; use aws_sdk_s3control::Client; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use aws_types::SdkConfig; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let conn = TestConnection::new(vec![( + let http_client = EventClient::new(vec![ConnectionEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20211112/us-east-1/s3/aws4_request, \ @@ -22,13 +23,13 @@ async fn test_signer() { .uri("https://test-bucket.s3-control.us-east-1.amazonaws.com/v20180820/accesspoint") .body(SdkBody::empty()) .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), + )], TokioSleep::new()); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .build(); let client = Client::new(&sdk_config); @@ -46,5 +47,5 @@ async fn test_signer() { .await .expect_err("empty response"); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/sts/Cargo.toml b/aws/sdk/integration-tests/sts/Cargo.toml index a5e50b83cf..b9e50af6e0 100644 --- a/aws/sdk/integration-tests/sts/Cargo.toml +++ b/aws/sdk/integration-tests/sts/Cargo.toml @@ -13,8 +13,8 @@ publish = false [dev-dependencies] aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } 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/sts/tests/signing-it.rs b/aws/sdk/integration-tests/sts/tests/signing-it.rs index 5bf851a5ca..1b0a4e918e 100644 --- a/aws/sdk/integration-tests/sts/tests/signing-it.rs +++ b/aws/sdk/integration-tests/sts/tests/signing-it.rs @@ -4,16 +4,16 @@ */ use aws_sdk_sts::config::{Credentials, Region}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn assume_role_signed() { let creds = Credentials::for_tests(); - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .credentials_provider(creds) .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role().send().await; @@ -26,10 +26,10 @@ async fn assume_role_signed() { #[tokio::test] async fn web_identity_unsigned() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_web_identity().send().await; @@ -42,10 +42,10 @@ async fn web_identity_unsigned() { #[tokio::test] async fn assume_role_saml_unsigned() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_saml().send().await; @@ -58,10 +58,10 @@ async fn assume_role_saml_unsigned() { #[tokio::test] async fn web_identity_no_creds() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_web_identity().send().await; diff --git a/aws/sdk/integration-tests/timestreamquery/Cargo.toml b/aws/sdk/integration-tests/timestreamquery/Cargo.toml index 99955e4764..b8c6a497ac 100644 --- a/aws/sdk/integration-tests/timestreamquery/Cargo.toml +++ b/aws/sdk/integration-tests/timestreamquery/Cargo.toml @@ -14,7 +14,6 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-sdk-timestreamquery = { path = "../../build/aws-sdk/sdk/timestreamquery" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util"] } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } tokio = { version = "1.23.1", features = ["full", "test-util"] } diff --git a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs index 4c36c1e157..57a087b316 100644 --- a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs +++ b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs @@ -11,7 +11,6 @@ async fn do_endpoint_discovery() { use aws_smithy_async::rt::sleep::SharedAsyncSleep; use aws_smithy_async::test_util::controlled_time_and_sleep; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; - use aws_smithy_client::dvr::{MediaType, ReplayingConnection}; use aws_types::region::Region; use aws_types::SdkConfig; use std::time::{Duration, UNIX_EPOCH}; @@ -23,7 +22,7 @@ async fn do_endpoint_discovery() { let start = UNIX_EPOCH + Duration::from_secs(1234567890); let (ts, sleep, mut gate) = controlled_time_and_sleep(start); let config = SdkConfig::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::from_static("us-west-2")) .sleep_impl(SharedAsyncSleep::new(sleep)) .credentials_provider(SharedCredentialsProvider::new( diff --git a/aws/sdk/integration-tests/transcribestreaming/Cargo.toml b/aws/sdk/integration-tests/transcribestreaming/Cargo.toml index 181ba493cb..45a26fb836 100644 --- a/aws/sdk/integration-tests/transcribestreaming/Cargo.toml +++ b/aws/sdk/integration-tests/transcribestreaming/Cargo.toml @@ -13,9 +13,9 @@ async-stream = "0.3.0" aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-transcribestreaming = { path = "../../build/aws-sdk/sdk/transcribestreaming" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-eventstream = { path = "../../build/aws-sdk/sdk/aws-smithy-eventstream" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } bytes = "1.0.0" futures-core = "0.3.14" hound = "3.4.0" diff --git a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs index 62654ebd82..fce762d82b 100644 --- a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs +++ b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs @@ -13,8 +13,8 @@ use aws_sdk_transcribestreaming::types::{ AudioEvent, AudioStream, LanguageCode, MediaEncoding, TranscriptResultStream, }; use aws_sdk_transcribestreaming::{Client, Config}; -use aws_smithy_client::dvr::{Event, ReplayingConnection}; use aws_smithy_eventstream::frame::{DecodedFrame, HeaderValue, Message, MessageFrameDecoder}; +use aws_smithy_runtime::client::http::test_util::dvr::{Event, ReplayingClient}; use bytes::BufMut; use futures_core::Stream; use std::collections::{BTreeMap, BTreeSet}; @@ -98,14 +98,14 @@ async fn start_request( region: &'static str, events_json: &str, input_stream: impl Stream> + Send + Sync + 'static, -) -> (ReplayingConnection, StartStreamTranscriptionOutput) { +) -> (ReplayingClient, StartStreamTranscriptionOutput) { let events: Vec = serde_json::from_str(events_json).unwrap(); - let replayer = ReplayingConnection::new(events); + let replayer = ReplayingClient::new(events); let region = Region::from_static(region); let config = Config::builder() .region(region) - .http_connector(replayer.clone()) + .http_client(replayer.clone()) .credentials_provider(Credentials::for_tests()) .build(); let client = Client::from_conf(config); diff --git a/aws/sdk/integration-tests/webassembly/Cargo.toml b/aws/sdk/integration-tests/webassembly/Cargo.toml index b66406e6f5..159fb4b867 100644 --- a/aws/sdk/integration-tests/webassembly/Cargo.toml +++ b/aws/sdk/integration-tests/webassembly/Cargo.toml @@ -21,8 +21,9 @@ crate-type = ["cdylib"] aws-config = { path = "../../build/aws-sdk/sdk/aws-config", default-features = false, features = ["rt-tokio"]} aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["hardcoded-credentials"] } aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", default-features = false } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", default-features = false } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client"] } +aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } http = "0.2.8" diff --git a/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs b/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs deleted file mode 100644 index b563eb097a..0000000000 --- a/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -mod http_client; - -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_http::body::SdkBody; -use aws_smithy_http::result::ConnectorError; -use std::task::{Context, Poll}; -use tower::Service; - -#[derive(Default, Debug, Clone)] -pub(crate) struct Adapter {} - -impl Adapter { - pub fn to_http_connector() -> impl Into { - DynConnector::new(Adapter::default()) - } -} - -impl Service> for Adapter { - type Response = http::Response; - - type Error = ConnectorError; - - #[allow(clippy::type_complexity)] - type Future = std::pin::Pin< - Box> + Send + 'static>, - >; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: http::Request) -> Self::Future { - println!("Adapter: sending request..."); - let res = http_client::make_request(req).unwrap(); - println!("{:?}", res); - Box::pin(async move { Ok(res) }) - } -} diff --git a/aws/sdk/integration-tests/webassembly/src/default_config.rs b/aws/sdk/integration-tests/webassembly/src/default_config.rs index 181d1bb470..c87a364b5e 100644 --- a/aws/sdk/integration-tests/webassembly/src/default_config.rs +++ b/aws/sdk/integration-tests/webassembly/src/default_config.rs @@ -3,14 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::http::WasmHttpConnector; use aws_config::retry::RetryConfig; use aws_credential_types::Credentials; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; use std::future::Future; -use crate::adapter::Adapter; - pub(crate) fn get_default_config() -> impl Future { aws_config::from_env() .region(Region::from_static("us-west-2")) @@ -21,7 +20,7 @@ pub(crate) fn get_default_config() -> impl Future) -> Result, ()> { // Consumers here would pass the HTTP request to @@ -27,3 +33,30 @@ pub(crate) fn make_request(_req: http::Request) -> Result"; Ok(http::Response::new(SdkBody::from(body))) } + +#[derive(Default, Debug, Clone)] +pub(crate) struct WasmHttpConnector; +impl WasmHttpConnector { + pub fn new() -> Self { + Self + } +} + +impl HttpConnector for WasmHttpConnector { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + println!("Adapter: sending request..."); + let res = make_request(request).unwrap(); + println!("{:?}", res); + HttpConnectorFuture::new(async move { Ok(res) }) + } +} + +impl HttpClient for WasmHttpConnector { + fn http_connector( + &self, + _settings: &HttpConnectorSettings, + _components: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/aws/sdk/integration-tests/webassembly/src/lib.rs b/aws/sdk/integration-tests/webassembly/src/lib.rs index bc9c1a112b..1c4932afbb 100644 --- a/aws/sdk/integration-tests/webassembly/src/lib.rs +++ b/aws/sdk/integration-tests/webassembly/src/lib.rs @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -mod adapter; mod default_config; +mod http; mod list_buckets; #[tokio::main(flavor = "current_thread")] diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt index cd797f938f..d8a9b6818b 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt @@ -26,7 +26,7 @@ class ConnectionPoisoningRuntimePluginCustomization( section.registerInterceptor(runtimeConfig, this) { rust( "#T::new()", - smithyRuntime(runtimeConfig).resolve("client::connectors::connection_poisoning::ConnectionPoisoningInterceptor"), + smithyRuntime(runtimeConfig).resolve("client::http::connection_poisoning::ConnectionPoisoningInterceptor"), ) } } 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 643d0af31a..1ebc8049ef 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 @@ -6,7 +6,6 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext -import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig @@ -35,85 +34,21 @@ private class HttpConnectorConfigCustomization( *preludeScope, "Connection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::Connection"), "ConnectorSettings" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::ConnectorSettings"), - "DynConnectorAdapter" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::connectors::adapter::DynConnectorAdapter"), - "HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"), + "IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"), "Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"), "SharedAsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::SharedAsyncSleep"), - "SharedHttpConnector" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::connectors::SharedHttpConnector"), + "SharedHttpClient" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::http::SharedHttpClient"), "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( - """ - fn set_connector(resolver: &mut #{Resolver}<'_>) { - // Initial configuration needs to set a default if no connector is given, so it - // should always get into the condition below. - // - // Override configuration should set the connector if the override config - // contains a connector, sleep impl, or a timeout config since these are all - // incorporated into the final connector. - let must_set_connector = resolver.is_initial() - || resolver.is_latest_set::<#{HttpConnector}>() - || resolver.latest_sleep_impl().is_some() - || resolver.is_latest_set::<#{TimeoutConfig}>(); - if must_set_connector { - let sleep_impl = resolver.sleep_impl(); - let timeout_config = resolver.resolve_config::<#{TimeoutConfig}>() - .cloned() - .unwrap_or_else(#{TimeoutConfig}::disabled); - let connector_settings = #{ConnectorSettings}::from_timeout_config(&timeout_config); - let http_connector = resolver.resolve_config::<#{HttpConnector}>(); - - // TODO(enableNewSmithyRuntimeCleanup): Replace the tower-based DynConnector and remove DynConnectorAdapter when deleting the middleware implementation - let connector = - http_connector - .and_then(|c| c.connector(&connector_settings, sleep_impl.clone())) - .or_else(|| #{default_connector}(&connector_settings, sleep_impl)) - .map(|c| #{SharedHttpConnector}::new(#{DynConnectorAdapter}::new(c))); - - resolver.runtime_components_mut().set_http_connector(connector); - } - } - """, - *codegenScope, - "default_connector" to defaultConnectorFn(), - ) - } - override fun section(section: ServiceConfig): Writable { return when (section) { is ServiceConfig.ConfigImpl -> writable { rustTemplate( """ - /// Return the [`SharedHttpConnector`](#{SharedHttpConnector}) to use when making requests, if any. - pub fn http_connector(&self) -> Option<#{SharedHttpConnector}> { - self.runtime_components.http_connector() + /// Return the [`SharedHttpClient`](#{SharedHttpClient}) to use when making requests, if any. + pub fn http_client(&self) -> Option<#{SharedHttpClient}> { + self.runtime_components.http_client() } """, *codegenScope, @@ -123,7 +58,7 @@ private class HttpConnectorConfigCustomization( ServiceConfig.BuilderImpl -> writable { rustTemplate( """ - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run @@ -132,10 +67,8 @@ private class HttpConnectorConfigCustomization( /// ## ##[test] /// ## fn example() { /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; /// use $moduleUseName::config::Config; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() @@ -143,23 +76,23 @@ private class HttpConnectorConfigCustomization( /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); + /// let hyper_client = HyperClientBuilder::new().build(https_connector); + /// + /// // This connector can then be given to a generated service Config + /// let config = my_service_client::Config::builder() + /// .endpoint_url("https://example.com") + /// .http_client(hyper_client) + /// .build(); + /// let client = my_service_client::Client::from_conf(config); /// ## } /// ## } /// ``` - pub fn http_connector(mut self, http_connector: impl Into<#{HttpConnector}>) -> Self { - self.set_http_connector(#{Some}(http_connector)); + pub fn http_client(mut self, http_client: impl #{IntoShared}<#{SharedHttpClient}>) -> Self { + self.set_http_client(#{Some}(#{IntoShared}::into_shared(http_client))); self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run @@ -168,30 +101,22 @@ private class HttpConnectorConfigCustomization( /// ## ##[test] /// ## fn example() { /// use std::time::Duration; - /// use aws_smithy_client::hyper_ext; - /// use aws_smithy_client::http_connector::ConnectorSettings; /// use $moduleUseName::config::{Builder, Config}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// - /// fn override_http_connector(builder: &mut Builder) { + /// fn override_http_client(builder: &mut Builder) { /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); - /// builder.set_http_connector(Some(smithy_connector)); + /// let hyper_client = HyperClientBuilder::new().build(https_connector); + /// builder.set_http_client(Some(hyper_client)); /// } /// /// let mut builder = $moduleUseName::Config::builder(); - /// override_http_connector(&mut builder); + /// override_http_client(&mut builder); /// let config = builder.build(); /// ## } /// ## } @@ -201,8 +126,8 @@ private class HttpConnectorConfigCustomization( ) rustTemplate( """ - pub fn set_http_connector(&mut self, http_connector: Option>) -> &mut Self { - http_connector.map(|c| self.config.store_put(c.into())); + pub fn set_http_client(&mut self, http_client: Option<#{SharedHttpClient}>) -> &mut Self { + self.runtime_components.set_http_client(http_client); self } """, @@ -210,20 +135,6 @@ private class HttpConnectorConfigCustomization( ) } - is ServiceConfig.BuilderBuild -> writable { - rustTemplate( - "#{set_connector}(&mut resolver);", - "set_connector" to setConnectorFn(), - ) - } - - is ServiceConfig.OperationConfigOverride -> writable { - rustTemplate( - "#{set_connector}(&mut resolver);", - "set_connector" to setConnectorFn(), - ) - } - else -> emptySection } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt index 87586fbcb7..799aff043d 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt @@ -32,6 +32,7 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon "ClientRateLimiter" to retries.resolve("ClientRateLimiter"), "ClientRateLimiterPartition" to retries.resolve("ClientRateLimiterPartition"), "debug" to RuntimeType.Tracing.resolve("debug"), + "IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"), "RetryConfig" to retryConfig.resolve("RetryConfig"), "RetryMode" to RuntimeType.smithyTypes(runtimeConfig).resolve("retry::RetryMode"), "RetryPartition" to retries.resolve("RetryPartition"), @@ -150,8 +151,8 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = Config::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: #{SharedAsyncSleep}) -> Self { - self.set_sleep_impl(Some(sleep_impl)); + pub fn sleep_impl(mut self, sleep_impl: impl #{IntoShared}<#{SharedAsyncSleep}>) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt index f3402c950d..4930e0e45e 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.pre class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() { private val codegenScope = arrayOf( *preludeScope, + "IntoShared" to RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig).resolve("shared::IntoShared"), "SharedTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::SharedTimeSource"), "StaticTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::StaticTimeSource"), "UNIX_EPOCH" to RuntimeType.std.resolve("time::UNIX_EPOCH"), @@ -46,9 +47,9 @@ class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCust /// Sets the time source used for this service pub fn time_source( mut self, - time_source: impl #{Into}<#{SharedTimeSource}>, + time_source: impl #{IntoShared}<#{SharedTimeSource}>, ) -> Self { - self.set_time_source(#{Some}(time_source.into())); + self.set_time_source(#{Some}(time_source.into_shared())); self } """, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt index 78686b2877..3852d09fb2 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt @@ -69,7 +69,13 @@ class RequiredCustomizations : ClientCodegenDecorator { val rc = codegenContext.runtimeConfig // Add rt-tokio feature for `ByteStream::from_path` - rustCrate.mergeFeature(Feature("rt-tokio", true, listOf("aws-smithy-http/rt-tokio"))) + rustCrate.mergeFeature( + Feature( + "rt-tokio", + true, + listOf("aws-smithy-async/rt-tokio", "aws-smithy-http/rt-tokio"), + ), + ) rustCrate.mergeFeature(TestUtilFeature) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt index 5234f6fe59..dfe7ca5802 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt @@ -39,7 +39,7 @@ class FluentClientDecorator : ClientCodegenDecorator { customizations = listOf(GenericFluentClient(codegenContext)), ).render(rustCrate) - rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-client/rustls"))) + rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-runtime/tls-rustls"))) } override fun libRsCustomizations( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt index 614d89bd48..239b912b6b 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt @@ -480,6 +480,7 @@ private fun baseClientRuntimePluginsFn(runtimeConfig: RuntimeConfig): RuntimeTyp let mut configured_plugins = #{Vec}::new(); ::std::mem::swap(&mut config.runtime_plugins, &mut configured_plugins); let mut plugins = #{RuntimePlugins}::new() + .with_client_plugin(#{default_http_client_plugin}()) .with_client_plugin( #{StaticRuntimePlugin}::new() .with_config(config.config.clone()) @@ -499,6 +500,8 @@ private fun baseClientRuntimePluginsFn(runtimeConfig: RuntimeConfig): RuntimeTyp .resolve("client::auth::no_auth::NoAuthRuntimePlugin"), "StaticRuntimePlugin" to RuntimeType.smithyRuntimeApi(runtimeConfig) .resolve("client::runtime_plugin::StaticRuntimePlugin"), + "default_http_client_plugin" to RuntimeType.smithyRuntime(runtimeConfig) + .resolve("client::http::default_http_client_plugin"), ) } 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 44ff13d45e..4a4f810789 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 @@ -49,7 +49,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType as RT data class ClientCreationParams( val codegenContext: ClientCodegenContext, - val connectorName: String, + val httpClientName: String, val configBuilderName: String, val clientName: String, ) @@ -75,7 +75,7 @@ class DefaultProtocolTestGenerator( """ let ${params.clientName} = #{Client}::from_conf( ${params.configBuilderName} - .http_connector(${params.connectorName}) + .http_client(${params.httpClientName}) .build() ); """, @@ -206,20 +206,17 @@ class DefaultProtocolTestGenerator( } ?: writable { } rustTemplate( """ - let (conn, request_receiver) = #{capture_request}(None); + let (http_client, request_receiver) = #{capture_request}(None); let config_builder = #{config}::Config::builder().with_test_defaults().endpoint_resolver("https://example.com"); #{customParams} """, - "capture_request" to CargoDependency.smithyClient(rc) - .toDevDependency() - .withFeature("test-util") - .toType() - .resolve("test_connection::capture_request"), + "capture_request" to CargoDependency.smithyRuntimeTestUtil(rc).toType() + .resolve("client::http::test_util::capture_request"), "config" to ClientRustModule.config, "customParams" to customParams, ) - renderClientCreation(this, ClientCreationParams(codegenContext, "conn", "config_builder", "client")) + renderClientCreation(this, ClientCreationParams(codegenContext, "http_client", "config_builder", "client")) writeInline("let result = ") instantiator.renderFluentCall(this, "client", operationShape, inputShape, httpRequestTestCase.params) diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt index e53b66fc14..d81a808645 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt @@ -17,10 +17,15 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class HttpAuthDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( - "TestConnection" to CargoDependency.smithyClient(runtimeConfig) + "ConnectionEvent" to CargoDependency.smithyRuntime(runtimeConfig) .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), + .resolve("client::http::test_util::ConnectionEvent"), + "EventClient" to CargoDependency.smithyRuntime(runtimeConfig) + .toDevDependency().withFeature("test-util").toType() + .resolve("client::http::test_util::EventClient"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), + "TokioSleep" to CargoDependency.smithyAsync(runtimeConfig).withFeature("rt-tokio").toType() + .resolve("rt::sleep::TokioSleep"), ) @Test @@ -34,25 +39,28 @@ class HttpAuthDecoratorTest { async fn use_api_key_auth_when_api_key_provided() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -63,26 +71,29 @@ class HttpAuthDecoratorTest { async fn use_basic_auth_when_basic_auth_login_provided() { use aws_smithy_runtime_api::client::identity::http::Login; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .basic_auth_login(Login::new("some-user", "some-pass", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -102,25 +113,28 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_query_string() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -140,26 +154,29 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_headers() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "ApiKey some-api-key") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "ApiKey some-api-key") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -179,26 +196,29 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Login; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .basic_auth_login(Login::new("some-user", "some-pass", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -218,26 +238,29 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Bearer some-token") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Bearer some-token") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .bearer_token(Token::new("some-token", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -255,24 +278,27 @@ class HttpAuthDecoratorTest { rustTemplate( """ async fn optional_auth() { - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt index 1bcc30e0c3..9010b94659 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt @@ -81,10 +81,10 @@ class MetadataCustomizationTest { let (tx, rx) = ::std::sync::mpsc::channel(); - let (conn, _captured_request) = #{capture_request}(#{None}); + let (http_client, _captured_request) = #{capture_request}(#{None}); let client_config = crate::config::Config::builder() .endpoint_resolver("http://localhost:1234/") - .http_connector(conn) + .http_client(http_client) .build(); let client = crate::client::Client::from_conf(client_config); let _ = client diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt index ec4d152151..bf634170ca 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt @@ -8,7 +8,6 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import org.junit.jupiter.api.Test 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.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -18,9 +17,6 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class SensitiveOutputDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( "capture_request" to RuntimeType.captureRequest(runtimeConfig), - "TestConnection" to CargoDependency.smithyClient(runtimeConfig) - .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), ) @@ -56,7 +52,7 @@ class SensitiveOutputDecoratorTest { rustTemplate( """ async fn redacting_sensitive_response_body() { - let (conn, _r) = #{capture_request}(Some( + let (http_client, _r) = #{capture_request}(Some( http::Response::builder() .status(200) .body(#{SdkBody}::from("")) @@ -65,7 +61,7 @@ class SensitiveOutputDecoratorTest { let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.say_hello() 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 b55faa439f..d57272b149 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 @@ -10,7 +10,8 @@ import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test 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.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate 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 @@ -132,11 +133,11 @@ class EndpointsDecoratorTest { rustCrate.integrationTest("endpoint_params_test") { val moduleName = clientCodegenContext.moduleUseName() Attribute.TokioTest.render(this) - rust( + rustTemplate( """ async fn endpoint_params_are_set() { - use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::never::NeverConnector; + use #{NeverClient}; + use #{TokioSleep}; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -194,7 +195,7 @@ class EndpointsDecoratorTest { let interceptor = TestInterceptor::default(); let config = Config::builder() - .http_connector(NeverConnector::new()) + .http_client(NeverClient::new()) .interceptor(interceptor.clone()) .timeout_config( TimeoutConfig::builder() @@ -218,6 +219,10 @@ class EndpointsDecoratorTest { assert_eq!(format!("{}", err), "failed to construct request"); } """, + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(clientCodegenContext.runtimeConfig) + .toType().resolve("client::http::test_util::NeverClient"), + "TokioSleep" to CargoDependency.smithyAsync(clientCodegenContext.runtimeConfig) + .withFeature("rt-tokio").toType().resolve("rt::sleep::TokioSleep"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt index 9534f7a79e..e7a4ed760f 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt @@ -90,16 +90,16 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { ) rustCrate.testModule { addDependency(CargoDependency.Tokio.toDevDependency().withFeature("test-util")) - tokioTest("test_operation_overrides_http_connection") { + tokioTest("test_operation_overrides_http_client") { rustTemplate( """ use #{AsyncSleep}; - let (conn, captured_request) = #{capture_request}(#{None}); + let (http_client, captured_request) = #{capture_request}(#{None}); let expected_url = "http://localhost:1234/"; let client_config = crate::config::Config::builder() .endpoint_resolver(expected_url) - .http_connector(#{NeverConnector}::new()) + .http_client(#{NeverClient}::new()) .build(); let client = crate::client::Client::from_conf(client_config.clone()); let sleep = #{TokioSleep}::new(); @@ -120,7 +120,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { .customize() .await .unwrap() - .config_override(crate::config::Config::builder().http_connector(conn)) + .config_override(crate::config::Config::builder().http_client(http_client)) .send(); let timeout = #{Timeout}::new( @@ -144,11 +144,11 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { *codegenScope, "AsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::AsyncSleep"), "capture_request" to RuntimeType.captureRequest(runtimeConfig), - "NeverConnector" to RuntimeType.smithyClient(runtimeConfig) - .resolve("never::NeverConnector"), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), "Timeout" to RuntimeType.smithyAsync(runtimeConfig).resolve("future::timeout::Timeout"), - "TokioSleep" to RuntimeType.smithyAsync(runtimeConfig) - .resolve("rt::sleep::TokioSleep"), + "TokioSleep" to CargoDependency.smithyAsync(runtimeConfig).withFeature("rt-tokio") + .toType().resolve("rt::sleep::TokioSleep"), ) } } 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 430ff4737e..3d1dcd009d 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 @@ -160,7 +160,7 @@ internal class EndpointTraitBindingsTest { rustTemplate( """ async fn test_endpoint_prefix() { - use #{aws_smithy_client}::test_connection::capture_request; + use #{capture_request}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::endpoint::EndpointPrefix; use aws_smithy_runtime_api::box_error::BoxError; @@ -202,7 +202,7 @@ internal class EndpointTraitBindingsTest { } } - let (conn, _r) = capture_request(Some( + let (http_client, _r) = capture_request(Some( http::Response::builder() .status(200) .body(SdkBody::from("")) @@ -210,7 +210,7 @@ internal class EndpointTraitBindingsTest { )); let interceptor = TestInterceptor::default(); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .interceptor(interceptor.clone()) .build(); let client = Client::from_conf(config); @@ -246,8 +246,8 @@ internal class EndpointTraitBindingsTest { ); } """, - "aws_smithy_client" to CargoDependency.smithyClient(clientCodegenContext.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "capture_request" to CargoDependency.smithyRuntimeTestUtil(clientCodegenContext.runtimeConfig) + .toType().resolve("client::http::test_util::capture_request"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt index 8fb6bb888f..2abb4229c8 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt @@ -10,7 +10,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext 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.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -44,20 +43,16 @@ class CustomizableOperationGeneratorTest { ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .endpoint_resolver("http://localhost:1234") .build(); let client = $moduleName::Client::from_conf(config); check_send_and_sync(client.say_hello().customize()); } """, - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt index 121b1fd6a5..9fa909bda9 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt @@ -12,7 +12,6 @@ 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.CargoDependency 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.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.util.lookup @@ -77,22 +76,16 @@ class FluentClientGeneratorTest { ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .build(); let client = $moduleName::Client::from_conf(config); check_send(client.say_hello().send()); } """, - "TestConnection" to software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency.smithyClient( - codegenContext.runtimeConfig, - ) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } @@ -107,10 +100,9 @@ class FluentClientGeneratorTest { """ ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .build(); let client = $moduleName::Client::from_conf(config); @@ -120,11 +112,8 @@ class FluentClientGeneratorTest { assert_eq!(*input.get_byte_value(), Some(4)); } """, - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } 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..64c7cae3ba 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 @@ -58,7 +58,6 @@ class AwsQueryCompatibleTest { ##[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| { @@ -80,7 +79,7 @@ class AwsQueryCompatibleTest { }; let client = crate::Client::from_conf( crate::Config::builder() - .http_connector(infallible_connection_fn(response)) + .http_client(#{infallible_client_fn}(response)) .endpoint_url("http://localhost:1234") .build() ); @@ -92,8 +91,8 @@ class AwsQueryCompatibleTest { assert_eq!(Some("Sender"), error.meta().extra("type")); } """, - "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "infallible_client_fn" to CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig) + .toType().resolve("client::http::test_util::infallible_client_fn"), "tokio" to CargoDependency.Tokio.toType(), ) } @@ -139,7 +138,6 @@ class AwsQueryCompatibleTest { ##[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| { @@ -157,7 +155,7 @@ class AwsQueryCompatibleTest { }; let client = crate::Client::from_conf( crate::Config::builder() - .http_connector(infallible_connection_fn(response)) + .http_client(#{infallible_client_fn}(response)) .endpoint_url("http://localhost:1234") .build() ); @@ -166,8 +164,8 @@ class AwsQueryCompatibleTest { assert_eq!(None, error.meta().extra("type")); } """, - "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "infallible_client_fn" to CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig) + .toType().resolve("client::http::test_util::infallible_client_fn"), "tokio" to CargoDependency.Tokio.toType(), ) } 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 73a5e9ebbe..9dbb43f497 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 @@ -298,6 +298,7 @@ data class CargoDependency( fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query") fun smithyRuntime(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime") .withFeature("client") + fun smithyRuntimeTestUtil(runtimeConfig: RuntimeConfig) = smithyRuntime(runtimeConfig).toDevDependency().withFeature("test-util") fun smithyRuntimeApi(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime-api") .withFeature("client") fun smithyRuntimeApiTestUtil(runtimeConfig: RuntimeConfig) = 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 f7e463b0a7..639964f762 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 @@ -476,8 +476,8 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) return smithyTypes(runtimeConfig).resolve("date_time::Format::$timestampFormat") } - fun captureRequest(runtimeConfig: RuntimeConfig) = - CargoDependency.smithyClientTestUtil(runtimeConfig).toType().resolve("test_connection::capture_request") + fun captureRequest(runtimeConfig: RuntimeConfig) = CargoDependency.smithyRuntimeTestUtil(runtimeConfig).toType() + .resolve("client::http::test_util::capture_request") fun forInlineDependency(inlineDependency: InlineDependency) = RuntimeType("crate::${inlineDependency.name}", inlineDependency) diff --git a/examples/pokemon-service-common/Cargo.toml b/examples/pokemon-service-common/Cargo.toml index f2c86eee0e..6a63045004 100644 --- a/examples/pokemon-service-common/Cargo.toml +++ b/examples/pokemon-service-common/Cargo.toml @@ -16,12 +16,12 @@ tokio = { version = "1", default-features = false, features = ["time"] } tower = "0.4" # Local paths -aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client" } +aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } +aws-smithy-runtime-api = { path = "../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } 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 cb5c4bd604..be15b07117 100644 --- a/examples/pokemon-service-common/src/lib.rs +++ b/examples/pokemon-service-common/src/lib.rs @@ -15,15 +15,15 @@ use std::{ }; use async_stream::stream; -use aws_smithy_client::{conns, hyper_ext::Adapter}; use aws_smithy_http::{body::SdkBody, byte_stream::ByteStream}; use aws_smithy_http_server::Extension; +use aws_smithy_runtime::client::http::hyper_014::HyperConnector; +use aws_smithy_runtime_api::client::http::HttpConnector; use http::Uri; use pokemon_service_server_sdk::{ error, input, model, model::CapturingPayload, output, types::Blob, }; use rand::{seq::SliceRandom, Rng}; -use tower::Service; use tracing_subscriber::{prelude::*, EnvFilter}; const PIKACHU_ENGLISH_FLAVOR_TEXT: &str = @@ -326,7 +326,7 @@ pub async fn stream_pokemon_radio( .parse::() .expect("Invalid url in `RADIO_STREAMS`"); - let mut connector = Adapter::builder().build(conns::https()); + let connector = HyperConnector::builder().build_https(); let result = connector .call( http::Request::builder() diff --git a/examples/pokemon-service-common/tests/plugins_execution_order.rs b/examples/pokemon-service-common/tests/plugins_execution_order.rs index fb5f0fb4d7..35d3ba5fe2 100644 --- a/examples/pokemon-service-common/tests/plugins_execution_order.rs +++ b/examples/pokemon-service-common/tests/plugins_execution_order.rs @@ -14,7 +14,7 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http_server::plugin::{HttpMarker, HttpPlugins, IdentityPlugin, Plugin}; use tower::{Layer, Service}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use pokemon_service_client::{Client, Config}; use pokemon_service_common::do_nothing; @@ -46,9 +46,9 @@ async fn plugin_layers_are_executed_in_registration_order() { .build_unchecked(); let request = { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .endpoint_url("http://localhost:1234") .build(); Client::from_conf(config).do_nothing().send().await.unwrap(); diff --git a/examples/pokemon-service-tls/Cargo.toml b/examples/pokemon-service-tls/Cargo.toml index 9f11a904fc..566afcfc0a 100644 --- a/examples/pokemon-service-tls/Cargo.toml +++ b/examples/pokemon-service-tls/Cargo.toml @@ -31,7 +31,7 @@ hyper-rustls = { version = "0.24", features = ["http2"] } hyper-tls = { version = "0.5" } # Local paths -aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client/", features = ["rustls"] } aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http/" } +aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } aws-smithy-types = { path = "../../rust-runtime/aws-smithy-types/" } pokemon-service-client = { path = "../pokemon-service-client/" } diff --git a/examples/pokemon-service-tls/tests/common/mod.rs b/examples/pokemon-service-tls/tests/common/mod.rs index 0d69407cf7..a13c2e60ee 100644 --- a/examples/pokemon-service-tls/tests/common/mod.rs +++ b/examples/pokemon-service-tls/tests/common/mod.rs @@ -6,7 +6,7 @@ use std::{fs::File, io::BufReader, process::Command, time::Duration}; use assert_cmd::prelude::*; -use aws_smithy_client::hyper_ext::Adapter; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; use tokio::time::sleep; use pokemon_service_client::{Client, Config}; @@ -44,7 +44,7 @@ pub fn client_http2_only() -> Client { .build(); let config = Config::builder() - .http_connector(Adapter::builder().build(connector)) + .http_client(HyperClientBuilder::new().build(connector)) .endpoint_url(format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) .build(); Client::from_conf(config) @@ -74,7 +74,7 @@ fn native_tls_connector() -> NativeTlsConnector { pub fn native_tls_client() -> Client { let config = Config::builder() - .http_connector(Adapter::builder().build(native_tls_connector())) + .http_client(HyperClientBuilder::new().build(native_tls_connector())) .endpoint_url(format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) .build(); Client::from_conf(config) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client.rs b/rust-runtime/aws-smithy-runtime-api/src/client.rs index 9f8a05686e..2afc2546c5 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +pub mod dns; + pub mod endpoint; /// Smithy identity used by auth and signing. @@ -20,6 +22,6 @@ pub mod runtime_plugin; pub mod auth; -pub mod connectors; +pub mod http; pub mod ser_de; diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs b/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs deleted file mode 100644 index 9399fa05bf..0000000000 --- a/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Smithy connectors and related code. -//! -//! # What is a connector? -//! -//! When we talk about connectors, we are referring to the [`HttpConnector`] trait, and implementations of -//! that trait. This trait simply takes a HTTP request, and returns a future with the response for that -//! request. -//! -//! This is slightly different from what a connector is in other libraries such as -//! [`hyper`](https://crates.io/crates/hyper). In hyper 0.x, the connector is a -//! [`tower`](https://crates.io/crates/tower) `Service` that takes a `Uri` and returns -//! a future with something that implements `AsyncRead + AsyncWrite`. -//! -//! The [`HttpConnector`](crate::client::connectors::HttpConnector) is designed to be a layer on top of -//! whole HTTP libraries, such as hyper, which allows Smithy clients to be agnostic to the underlying HTTP -//! transport layer. This also makes it easy to write tests with a fake HTTP connector, and several -//! such test connector implementations are availble in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime). -//! -//! # Responsibilities of a connector -//! -//! A connector primarily makes HTTP requests, but can also be used to implement connect and read -//! timeouts. The `HyperConnector` in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) -//! is an example where timeouts are implemented as part of the connector. -//! -//! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction. -//! The Smithy clients have no knowledge of such concepts. - -use crate::client::orchestrator::{HttpRequest, HttpResponse}; -use crate::impl_shared_conversions; -use aws_smithy_async::future::now_or_later::NowOrLater; -use aws_smithy_http::result::ConnectorError; -use pin_project_lite::pin_project; -use std::fmt; -use std::future::Future as StdFuture; -use std::pin::Pin; -use std::sync::Arc; -use std::task::Poll; - -type BoxFuture = Pin> + Send>>; - -pin_project! { - /// Future for [`HttpConnector::call`]. - pub struct HttpConnectorFuture { - #[pin] - inner: NowOrLater, BoxFuture>, - } -} - -impl HttpConnectorFuture { - /// Create a new `HttpConnectorFuture` with the given future. - pub fn new(future: F) -> Self - where - F: StdFuture> + Send + 'static, - { - Self { - inner: NowOrLater::new(Box::pin(future)), - } - } - - /// Create a new `HttpConnectorFuture` with the given boxed future. - /// - /// Use this if you already have a boxed future to avoid double boxing it. - pub fn new_boxed( - future: Pin> + Send>>, - ) -> Self { - Self { - inner: NowOrLater::new(future), - } - } - - /// Create a `HttpConnectorFuture` that is immediately ready with the given result. - pub fn ready(result: Result) -> Self { - Self { - inner: NowOrLater::ready(result), - } - } -} - -impl StdFuture for HttpConnectorFuture { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let this = self.project(); - this.inner.poll(cx) - } -} - -/// Trait with a `call` function that asynchronously converts a request into a response. -/// -/// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper), -/// and any associated HTTPS implementation alongside it to service requests. -/// -/// However, it can also be useful to create fake connectors implementing this trait -/// for testing. -pub trait HttpConnector: Send + Sync + fmt::Debug { - /// Asynchronously converts a request into a response. - fn call(&self, request: HttpRequest) -> HttpConnectorFuture; -} - -/// A shared [`HttpConnector`] implementation. -#[derive(Clone, Debug)] -pub struct SharedHttpConnector(Arc); - -impl SharedHttpConnector { - /// Returns a new [`SharedHttpConnector`]. - pub fn new(connection: impl HttpConnector + 'static) -> Self { - Self(Arc::new(connection)) - } -} - -impl HttpConnector for SharedHttpConnector { - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - (*self.0).call(request) - } -} - -impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new); diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs new file mode 100644 index 0000000000..5dc237b82a --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Interfaces for resolving DNS + +use crate::box_error::BoxError; +use crate::client::orchestrator::BoxFuture; +use crate::impl_shared_conversions; +use aws_smithy_async::future::now_or_later::NowOrLater; +use std::fmt; +use std::future::Future; +use std::net::IpAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +/// New-type for the future returned by the [`DnsResolver`] trait. +pub struct DnsFuture(NowOrLater, BoxError>, BoxFuture>>); +impl DnsFuture { + /// Create a new `DnsFuture` + pub fn new( + future: impl Future, BoxError>> + Send + 'static, + ) -> Self { + Self(NowOrLater::new(Box::pin(future))) + } + + /// Create a `DnsFuture` that is immediately ready + pub fn ready(result: Result, BoxError>) -> Self { + Self(NowOrLater::ready(result)) + } +} +impl Future for DnsFuture { + type Output = Result, BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.as_mut(); + let inner = Pin::new(&mut this.0); + Future::poll(inner, cx) + } +} + +/// Trait for resolving domain names +pub trait DnsResolver: fmt::Debug + Send + Sync { + /// Asynchronously resolve the given domain name + fn resolve_dns(&self, name: String) -> DnsFuture; +} + +/// Shared DNS resolver +#[derive(Clone, Debug)] +pub struct SharedDnsResolver(Arc); + +impl SharedDnsResolver { + /// Create a new `SharedDnsResolver`. + pub fn new(resolver: impl DnsResolver + 'static) -> Self { + Self(Arc::new(resolver)) + } +} + +impl DnsResolver for SharedDnsResolver { + fn resolve_dns(&self, name: String) -> DnsFuture { + self.0.resolve_dns(name) + } +} + +impl_shared_conversions!(convert SharedDnsResolver from DnsResolver using SharedDnsResolver::new); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_send() { + fn is_send() {} + is_send::(); + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs new file mode 100644 index 0000000000..4a2c363e02 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs @@ -0,0 +1,305 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! HTTP clients and connectors +//! +//! # What is a connector? +//! +//! When we talk about connectors, we are referring to the [`HttpConnector`] trait, and implementations of +//! that trait. This trait simply takes a HTTP request, and returns a future with the response for that +//! request. +//! +//! This is slightly different from what a connector is in other libraries such as +//! [`hyper`](https://crates.io/crates/hyper). In hyper 0.x, the connector is a +//! [`tower`](https://crates.io/crates/tower) `Service` that takes a `Uri` and returns +//! a future with something that implements `AsyncRead + AsyncWrite`. +//! +//! The [`HttpConnector`] is designed to be a layer on top of +//! whole HTTP libraries, such as hyper, which allows Smithy clients to be agnostic to the underlying HTTP +//! transport layer. This also makes it easy to write tests with a fake HTTP connector, and several +//! such test connector implementations are available in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) +//! with the `test-util` feature enabled. +//! +//! # Responsibilities of a connector +//! +//! A connector primarily makes HTTP requests, but is also the place where connect and read timeouts are +//! implemented. The `HyperConnector` in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) +//! is an example where timeouts are implemented as part of the connector. +//! +//! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction. +//! The Smithy clients have no knowledge of such concepts. +//! +//! # The [`HttpClient`] trait +//! +//! Connectors allow us to make requests, but we need a layer on top of connectors so that we can handle +//! varying connector settings. For example, say we configure some default HTTP connect/read timeouts on +//! Client, and then configure some override connect/read timeouts for a specific operation. These timeouts +//! ultimately are part of the connector, so the same connector can't be reused for the two different sets +//! of timeouts. Thus, the [`HttpClient`] implementation is responsible for managing multiple connectors +//! with varying config. Some example configs that can impact which connector is used: +//! +//! - HTTP protocol versions +//! - TLS settings +//! - Timeouts +//! +//! Some of these aren't implemented yet, but they will appear in the [`HttpConnectorSettings`] struct +//! once they are. + +use crate::client::orchestrator::{HttpRequest, HttpResponse}; +use crate::client::runtime_components::RuntimeComponents; +use crate::impl_shared_conversions; +use aws_smithy_async::future::now_or_later::NowOrLater; +use aws_smithy_http::result::ConnectorError; +use pin_project_lite::pin_project; +use std::fmt; +use std::future::Future as StdFuture; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Poll; +use std::time::Duration; + +type BoxFuture = Pin> + Send>>; + +pin_project! { + /// Future for [`HttpConnector::call`]. + pub struct HttpConnectorFuture { + #[pin] + inner: NowOrLater, BoxFuture>, + } +} + +impl HttpConnectorFuture { + /// Create a new `HttpConnectorFuture` with the given future. + pub fn new(future: F) -> Self + where + F: StdFuture> + Send + 'static, + { + Self { + inner: NowOrLater::new(Box::pin(future)), + } + } + + /// Create a new `HttpConnectorFuture` with the given boxed future. + /// + /// Use this if you already have a boxed future to avoid double boxing it. + pub fn new_boxed( + future: Pin> + Send>>, + ) -> Self { + Self { + inner: NowOrLater::new(future), + } + } + + /// Create a `HttpConnectorFuture` that is immediately ready with the given result. + pub fn ready(result: Result) -> Self { + Self { + inner: NowOrLater::ready(result), + } + } +} + +impl StdFuture for HttpConnectorFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let this = self.project(); + this.inner.poll(cx) + } +} + +/// Trait with a `call` function that asynchronously converts a request into a response. +/// +/// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper), +/// and any associated HTTPS implementation alongside it to service requests. +/// +/// However, it can also be useful to create fake connectors implementing this trait +/// for testing. +pub trait HttpConnector: Send + Sync + fmt::Debug { + /// Asynchronously converts a request into a response. + fn call(&self, request: HttpRequest) -> HttpConnectorFuture; +} + +/// A shared [`HttpConnector`] implementation. +#[derive(Clone, Debug)] +pub struct SharedHttpConnector(Arc); + +impl SharedHttpConnector { + /// Returns a new [`SharedHttpConnector`]. + pub fn new(connection: impl HttpConnector + 'static) -> Self { + Self(Arc::new(connection)) + } +} + +impl HttpConnector for SharedHttpConnector { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + (*self.0).call(request) + } +} + +impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new); + +/// Returns a [`SharedHttpClient`] that calls the given `connector` function to select a HTTP connector. +pub fn http_client_fn(connector: F) -> SharedHttpClient +where + F: Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector + + Send + + Sync + + 'static, +{ + struct ConnectorFn(T); + impl fmt::Debug for ConnectorFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ConnectorFn") + } + } + impl HttpClient for ConnectorFn + where + T: (Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector) + Send + Sync, + { + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + (self.0)(settings, components) + } + } + + SharedHttpClient::new(ConnectorFn(connector)) +} + +/// HTTP client abstraction. +/// +/// A HTTP client implementation must apply connect/read timeout settings, +/// and must maintain a connection pool. +pub trait HttpClient: Send + Sync + fmt::Debug { + /// Returns a HTTP connector based on the requested connector settings. + /// + /// The settings include connector timeouts, which should be incorporated + /// into the connector. The `HttpClient` is responsible for caching + /// the connector across requests. + /// + /// In the future, the settings may have additional parameters added, + /// such as HTTP version, or TLS certificate paths. + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector; +} + +/// Shared HTTP client for use across multiple clients and requests. +#[derive(Clone, Debug)] +pub struct SharedHttpClient { + selector: Arc, +} + +impl SharedHttpClient { + /// Creates a new `SharedHttpClient` + pub fn new(selector: impl HttpClient + 'static) -> Self { + Self { + selector: Arc::new(selector), + } + } +} + +impl HttpClient for SharedHttpClient { + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + self.selector.http_connector(settings, components) + } +} + +impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new); + +/// Builder for [`HttpConnectorSettings`]. +#[non_exhaustive] +#[derive(Default, Debug)] +pub struct HttpConnectorSettingsBuilder { + connect_timeout: Option, + read_timeout: Option, +} + +impl HttpConnectorSettingsBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Default::default() + } + + /// Sets the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self { + self.connect_timeout = Some(connect_timeout); + self + } + + /// Sets the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn set_connect_timeout(&mut self, connect_timeout: Option) -> &mut Self { + self.connect_timeout = connect_timeout; + self + } + + /// Sets the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn read_timeout(mut self, read_timeout: Duration) -> Self { + self.read_timeout = Some(read_timeout); + self + } + + /// Sets the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn set_read_timeout(&mut self, read_timeout: Option) -> &mut Self { + self.read_timeout = read_timeout; + self + } + + /// Builds the [`HttpConnectorSettings`]. + pub fn build(self) -> HttpConnectorSettings { + HttpConnectorSettings { + connect_timeout: self.connect_timeout, + read_timeout: self.read_timeout, + } + } +} + +/// Settings for HTTP Connectors +#[non_exhaustive] +#[derive(Clone, Default, Debug)] +pub struct HttpConnectorSettings { + connect_timeout: Option, + read_timeout: Option, +} + +impl HttpConnectorSettings { + /// Returns a builder for `HttpConnectorSettings`. + pub fn builder() -> HttpConnectorSettingsBuilder { + Default::default() + } + + /// Returns the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn connect_timeout(&self) -> Option { + self.connect_timeout + } + + /// Returns the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn read_timeout(&self) -> Option { + self.read_timeout + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs index 4a92ec6988..a0ef4a8023 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs @@ -14,8 +14,8 @@ use crate::client::auth::{ AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver, }; -use crate::client::connectors::SharedHttpConnector; use crate::client::endpoint::SharedEndpointResolver; +use crate::client::http::SharedHttpClient; use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver}; use crate::client::interceptors::SharedInterceptor; use crate::client::retries::{RetryClassifiers, SharedRetryStrategy}; @@ -184,7 +184,7 @@ declare_runtime_components! { auth_scheme_option_resolver: Option, // A connector is not required since a client could technically only be used for presigning - http_connector: Option, + http_client: Option, #[required] endpoint_resolver: Option, @@ -219,9 +219,9 @@ impl RuntimeComponents { self.auth_scheme_option_resolver.value.clone() } - /// Returns the connector. - pub fn http_connector(&self) -> Option { - self.http_connector.as_ref().map(|s| s.value.clone()) + /// Returns the HTTP client. + pub fn http_client(&self) -> Option { + self.http_client.as_ref().map(|s| s.value.clone()) } /// Returns the endpoint resolver. @@ -290,26 +290,26 @@ impl RuntimeComponentsBuilder { self } - /// Returns the HTTP connector. - pub fn http_connector(&self) -> Option { - self.http_connector.as_ref().map(|s| s.value.clone()) + /// Returns the HTTP client. + pub fn http_client(&self) -> Option { + self.http_client.as_ref().map(|s| s.value.clone()) } - /// Sets the HTTP connector. - pub fn set_http_connector( + /// Sets the HTTP client. + pub fn set_http_client( &mut self, - connector: Option>, + connector: Option>, ) -> &mut Self { - self.http_connector = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); + self.http_client = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); self } - /// Sets the HTTP connector. - pub fn with_http_connector( + /// Sets the HTTP client. + pub fn with_http_client( mut self, - connector: Option>, + connector: Option>, ) -> Self { - self.set_http_connector(connector); + self.set_http_client(connector); self } @@ -489,8 +489,11 @@ impl RuntimeComponentsBuilder { } /// Sets the async sleep implementation. - pub fn with_sleep_impl(mut self, sleep_impl: Option) -> Self { - self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s)); + pub fn with_sleep_impl( + mut self, + sleep_impl: Option>, + ) -> Self { + self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } @@ -506,8 +509,11 @@ impl RuntimeComponentsBuilder { } /// Sets the time source. - pub fn with_time_source(mut self, time_source: Option) -> Self { - self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s)); + pub fn with_time_source( + mut self, + time_source: Option>, + ) -> Self { + self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } } @@ -533,11 +539,11 @@ impl RuntimeComponentsBuilder { #[cfg(feature = "test-util")] pub fn for_tests() -> Self { use crate::client::auth::AuthSchemeOptionResolver; - use crate::client::connectors::{HttpConnector, HttpConnectorFuture}; use crate::client::endpoint::{EndpointResolver, EndpointResolverParams}; + use crate::client::http::HttpClient; use crate::client::identity::Identity; use crate::client::identity::IdentityResolver; - use crate::client::orchestrator::{Future, HttpRequest}; + use crate::client::orchestrator::Future; use crate::client::retries::RetryStrategy; use aws_smithy_async::rt::sleep::AsyncSleep; use aws_smithy_async::time::TimeSource; @@ -557,10 +563,14 @@ impl RuntimeComponentsBuilder { } #[derive(Debug)] - struct FakeConnector; - impl HttpConnector for FakeConnector { - fn call(&self, _: HttpRequest) -> HttpConnectorFuture { - unreachable!("fake connector must be overridden for this test") + struct FakeClient; + impl HttpClient for FakeClient { + fn http_connector( + &self, + _: &crate::client::http::HttpConnectorSettings, + _: &RuntimeComponents, + ) -> crate::client::http::SharedHttpConnector { + unreachable!("fake client must be overridden for this test") } } @@ -642,7 +652,7 @@ impl RuntimeComponentsBuilder { .with_auth_scheme(FakeAuthScheme) .with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver)) .with_endpoint_resolver(Some(FakeEndpointResolver)) - .with_http_connector(Some(FakeConnector)) + .with_http_client(Some(FakeClient)) .with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver) .with_retry_classifiers(Some(RetryClassifiers::new())) .with_retry_strategy(Some(FakeRetryStrategy)) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs index 34548c0ab7..d0e529b67f 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs @@ -268,10 +268,13 @@ impl RuntimePlugins { #[cfg(test)] mod tests { use super::{RuntimePlugin, RuntimePlugins}; - use crate::client::connectors::{HttpConnector, HttpConnectorFuture, SharedHttpConnector}; + use crate::client::http::{ + http_client_fn, HttpClient, HttpConnector, HttpConnectorFuture, SharedHttpConnector, + }; use crate::client::orchestrator::HttpRequest; use crate::client::runtime_components::RuntimeComponentsBuilder; use crate::client::runtime_plugin::{Order, SharedRuntimePlugin}; + use crate::shared::IntoShared; use aws_smithy_http::body::SdkBody; use aws_smithy_types::config_bag::ConfigBag; use http::HeaderValue; @@ -392,7 +395,7 @@ mod tests { ) -> Cow<'_, RuntimeComponentsBuilder> { Cow::Owned( RuntimeComponentsBuilder::new("Plugin1") - .with_http_connector(Some(SharedHttpConnector::new(Connector1))), + .with_http_client(Some(http_client_fn(|_, _| Connector1.into_shared()))), ) } } @@ -409,11 +412,13 @@ mod tests { &self, current_components: &RuntimeComponentsBuilder, ) -> Cow<'_, RuntimeComponentsBuilder> { + let current = current_components.http_client().unwrap(); Cow::Owned( - RuntimeComponentsBuilder::new("Plugin2").with_http_connector(Some( - SharedHttpConnector::new(Connector2( - current_components.http_connector().unwrap(), - )), + RuntimeComponentsBuilder::new("Plugin2").with_http_client(Some( + http_client_fn(move |settings, components| { + let connector = current.http_connector(settings, components); + SharedHttpConnector::new(Connector2(connector)) + }), )), ) } @@ -426,11 +431,13 @@ mod tests { .with_client_plugin(Plugin1); let mut cfg = ConfigBag::base(); let components = plugins.apply_client_configuration(&mut cfg).unwrap(); + let fake_components = RuntimeComponentsBuilder::for_tests().build().unwrap(); // Use the resulting HTTP connector to make a response let resp = components - .http_connector() + .http_client() .unwrap() + .http_connector(&Default::default(), &fake_components) .call( http::Request::builder() .method("GET") diff --git a/rust-runtime/aws-smithy-runtime-api/src/shared.rs b/rust-runtime/aws-smithy-runtime-api/src/shared.rs index c45506c9f3..b3c778edb1 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/shared.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/shared.rs @@ -19,8 +19,8 @@ #![cfg_attr( feature = "client", doc = " -For example, [`SharedHttpConnector`](crate::client::connectors::SharedHttpConnector), is -a shared type for the [`HttpConnector`](crate::client::connectors::HttpConnector) trait, +For example, [`SharedHttpConnector`](crate::client::http::SharedHttpConnector), is +a shared type for the [`HttpConnector`](crate::client::http::HttpConnector) trait, which allows for sharing a single HTTP connector instance (and its connection pool) among multiple clients. " )] @@ -180,6 +180,14 @@ macro_rules! impl_shared_conversions { }; } +// TODO(TODO): Move these impls once aws-smithy-async is merged into aws-smithy-runtime-api +mod async_impls { + use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; + impl_shared_conversions!(convert SharedAsyncSleep from AsyncSleep using SharedAsyncSleep::new); + impl_shared_conversions!(convert SharedTimeSource from TimeSource using SharedTimeSource::new); +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 743d5d2dae..0ab6992620 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -13,8 +13,9 @@ repository = "https://github.com/awslabs/smithy-rs" client = ["aws-smithy-runtime-api/client"] http-auth = ["aws-smithy-runtime-api/http-auth"] test-util = ["aws-smithy-runtime-api/test-util", "dep:aws-smithy-protocol-test", "dep:tracing-subscriber", "dep:serde", "dep:serde_json"] -connector-hyper = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] -tls-rustls = ["dep:hyper-rustls", "dep:rustls", "connector-hyper"] +connector-hyper-0-14-x = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] +tls-rustls = ["dep:hyper-rustls", "dep:rustls"] +rt-tokio = ["tokio/rt"] [dependencies] aws-smithy-async = { path = "../aws-smithy-async" } @@ -44,7 +45,6 @@ approx = "0.5.1" aws-smithy-async = { path = "../aws-smithy-async", features = ["rt-tokio", "test-util"] } aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api", features = ["test-util"] } aws-smithy-types = { path = "../aws-smithy-types", features = ["test-util"] } -hyper-tls = { version = "0.5.0" } tokio = { version = "1.25", features = ["macros", "rt", "test-util"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-test = "0.2.1" diff --git a/rust-runtime/aws-smithy-runtime/external-types.toml b/rust-runtime/aws-smithy-runtime/external-types.toml index f735d80eef..fe24b1a1b0 100644 --- a/rust-runtime/aws-smithy-runtime/external-types.toml +++ b/rust-runtime/aws-smithy-runtime/external-types.toml @@ -3,8 +3,6 @@ allowed_external_types = [ "aws_smithy_async::*", "aws_smithy_http::*", "aws_smithy_types::*", - "aws_smithy_client::erase::DynConnector", - "aws_smithy_client::http_connector::ConnectorSettings", # TODO(audit-external-type-usage) We should newtype these or otherwise avoid exposing them "http::header::name::HeaderName", @@ -13,7 +11,7 @@ allowed_external_types = [ "http::uri::Uri", # Used for creating hyper connectors - "tower_service::Service", + "tower_service::Service", # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `test-util` feature "aws_smithy_protocol_test::MediaType", @@ -22,7 +20,7 @@ allowed_external_types = [ "serde::de::Deserialize", "hyper::client::connect::dns::Name", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `connector-hyper` feature + # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `connector-hyper-0-14-x` feature "hyper::client::client::Builder", "hyper::client::connect::Connection", "tokio::io::async_read::AsyncRead", diff --git a/rust-runtime/aws-smithy-runtime/src/client.rs b/rust-runtime/aws-smithy-runtime/src/client.rs index 71d1a0ea0f..392dc12023 100644 --- a/rust-runtime/aws-smithy-runtime/src/client.rs +++ b/rust-runtime/aws-smithy-runtime/src/client.rs @@ -6,11 +6,13 @@ /// Smithy auth scheme implementations. pub mod auth; -/// Built-in Smithy connectors. +pub mod dns; + +/// Built-in Smithy HTTP clients and connectors. /// -/// See the [module docs in `aws-smithy-runtime-api`](aws_smithy_runtime_api::client::connectors) -/// for more information about connectors. -pub mod connectors; +/// See the [module docs in `aws-smithy-runtime-api`](aws_smithy_runtime_api::client::http) +/// for more information about clients and connectors. +pub mod http; /// Utility to simplify config building for config and config overrides. pub mod config_override; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors.rs deleted file mode 100644 index 4666c04bc2..0000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/// Interceptor for connection poisoning. -pub mod connection_poisoning; - -#[cfg(feature = "test-util")] -pub mod test_util; - -/// Default HTTP and TLS connectors that use hyper and rustls. -#[cfg(feature = "connector-hyper")] -pub mod hyper_connector; - -// TODO(enableNewSmithyRuntimeCleanup): Delete this module -/// Unstable API for interfacing the old middleware connectors with the newer orchestrator connectors. -/// -/// Important: This module and its contents will be removed in the next release. -pub mod adapter { - use aws_smithy_client::erase::DynConnector; - use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; - use aws_smithy_runtime_api::client::orchestrator::HttpRequest; - use std::sync::{Arc, Mutex}; - - /// Adapts a [`DynConnector`] to the [`HttpConnector`] trait. - /// - /// This is a temporary adapter that allows the old-style tower-based connectors to - /// work with the new non-tower based architecture of the generated clients. - /// It will be removed in a future release. - #[derive(Debug)] - pub struct DynConnectorAdapter { - // `DynConnector` requires `&mut self`, so we need interior mutability to adapt to it - dyn_connector: Arc>, - } - - impl DynConnectorAdapter { - /// Creates a new `DynConnectorAdapter`. - pub fn new(dyn_connector: DynConnector) -> Self { - Self { - dyn_connector: Arc::new(Mutex::new(dyn_connector)), - } - } - } - - impl HttpConnector for DynConnectorAdapter { - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - let future = self.dyn_connector.lock().unwrap().call_lite(request); - HttpConnectorFuture::new(future) - } - } -} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs deleted file mode 100644 index dbd1d7c4cf..0000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Test connectors that never return data - -use aws_smithy_async::future::never::Never; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; -use aws_smithy_runtime_api::client::orchestrator::HttpRequest; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; - -/// A connector that will never respond. -/// -/// Returned futures will return Pending forever -#[derive(Clone, Debug, Default)] -pub struct NeverConnector { - invocations: Arc, -} - -impl NeverConnector { - /// Create a new never connector. - pub fn new() -> Self { - Default::default() - } - - /// Returns the number of invocations made to this connector. - pub fn num_calls(&self) -> usize { - self.invocations.load(Ordering::SeqCst) - } -} - -impl HttpConnector for NeverConnector { - fn call(&self, _request: HttpRequest) -> HttpConnectorFuture { - self.invocations.fetch_add(1, Ordering::SeqCst); - HttpConnectorFuture::new(async move { - Never::new().await; - unreachable!() - }) - } -} diff --git a/rust-runtime/aws-smithy-runtime/src/client/dns.rs b/rust-runtime/aws-smithy-runtime/src/client/dns.rs new file mode 100644 index 0000000000..8f4da84b6c --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/dns.rs @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Built-in DNS resolver implementations. + +#[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] +mod tokio { + use aws_smithy_runtime_api::client::dns::{DnsFuture, DnsResolver}; + use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + use std::net::ToSocketAddrs; + + /// DNS resolver that uses `tokio::spawn_blocking` to resolve DNS using the standard library. + /// + /// This implementation isn't available for WASM targets. + #[non_exhaustive] + #[derive(Debug, Default)] + pub struct TokioDnsResolver; + + impl TokioDnsResolver { + /// Creates a new Tokio DNS resolver + pub fn new() -> Self { + Self + } + } + + impl DnsResolver for TokioDnsResolver { + fn resolve_dns(&self, name: String) -> DnsFuture { + DnsFuture::new(async move { + let result = tokio::task::spawn_blocking(move || (name, 0).to_socket_addrs()).await; + match result { + Err(join_failure) => Err(IoError::new(IoErrorKind::Other, join_failure).into()), + Ok(Ok(dns_result)) => { + Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) + } + Ok(Err(dns_failure)) => Err(dns_failure.into()), + } + }) + } + } +} + +#[cfg(feature = "rt-tokio")] +pub use self::tokio::TokioDnsResolver; diff --git a/rust-runtime/aws-smithy-runtime/src/client/http.rs b/rust-runtime/aws-smithy-runtime/src/client/http.rs new file mode 100644 index 0000000000..b04cb91824 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http.rs @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; +use aws_smithy_runtime_api::client::runtime_plugin::{ + Order, SharedRuntimePlugin, StaticRuntimePlugin, +}; + +/// Interceptor for connection poisoning. +pub mod connection_poisoning; + +#[cfg(feature = "test-util")] +pub mod test_util; + +/// Default HTTP and TLS connectors that use hyper 0.14.x and rustls. +/// +/// This module is named after the hyper version number since we anticipate +/// needing to provide equivalent functionality for hyper 1.x in the future. +#[cfg(feature = "connector-hyper-0-14-x")] +pub mod hyper_014; + +/// Runtime plugin that provides a default connector. Intended to be used by the generated code. +#[doc(hidden)] +pub fn default_http_client_plugin() -> SharedRuntimePlugin { + let _default: Option = None; + #[cfg(feature = "connector-hyper-0-14-x")] + let _default = hyper_014::default_client(); + + let plugin = StaticRuntimePlugin::new() + .with_order(Order::Defaults) + .with_runtime_components( + RuntimeComponentsBuilder::new("default_http_client_plugin").with_http_client(_default), + ); + SharedRuntimePlugin::new(plugin) +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs b/rust-runtime/aws-smithy-runtime/src/client/http/connection_poisoning.rs similarity index 100% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/connection_poisoning.rs diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs similarity index 76% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 9285394020..47e013b4e8 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -5,28 +5,34 @@ use aws_smithy_async::future::timeout::TimedOutError; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_smithy_http::body::SdkBody; use aws_smithy_http::connection::{CaptureSmithyConnection, ConnectionMetadata}; use aws_smithy_http::result::ConnectorError; use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, + SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::retry::ErrorKind; use http::{Extensions, Uri}; use hyper::client::connect::{capture_connection, CaptureConnection, Connection, HttpInfo}; use hyper::service::Service; +use std::collections::HashMap; use std::error::Error; use std::fmt; use std::fmt::Debug; +use std::sync::RwLock; +use std::time::Duration; use tokio::io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls-rustls")] mod default_connector { use aws_smithy_async::rt::sleep::SharedAsyncSleep; - use aws_smithy_client::http_connector::ConnectorSettings; + use aws_smithy_runtime_api::client::http::HttpConnectorSettings; // Creating a `with_native_roots` HTTP client takes 300ms on OS X. Cache this so that we // don't need to repeatedly incur that cost. @@ -61,7 +67,7 @@ mod default_connector { }); pub(super) fn base( - settings: &ConnectorSettings, + settings: &HttpConnectorSettings, sleep: Option, ) -> super::HyperConnectorBuilder { let mut hyper = super::HyperConnector::builder().connector_settings(settings.clone()); @@ -80,9 +86,9 @@ mod default_connector { } } -/// Given `ConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated. +/// Given `HttpConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated. pub fn default_connector( - settings: &ConnectorSettings, + settings: &HttpConnectorSettings, sleep: Option, ) -> Option { #[cfg(feature = "tls-rustls")] @@ -98,6 +104,20 @@ pub fn default_connector( } } +/// Creates a hyper-backed HTTPS client from defaults depending on what cargo features are activated. +pub fn default_client() -> Option { + #[cfg(feature = "tls-rustls")] + { + tracing::trace!("creating a new default hyper 0.14.x client"); + Some(HyperClientBuilder::new().build_https()) + } + #[cfg(not(feature = "tls-rustls"))] + { + tracing::trace!("no default connector available"); + None + } +} + /// [`HttpConnector`] that uses [`hyper`] to make HTTP requests. /// /// This connector also implements socket connect and read timeouts. @@ -170,7 +190,7 @@ impl HttpConnector for HyperConnector { /// Builder for [`HyperConnector`]. #[derive(Default, Debug)] pub struct HyperConnectorBuilder { - connector_settings: Option, + connector_settings: Option, sleep_impl: Option, client_builder: Option, } @@ -228,8 +248,8 @@ impl HyperConnectorBuilder { /// /// Calling this is only necessary for testing or to use something other than /// [`default_async_sleep`]. - pub fn sleep_impl(mut self, sleep_impl: SharedAsyncSleep) -> Self { - self.sleep_impl = Some(sleep_impl); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.sleep_impl = Some(sleep_impl.into_shared()); self } @@ -243,7 +263,7 @@ impl HyperConnectorBuilder { } /// Configure the HTTP settings for the `HyperAdapter` - pub fn connector_settings(mut self, connector_settings: ConnectorSettings) -> Self { + pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self { self.connector_settings = Some(connector_settings); self } @@ -251,7 +271,7 @@ impl HyperConnectorBuilder { /// Configure the HTTP settings for the `HyperAdapter` pub fn set_connector_settings( &mut self, - connector_settings: Option, + connector_settings: Option, ) -> &mut Self { self.connector_settings = connector_settings; self @@ -391,6 +411,151 @@ fn find_source<'a, E: Error + 'static>(err: &'a (dyn Error + 'static)) -> Option None } +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct CacheKey { + connect_timeout: Option, + read_timeout: Option, +} + +impl From<&HttpConnectorSettings> for CacheKey { + fn from(value: &HttpConnectorSettings) -> Self { + Self { + connect_timeout: value.connect_timeout(), + read_timeout: value.read_timeout(), + } + } +} + +struct HyperClient { + connector_cache: RwLock>, + client_builder: hyper::client::Builder, + tcp_connector_fn: F, +} + +impl fmt::Debug for HyperClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HyperClient") + .field("connector_cache", &self.connector_cache) + .field("client_builder", &self.client_builder) + .finish() + } +} + +impl HttpClient for HyperClient +where + F: Fn() -> C + Send + Sync, + C: Clone + Send + Sync + 'static, + C: Service, + C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, +{ + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + let key = CacheKey::from(settings); + let mut connector = self.connector_cache.read().unwrap().get(&key).cloned(); + if connector.is_none() { + let mut cache = self.connector_cache.write().unwrap(); + // Short-circuit if another thread already wrote a connector to the cache for this key + if !cache.contains_key(&key) { + let mut builder = HyperConnector::builder() + .hyper_builder(self.client_builder.clone()) + .connector_settings(settings.clone()); + builder.set_sleep_impl(components.sleep_impl()); + + let tcp_connector = (self.tcp_connector_fn)(); + let connector = SharedHttpConnector::new(builder.build(tcp_connector)); + cache.insert(key.clone(), connector); + } + connector = cache.get(&key).cloned(); + } + + connector.expect("cache populated above") + } +} + +/// Builder for a hyper-backed [`HttpClient`] implementation. +/// +/// This builder can be used to customize the underlying TCP connector used, as well as +/// hyper client configuration. +#[derive(Clone, Default, Debug)] +pub struct HyperClientBuilder { + client_builder: Option, +} + +impl HyperClientBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Self::default() + } + + /// Override the Hyper client [`Builder`](hyper::client::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub fn hyper_builder(mut self, hyper_builder: hyper::client::Builder) -> Self { + self.client_builder = Some(hyper_builder); + self + } + + /// Override the Hyper client [`Builder`](hyper::client::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub fn set_hyper_builder( + &mut self, + hyper_builder: Option, + ) -> &mut Self { + self.client_builder = hyper_builder; + self + } + + /// Create a [`HyperConnector`] with the default rustls HTTPS implementation. + #[cfg(feature = "tls-rustls")] + pub fn build_https(self) -> SharedHttpClient { + self.build(default_connector::https()) + } + + /// Create a [`SharedHttpClient`] from this builder and a given connector. + /// + #[cfg_attr( + feature = "tls-rustls", + doc = "Use [`build_https`](HyperClientBuilder::build_https) if you don't want to provide a custom TCP connector." + )] + pub fn build(self, tcp_connector: C) -> SharedHttpClient + where + C: Clone + Send + Sync + 'static, + C: Service, + C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, + { + SharedHttpClient::new(HyperClient { + connector_cache: RwLock::new(HashMap::new()), + client_builder: self.client_builder.unwrap_or_default(), + tcp_connector_fn: move || tcp_connector.clone(), + }) + } + + #[cfg(test)] + fn build_with_fn(self, tcp_connector_fn: F) -> SharedHttpClient + where + F: Fn() -> C + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, + C: Service, + C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, + { + SharedHttpClient::new(HyperClient { + connector_cache: RwLock::new(HashMap::new()), + client_builder: self.client_builder.unwrap_or_default(), + tcp_connector_fn, + }) + } +} + mod timeout_middleware { use aws_smithy_async::future::timeout::{TimedOutError, Timeout}; use aws_smithy_async::rt::sleep::Sleep; @@ -600,7 +765,6 @@ mod timeout_middleware { use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_types::error::display::DisplayErrorContext; - use aws_smithy_types::timeout::TimeoutConfig; use hyper::client::connect::Connected; use std::time::Duration; use tokio::io::ReadBuf; @@ -699,11 +863,9 @@ mod timeout_middleware { #[tokio::test] async fn http_connect_timeout_works() { let tcp_connector = NeverConnects::default(); - let connector_settings = ConnectorSettings::from_timeout_config( - &TimeoutConfig::builder() - .connect_timeout(Duration::from_secs(1)) - .build(), - ); + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .build(); let hyper = HyperConnector::builder() .connector_settings(connector_settings) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) @@ -738,12 +900,10 @@ mod timeout_middleware { #[tokio::test] async fn http_read_timeout_works() { let tcp_connector = NeverReplies::default(); - let connector_settings = ConnectorSettings::from_timeout_config( - &TimeoutConfig::builder() - .connect_timeout(Duration::from_secs(1)) - .read_timeout(Duration::from_secs(2)) - .build(), - ); + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .read_timeout(Duration::from_secs(2)) + .build(); let hyper = HyperConnector::builder() .connector_settings(connector_settings) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) @@ -778,14 +938,71 @@ mod timeout_middleware { #[cfg(test)] mod test { use super::*; + use crate::client::http::test_util::NeverTcpConnector; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use http::Uri; use hyper::client::connect::{Connected, Connection}; use std::io::{Error, ErrorKind}; use std::pin::Pin; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + #[tokio::test] + async fn connector_selection() { + // Create a client that increments a count every time it creates a new HyperConnector + let creation_count = Arc::new(AtomicU32::new(0)); + let http_client = HyperClientBuilder::new().build_with_fn({ + let count = creation_count.clone(); + move || { + count.fetch_add(1, Ordering::Relaxed); + NeverTcpConnector::new() + } + }); + + // This configuration should result in 4 separate connectors with different timeout settings + let settings = [ + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(5)) + .read_timeout(Duration::from_secs(3)) + .build(), + ]; + + // Kick off thousands of parallel tasks that will try to create a connector + let components = RuntimeComponentsBuilder::for_tests().build().unwrap(); + let mut handles = Vec::new(); + for setting in &settings { + for _ in 0..1000 { + let client = http_client.clone(); + handles.push(tokio::spawn({ + let setting = setting.clone(); + let components = components.clone(); + async move { + let _ = client.http_connector(&setting, &components); + } + })); + } + } + for handle in handles { + handle.await.unwrap(); + } + + // Verify only 4 connectors were created amidst the chaos + assert_eq!(4, creation_count.load(Ordering::Relaxed)); + } + #[tokio::test] async fn hyper_io_error() { let connector = TestConnection { diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs similarity index 65% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs index 686b710197..e615f84911 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs @@ -3,39 +3,42 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Module with client connectors useful for testing. +//! Various fake clients for testing. //! -//! Each test connector is useful for different test use cases: +//! Each test client is useful for different test use cases: //! - [`capture_request`](capture_request::capture_request): If you don't care what the //! response is, but just want to check that the serialized request is what you expect, //! then use `capture_request`. Or, alternatively, if you don't care what the request //! is, but want to always respond with a given response, then capture request can also //! be useful since you can optionally give it a response to return. //! - [`dvr`]: If you want to record real-world traffic and then replay it later, then DVR's -//! [`RecordingConnector`](dvr::RecordingConnector) and [`ReplayingConnector`](dvr::ReplayingConnector) +//! [`RecordingClient`](dvr::RecordingClient) and [`ReplayingClient`](dvr::ReplayingClient) //! can accomplish this, and the recorded traffic can be saved to JSON and checked in. Note: if //! the traffic recording has sensitive information in it, such as signatures or authorization, //! you will need to manually scrub this out if you intend to store the recording alongside //! your tests. -//! - [`EventConnector`]: If you want to have a set list of requests and their responses in a test, +//! - [`EventClient`]: If you want to have a set list of requests and their responses in a test, //! then the event connector will be useful. On construction, it takes a list of tuples that represent //! each expected request and the response for that request. At the end of the test, you can ask the //! connector to verify that the requests matched the expectations. -//! - [`infallible_connection_fn`]: Allows you to create a connector from an infallible function +//! - [`infallible_client_fn`]: Allows you to create a connector from an infallible function //! that takes a request and returns a response. -//! - [`NeverConnector`]: Useful for testing timeouts, where you want the connector to never respond. +//! - [`NeverClient`]: Useful for testing timeouts, where you want the connector to never respond. mod capture_request; pub use capture_request::{capture_request, CaptureRequestHandler, CaptureRequestReceiver}; -#[cfg(feature = "connector-hyper")] +#[cfg(feature = "connector-hyper-0-14-x")] pub mod dvr; -mod event_connector; -pub use event_connector::{ConnectionEvent, EventConnector}; +mod event; +pub use event::{ConnectionEvent, EventClient}; mod infallible; -pub use infallible::infallible_connection_fn; +pub use infallible::infallible_client_fn; mod never; -pub use never::NeverConnector; +pub use never::NeverClient; + +#[cfg(feature = "connector-hyper-0-14-x")] +pub use never::NeverTcpConnector; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs similarity index 83% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs index 7721af15de..06915c942e 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs @@ -4,8 +4,12 @@ */ use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use tokio::sync::oneshot; @@ -36,6 +40,16 @@ impl HttpConnector for CaptureRequestHandler { } } +impl HttpClient for CaptureRequestHandler { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} + /// Receiver for [`CaptureRequestHandler`](CaptureRequestHandler) #[derive(Debug)] pub struct CaptureRequestReceiver { @@ -70,9 +84,9 @@ impl CaptureRequestReceiver { /// /// Example: /// ```compile_fail -/// let (server, request) = capture_request(None); +/// let (capture_client, request) = capture_request(None); /// let conf = aws_sdk_sts::Config::builder() -/// .http_connector(server) +/// .http_client(capture_client) /// .build(); /// let client = aws_sdk_sts::Client::from_conf(conf); /// let _ = client.assume_role_with_saml().send().await; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs similarity index 94% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs index 90f37b95ba..a4c5138dc5 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs @@ -18,12 +18,12 @@ mod record; mod replay; pub use aws_smithy_protocol_test::MediaType; -pub use record::RecordingConnector; -pub use replay::ReplayingConnector; +pub use record::RecordingClient; +pub use replay::ReplayingClient; /// A complete traffic recording /// -/// A traffic recording can be replayed with [`RecordingConnector`](RecordingConnector) +/// A traffic recording can be replayed with [`RecordingClient`](RecordingClient) #[derive(Debug, Serialize, Deserialize)] pub struct NetworkTraffic { events: Vec, @@ -232,7 +232,7 @@ mod tests { use super::*; use aws_smithy_http::body::SdkBody; use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_runtime_api::client::connectors::{HttpConnector, SharedHttpConnector}; + use aws_smithy_runtime_api::client::http::{HttpConnector, SharedHttpConnector}; use bytes::Bytes; use http::Uri; use std::error::Error; @@ -244,8 +244,8 @@ mod tests { // make a request, then verify that the same traffic was recorded. let network_traffic = fs::read_to_string("test-data/example.com.json")?; let network_traffic: NetworkTraffic = serde_json::from_str(&network_traffic)?; - let inner = ReplayingConnector::new(network_traffic.events.clone()); - let connection = RecordingConnector::new(SharedHttpConnector::new(inner.clone())); + let inner = ReplayingClient::new(network_traffic.events.clone()); + let connection = RecordingClient::new(SharedHttpConnector::new(inner.clone())); let req = http::Request::post("https://www.example.com") .body(SdkBody::from("hello world")) .unwrap(); diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs similarity index 87% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs index c175b24d3c..8ab4e56d3e 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs @@ -8,10 +8,12 @@ use super::{ Version, }; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, }; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use http_body::Body; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -19,21 +21,21 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::{fs, io}; use tokio::task::JoinHandle; -/// Recording Connection Wrapper +/// Recording client /// -/// RecordingConnector wraps an inner connection and records all traffic, enabling traffic replay. +/// `RecordingClient` wraps an inner connection and records all traffic, enabling traffic replay. #[derive(Clone, Debug)] -pub struct RecordingConnector { +pub struct RecordingClient { pub(crate) data: Arc>>, pub(crate) num_events: Arc, pub(crate) inner: SharedHttpConnector, } #[cfg(all(feature = "tls-rustls"))] -impl RecordingConnector { - /// Construct a recording connection wrapping a default HTTPS implementation +impl RecordingClient { + /// Construct a recording connection wrapping a default HTTPS implementation without any timeouts. pub fn https() -> Self { - use crate::client::connectors::hyper_connector::HyperConnector; + use crate::client::http::hyper_014::HyperConnector; Self { data: Default::default(), num_events: Arc::new(AtomicUsize::new(0)), @@ -42,13 +44,13 @@ impl RecordingConnector { } } -impl RecordingConnector { +impl RecordingClient { /// Create a new recording connection from a connection - pub fn new(underlying_connector: SharedHttpConnector) -> Self { + pub fn new(underlying_connector: impl IntoShared) -> Self { Self { data: Default::default(), num_events: Arc::new(AtomicUsize::new(0)), - inner: underlying_connector, + inner: underlying_connector.into_shared(), } } @@ -141,7 +143,7 @@ fn record_body( }) } -impl HttpConnector for RecordingConnector { +impl HttpConnector for RecordingClient { fn call(&self, mut request: HttpRequest) -> HttpConnectorFuture { let event_id = self.next_id(); // A request has three phases: @@ -200,3 +202,13 @@ impl HttpConnector for RecordingConnector { HttpConnectorFuture::new(fut) } } + +impl HttpClient for RecordingClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs similarity index 91% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs index 4388e939be..f9386e624d 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs @@ -7,14 +7,19 @@ use super::{Action, ConnectionId, Direction, Event, NetworkTraffic}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_protocol_test::MediaType; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use bytes::{Bytes, BytesMut}; use http::{Request, Version}; use http_body::Body; use std::collections::{HashMap, VecDeque}; use std::error::Error; +use std::fmt; use std::ops::DerefMut; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -46,16 +51,25 @@ impl Waitable { } } -/// Replay traffic recorded by a [`RecordingConnector`](super::RecordingConnector) -#[derive(Clone, Debug)] -pub struct ReplayingConnector { +/// Replay traffic recorded by a [`RecordingClient`](super::RecordingClient) +#[derive(Clone)] +pub struct ReplayingClient { live_events: Arc>>>, verifiable_events: Arc>>, num_events: Arc, recorded_requests: Arc>>>>, } -impl ReplayingConnector { +// Ideally, this would just derive Debug, but that makes the tests in aws-config think they found AWS secrets +// when really it's just the test response data they're seeing from the Debug impl of this client. +// This is just a quick workaround. A better fix can be considered later. +impl fmt::Debug for ReplayingClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("test_util::dvr::ReplayingClient") + } +} + +impl ReplayingClient { fn next_id(&self) -> ConnectionId { ConnectionId(self.num_events.fetch_add(1, Ordering::Relaxed)) } @@ -204,7 +218,7 @@ impl ReplayingConnector { .collect(); let verifiable_events = Arc::new(verifiable_events); - ReplayingConnector { + ReplayingClient { live_events: Arc::new(Mutex::new(event_map)), num_events: Arc::new(AtomicUsize::new(0)), recorded_requests: Default::default(), @@ -263,7 +277,7 @@ fn convert_version(version: &str) -> Version { } } -impl HttpConnector for ReplayingConnector { +impl HttpConnector for ReplayingClient { fn call(&self, mut request: HttpRequest) -> HttpConnectorFuture { let event_id = self.next_id(); tracing::debug!("received event {}: {request:?}", event_id.0); @@ -349,3 +363,13 @@ impl HttpConnector for ReplayingConnector { HttpConnectorFuture::new(fut) } } + +impl HttpClient for ReplayingClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs similarity index 66% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs index 2150235c03..9b5ead9e97 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs @@ -7,21 +7,24 @@ use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use http::header::{HeaderName, CONTENT_TYPE}; -use std::fmt::Debug; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; type ConnectionEvents = Vec; -/// Test data for the [`EventConnector`]. +/// Test data for the [`EventClient`]. /// /// Each `ConnectionEvent` represents one HTTP request and response /// through the connector. Optionally, a latency value can be set to simulate -/// network latency (done via async sleep in the `EventConnector`). +/// network latency (done via async sleep in the `EventClient`). #[derive(Debug)] pub struct ConnectionEvent { latency: Duration, @@ -112,26 +115,68 @@ impl ValidateRequest { } } -/// Request/response event-driven connector for use in tests. +/// Request/response event-driven client for use in tests. /// /// A basic test connection. It will: /// - Respond to requests with a preloaded series of responses /// - Record requests for future examination -#[derive(Debug, Clone)] -pub struct EventConnector { +#[derive(Clone, Debug)] +pub struct EventClient { data: Arc>, requests: Arc>>, sleep_impl: SharedAsyncSleep, } -impl EventConnector { +impl EventClient { /// Creates a new event connector. - pub fn new(mut data: ConnectionEvents, sleep_impl: impl Into) -> Self { + pub fn new(mut data: ConnectionEvents, sleep_impl: impl IntoShared) -> Self { data.reverse(); - EventConnector { + EventClient { data: Arc::new(Mutex::new(data)), requests: Default::default(), - sleep_impl: sleep_impl.into(), + sleep_impl: sleep_impl.into_shared(), + } + } + + /// Returns an iterator over the actual requests that were made. + pub fn actual_requests(&self) -> impl Iterator + '_ { + // The iterator trait doesn't allow us to specify a lifetime on `self` in the `next()` method, + // so we have to do some unsafe code in order to actually implement this iterator without + // angering the borrow checker. + struct Iter<'a> { + // We store an exclusive lock to the data so that the data is completely immutable + _guard: MutexGuard<'a, Vec>, + // We store a pointer into the immutable data for accessing it later + values: *const ValidateRequest, + len: usize, + next_index: usize, + } + impl<'a> Iterator for Iter<'a> { + type Item = &'a HttpRequest; + + fn next(&mut self) -> Option { + // Safety: check the next index is in bounds + if self.next_index >= self.len { + None + } else { + // Safety: It is OK to offset into the pointer and dereference since we did a bounds check. + // It is OK to assign lifetime 'a to the reference since we hold the mutex guard for all of lifetime 'a. + let next = unsafe { + let offset = self.values.add(self.next_index); + &*offset + }; + self.next_index += 1; + Some(&next.actual) + } + } + } + + let guard = self.requests.lock().unwrap(); + Iter { + values: guard.as_ptr(), + len: guard.len(), + _guard: guard, + next_index: 0, } } @@ -162,7 +207,7 @@ impl EventConnector { } } -impl HttpConnector for EventConnector { +impl HttpConnector for EventClient { fn call(&self, request: HttpRequest) -> HttpConnectorFuture { let (res, simulated_latency) = if let Some(event) = self.data.lock().unwrap().pop() { self.requests.lock().unwrap().push(ValidateRequest { @@ -173,7 +218,10 @@ impl HttpConnector for EventConnector { (Ok(event.res.map(SdkBody::from)), event.latency) } else { ( - Err(ConnectorError::other("No more data".into(), None)), + Err(ConnectorError::other( + "EventClient: no more test data available to respond with".into(), + None, + )), Duration::from_secs(0), ) }; @@ -185,3 +233,13 @@ impl HttpConnector for EventConnector { }) } } + +impl HttpClient for EventClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs similarity index 51% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs index b311464774..491f26c0e2 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs @@ -5,32 +5,35 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; -use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, + SharedHttpConnector, }; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use std::fmt; use std::sync::Arc; -/// Create a [`SharedHttpConnector`] from `Fn(http:Request) -> http::Response` +/// Create a [`SharedHttpClient`] from `Fn(http:Request) -> http::Response` /// /// # Examples /// /// ```rust -/// use aws_smithy_runtime::client::connectors::test_util::infallible_connection_fn; -/// let connector = infallible_connection_fn(|_req| http::Response::builder().status(200).body("OK!").unwrap()); +/// use aws_smithy_runtime::client::http::test_util::infallible_client_fn; +/// let http_client = infallible_client_fn(|_req| http::Response::builder().status(200).body("OK!").unwrap()); /// ``` -pub fn infallible_connection_fn( +pub fn infallible_client_fn( f: impl Fn(http::Request) -> http::Response + Send + Sync + 'static, -) -> SharedHttpConnector +) -> SharedHttpClient where B: Into, { - SharedHttpConnector::new(InfallibleConnectorFn::new(f)) + InfallibleClientFn::new(f).into_shared() } #[derive(Clone)] -struct InfallibleConnectorFn { +struct InfallibleClientFn { #[allow(clippy::type_complexity)] response: Arc< dyn Fn(http::Request) -> Result, ConnectorError> @@ -39,13 +42,13 @@ struct InfallibleConnectorFn { >, } -impl fmt::Debug for InfallibleConnectorFn { +impl fmt::Debug for InfallibleClientFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InfallibleConnectorFn").finish() + f.debug_struct("InfallibleClientFn").finish() } } -impl InfallibleConnectorFn { +impl InfallibleClientFn { fn new>( f: impl Fn(http::Request) -> http::Response + Send + Sync + 'static, ) -> Self { @@ -55,8 +58,18 @@ impl InfallibleConnectorFn { } } -impl HttpConnector for InfallibleConnectorFn { +impl HttpConnector for InfallibleClientFn { fn call(&self, request: HttpRequest) -> HttpConnectorFuture { HttpConnectorFuture::ready((self.response)(request)) } } + +impl HttpClient for InfallibleClientFn { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs new file mode 100644 index 0000000000..6afff513d2 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs @@ -0,0 +1,176 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Test connectors that never return data + +use aws_smithy_async::future::never::Never; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; +use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +/// A client that will never respond. +/// +/// Returned futures will return `Pending` forever +#[derive(Clone, Debug, Default)] +pub struct NeverClient { + invocations: Arc, +} + +impl NeverClient { + /// Create a new never connector. + pub fn new() -> Self { + Default::default() + } + + /// Returns the number of invocations made to this connector. + pub fn num_calls(&self) -> usize { + self.invocations.load(Ordering::SeqCst) + } +} + +impl HttpConnector for NeverClient { + fn call(&self, _request: HttpRequest) -> HttpConnectorFuture { + self.invocations.fetch_add(1, Ordering::SeqCst); + HttpConnectorFuture::new(async move { + Never::new().await; + unreachable!() + }) + } +} + +impl HttpClient for NeverClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} + +/// A TCP connector that never connects. +// In the future, this can be available for multiple hyper version feature flags, with the impls gated between individual features +#[cfg(feature = "connector-hyper-0-14-x")] +#[derive(Clone, Debug, Default)] +pub struct NeverTcpConnector; + +#[cfg(feature = "connector-hyper-0-14-x")] +impl NeverTcpConnector { + /// Creates a new `NeverTcpConnector`. + pub fn new() -> Self { + Self + } +} + +#[cfg(feature = "connector-hyper-0-14-x")] +impl hyper::service::Service for NeverTcpConnector { + type Response = connection::NeverTcpConnection; + type Error = aws_smithy_runtime_api::box_error::BoxError; + type Future = std::pin::Pin< + Box> + Send + Sync>, + >; + + fn poll_ready( + &mut self, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, _: http::Uri) -> Self::Future { + Box::pin(async { + Never::new().await; + unreachable!() + }) + } +} + +#[cfg(feature = "connector-hyper-0-14-x")] +mod connection { + use hyper::client::connect::{Connected, Connection}; + use std::io::Error; + use std::pin::Pin; + use std::task::{Context, Poll}; + use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + + /// A connection type that appeases hyper's trait bounds for a TCP connector, but will panic if any of its traits are used. + #[non_exhaustive] + #[derive(Debug, Default)] + pub struct NeverTcpConnection; + + impl Connection for NeverTcpConnection { + fn connected(&self) -> Connected { + unreachable!() + } + } + + impl AsyncRead for NeverTcpConnection { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut ReadBuf<'_>, + ) -> Poll> { + unreachable!() + } + } + + impl AsyncWrite for NeverTcpConnection { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + unreachable!() + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unreachable!() + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unreachable!() + } + } +} + +#[cfg(all(test, feature = "connector-hyper-0-14-x"))] +#[tokio::test] +async fn never_tcp_connector_plugs_into_hyper_014() { + use super::*; + use crate::client::http::hyper_014::HyperClientBuilder; + use aws_smithy_async::rt::sleep::TokioSleep; + use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + use std::time::Duration; + + // it should compile + let client = HyperClientBuilder::new().build(NeverTcpConnector::new()); + let components = RuntimeComponentsBuilder::for_tests() + .with_sleep_impl(Some(TokioSleep::new())) + .build() + .unwrap(); + let http_connector = client.http_connector( + &HttpConnectorSettings::builder() + .connect_timeout(Duration::from_millis(100)) + .build(), + &components, + ); + + let err = http_connector + .call( + http::Request::builder() + .uri("https://example.com/") + .body(SdkBody::empty()) + .unwrap(), + ) + .await + .expect_err("it should time out"); + assert!(dbg!(err).is_timeout()); +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 85fdd34564..e66e3b268d 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -16,7 +16,7 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http::byte_stream::ByteStream; use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::connectors::HttpConnector; +use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnector, HttpConnectorSettings}; use aws_smithy_runtime_api::client::interceptors::context::{ Error, Input, InterceptorContext, Output, RewindResult, }; @@ -30,6 +30,7 @@ use aws_smithy_runtime_api::client::ser_de::{ RequestSerializer, ResponseDeserializer, SharedRequestSerializer, SharedResponseDeserializer, }; use aws_smithy_types::config_bag::ConfigBag; +use aws_smithy_types::timeout::TimeoutConfig; use std::mem; use tracing::{debug, debug_span, instrument, trace, Instrument}; @@ -356,10 +357,18 @@ async fn try_attempt( let response = halt_on_err!([ctx] => { let request = ctx.take_request().expect("set during serialization"); trace!(request = ?request, "transmitting request"); - let connector = halt_on_err!([ctx] => runtime_components.http_connector().ok_or_else(|| + let http_client = halt_on_err!([ctx] => runtime_components.http_client().ok_or_else(|| OrchestratorError::other("No HTTP connector was available to send this request. \ Enable the `rustls` crate feature or set a connector to fix this.") )); + let timeout_config = cfg.load::().expect("timeout config must be set"); + let settings = { + let mut builder = HttpConnectorSettings::builder(); + builder.set_connect_timeout(timeout_config.connect_timeout()); + builder.set_read_timeout(timeout_config.read_timeout()); + builder.build() + }; + let connector = http_client.http_connector(&settings, runtime_components); connector.call(request).await.map_err(OrchestratorError::connector) }); trace!(response = ?response, "received response from service"); @@ -442,12 +451,12 @@ mod tests { use aws_smithy_runtime_api::client::auth::{ AuthSchemeOptionResolverParams, SharedAuthSchemeOptionResolver, }; - use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, - }; use aws_smithy_runtime_api::client::endpoint::{ EndpointResolverParams, SharedEndpointResolver, }; + use aws_smithy_runtime_api::client::http::{ + http_client_fn, HttpConnector, HttpConnectorFuture, + }; use aws_smithy_runtime_api::client::interceptors::context::{ AfterDeserializationInterceptorContextRef, BeforeDeserializationInterceptorContextMut, BeforeDeserializationInterceptorContextRef, BeforeSerializationInterceptorContextMut, @@ -460,6 +469,7 @@ mod tests { use aws_smithy_runtime_api::client::retries::SharedRetryStrategy; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins}; + use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer}; use std::borrow::Cow; use std::sync::atomic::{AtomicBool, Ordering}; @@ -515,7 +525,9 @@ mod tests { .with_endpoint_resolver(Some(SharedEndpointResolver::new( StaticUriEndpointResolver::http_localhost(8080), ))) - .with_http_connector(Some(SharedHttpConnector::new(OkConnector::new()))) + .with_http_client(Some(http_client_fn(|_, _| { + OkConnector::new().into_shared() + }))) .with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new( StaticAuthSchemeOptionResolver::new(vec![NO_AUTH_SCHEME_ID]), ))), @@ -530,6 +542,7 @@ mod tests { layer.store_put(EndpointResolverParams::new("dontcare")); layer.store_put(SharedRequestSerializer::new(new_request_serializer())); layer.store_put(SharedResponseDeserializer::new(new_response_deserializer())); + layer.store_put(TimeoutConfig::builder().build()); Some(layer.freeze()) } diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index ac61dc7222..c7638ef5d8 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -4,6 +4,7 @@ */ use crate::client::auth::no_auth::{NoAuthScheme, NO_AUTH_SCHEME_ID}; +use crate::client::http::default_http_client_plugin; use crate::client::identity::no_auth::NoAuthIdentityResolver; use crate::client::orchestrator::endpoints::StaticUriEndpointResolver; use crate::client::retries::strategy::{NeverRetryStrategy, StandardRetryStrategy}; @@ -15,8 +16,8 @@ use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptio use aws_smithy_runtime_api::client::auth::{ AuthSchemeOptionResolverParams, SharedAuthScheme, SharedAuthSchemeOptionResolver, }; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; use aws_smithy_runtime_api::client::endpoint::{EndpointResolverParams, SharedEndpointResolver}; +use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::client::identity::SharedIdentityResolver; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output}; use aws_smithy_runtime_api::client::interceptors::SharedInterceptor; @@ -33,6 +34,7 @@ use aws_smithy_runtime_api::client::ser_de::{ use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::config_bag::{ConfigBag, Layer}; use aws_smithy_types::retry::RetryConfig; +use aws_smithy_types::timeout::TimeoutConfig; use std::borrow::Cow; use std::fmt; use std::marker::PhantomData; @@ -189,8 +191,8 @@ impl OperationBuilder { self } - pub fn http_connector(mut self, connector: impl IntoShared) -> Self { - self.runtime_components.set_http_connector(Some(connector)); + pub fn http_client(mut self, connector: impl IntoShared) -> Self { + self.runtime_components.set_http_client(Some(connector)); self } @@ -223,6 +225,11 @@ impl OperationBuilder { self } + pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self { + self.config.store_put(timeout_config); + self + } + pub fn no_auth(mut self) -> Self { self.config .store_put(AuthSchemeOptionResolverParams::new(())); @@ -239,13 +246,15 @@ impl OperationBuilder { self } - pub fn sleep_impl(mut self, async_sleep: SharedAsyncSleep) -> Self { - self.runtime_components.set_sleep_impl(Some(async_sleep)); + pub fn sleep_impl(mut self, async_sleep: impl IntoShared) -> Self { + self.runtime_components + .set_sleep_impl(Some(async_sleep.into_shared())); self } - pub fn time_source(mut self, time_source: SharedTimeSource) -> Self { - self.runtime_components.set_time_source(Some(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.runtime_components + .set_time_source(Some(time_source.into_shared())); self } @@ -306,11 +315,13 @@ impl OperationBuilder { pub fn build(self) -> Operation { let service_name = self.service_name.expect("service_name required"); let operation_name = self.operation_name.expect("operation_name required"); - let mut runtime_plugins = RuntimePlugins::new().with_client_plugin( - StaticRuntimePlugin::new() - .with_config(self.config.freeze()) - .with_runtime_components(self.runtime_components), - ); + let mut runtime_plugins = RuntimePlugins::new() + .with_client_plugin(default_http_client_plugin()) + .with_client_plugin( + StaticRuntimePlugin::new() + .with_config(self.config.freeze()) + .with_runtime_components(self.runtime_components), + ); for runtime_plugin in self.runtime_plugins { runtime_plugins = runtime_plugins.with_client_plugin(runtime_plugin); } @@ -323,8 +334,8 @@ impl OperationBuilder { .expect("the runtime plugins should succeed"); assert!( - components.http_connector().is_some(), - "a http_connector is required" + components.http_client().is_some(), + "a http_client is required" ); assert!( components.endpoint_resolver().is_some(), @@ -346,6 +357,10 @@ impl OperationBuilder { config.load::().is_some(), "endpoint resolver params are required" ); + assert!( + config.load::().is_some(), + "timeout config is required" + ); } Operation { @@ -360,7 +375,7 @@ impl OperationBuilder { #[cfg(all(test, feature = "test-util"))] mod tests { use super::*; - use crate::client::connectors::test_util::{capture_request, ConnectionEvent, EventConnector}; + use crate::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; use crate::client::retries::classifier::HttpStatusCodeClassifier; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; @@ -378,10 +393,11 @@ mod tests { let operation = Operation::builder() .service_name("test") .operation_name("test") - .http_connector(SharedHttpConnector::new(connector)) + .http_client(connector) .endpoint_url("http://localhost:1234") .no_auth() .no_retry() + .timeout_config(TimeoutConfig::disabled()) .serializer(|input: String| { Ok(http::Request::builder() .body(SdkBody::from(input.as_bytes())) @@ -408,7 +424,7 @@ mod tests { #[tokio::test] async fn operation_retries() { - let connector = EventConnector::new( + let connector = EventClient::new( vec![ ConnectionEvent::new( http::Request::builder() @@ -436,13 +452,14 @@ mod tests { let operation = Operation::builder() .service_name("test") .operation_name("test") - .http_connector(SharedHttpConnector::new(connector.clone())) + .http_client(connector.clone()) .endpoint_url("http://localhost:1234") .no_auth() .retry_classifiers( RetryClassifiers::new().with_classifier(HttpStatusCodeClassifier::default()), ) .standard_retry(&RetryConfig::standard()) + .timeout_config(TimeoutConfig::disabled()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .serializer(|input: String| { Ok(http::Request::builder() diff --git a/rust-runtime/aws-smithy-runtime/src/client/timeout.rs b/rust-runtime/aws-smithy-runtime/src/client/timeout.rs index c149878dda..e2e26418eb 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/timeout.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/timeout.rs @@ -5,7 +5,7 @@ use aws_smithy_async::future::timeout::Timeout; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::SdkError; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_types::config_bag::ConfigBag; diff --git a/tools/ci-resources/tls-stub/Cargo.toml b/tools/ci-resources/tls-stub/Cargo.toml index aea6e0a76d..a70780c482 100644 --- a/tools/ci-resources/tls-stub/Cargo.toml +++ b/tools/ci-resources/tls-stub/Cargo.toml @@ -13,7 +13,7 @@ publish = false aws-config = {path = "../../../aws/sdk/build/aws-sdk/sdk/aws-config", features = ["client-hyper"] } aws-credential-types = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-credential-types", features = ["hardcoded-credentials"] } aws-sdk-sts = { path = "../../../aws/sdk/build/aws-sdk/sdk/sts" } -aws-smithy-client = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["client-hyper", "rustls"] } +aws-smithy-runtime = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } exitcode = "1" hyper-rustls = { version = "0.24", features = ["rustls-native-certs", "http2"] } rustls = "0.21" diff --git a/tools/ci-resources/tls-stub/src/main.rs b/tools/ci-resources/tls-stub/src/main.rs index 8daebc5a7d..f674d0116b 100644 --- a/tools/ci-resources/tls-stub/src/main.rs +++ b/tools/ci-resources/tls-stub/src/main.rs @@ -11,6 +11,7 @@ use std::time::Duration; use aws_config::timeout::TimeoutConfig; use aws_credential_types::Credentials; use aws_sdk_sts::error::SdkError; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; #[cfg(debug_assertions)] use x509_parser::prelude::*; @@ -103,15 +104,15 @@ async fn create_client( .unwrap() .with_root_certificates(roots) .with_no_client_auth(); - let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_only() .enable_http1() .enable_http2() .build(); - let smithy_connector = aws_smithy_client::hyper_ext::Adapter::builder().build(https_connector); + let http_client = HyperClientBuilder::new().build(tls_connector); let sdk_config = aws_config::from_env() - .http_connector(smithy_connector) + .http_client(http_client) .credentials_provider(credentials) .region("us-nether-1") .endpoint_url(format!("https://{host}:{port}")) From d2eef3f995f62e60e0db20c743779a78adae8334 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 2 Oct 2023 11:26:52 -0700 Subject: [PATCH 02/23] Fix several CI issues --- aws/rust-runtime/aws-endpoint/Cargo.toml | 1 - aws/rust-runtime/aws-http/Cargo.toml | 1 - examples/python/pokemon-service-test/Cargo.toml | 2 +- examples/python/pokemon-service-test/tests/helpers.rs | 6 +++--- rust-runtime/aws-smithy-runtime-api/src/shared.rs | 2 +- rust-runtime/aws-smithy-runtime/Cargo.toml | 1 - .../aws-smithy-runtime/src/client/http/hyper_014.rs | 2 +- 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/aws/rust-runtime/aws-endpoint/Cargo.toml b/aws/rust-runtime/aws-endpoint/Cargo.toml index 67cb4d4e6e..5a6846d2d6 100644 --- a/aws/rust-runtime/aws-endpoint/Cargo.toml +++ b/aws/rust-runtime/aws-endpoint/Cargo.toml @@ -12,7 +12,6 @@ aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types"} aws-types = { path = "../aws-types" } http = "0.2.3" -regex = { version = "1.5.5", default-features = false, features = ["std"] } tracing = "0.1" [package.metadata.docs.rs] diff --git a/aws/rust-runtime/aws-http/Cargo.toml b/aws/rust-runtime/aws-http/Cargo.toml index 7b01057016..fc25360575 100644 --- a/aws/rust-runtime/aws-http/Cargo.toml +++ b/aws/rust-runtime/aws-http/Cargo.toml @@ -15,7 +15,6 @@ aws-types = { path = "../aws-types" } bytes = "1.1" http = "0.2.3" http-body = "0.4.5" -lazy_static = "1.4.0" tracing = "0.1" percent-encoding = "2.1.0" pin-project-lite = "0.2.9" diff --git a/examples/python/pokemon-service-test/Cargo.toml b/examples/python/pokemon-service-test/Cargo.toml index b4084185c2..a7960c24e4 100644 --- a/examples/python/pokemon-service-test/Cargo.toml +++ b/examples/python/pokemon-service-test/Cargo.toml @@ -17,7 +17,7 @@ tokio-rustls = "0.24.0" hyper-rustls = { version = "0.24", features = ["http2"] } # Local paths -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client/", features = ["rustls"] } +aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime/", features = ["client", "connector-hyper-0-14-x"] } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http/" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types/" } pokemon-service-client = { path = "../pokemon-service-client/" } diff --git a/examples/python/pokemon-service-test/tests/helpers.rs b/examples/python/pokemon-service-test/tests/helpers.rs index 708e17fe88..53064e3402 100644 --- a/examples/python/pokemon-service-test/tests/helpers.rs +++ b/examples/python/pokemon-service-test/tests/helpers.rs @@ -8,7 +8,7 @@ use std::io::BufReader; use std::process::Command; use std::time::Duration; -use aws_smithy_client::hyper_ext::Adapter; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; use command_group::{CommandGroup, GroupChild}; use pokemon_service_client::{Client, Config}; use tokio::time; @@ -102,7 +102,7 @@ pub fn http2_client() -> PokemonClient { let mut roots = tokio_rustls::rustls::RootCertStore::empty(); roots.add_parsable_certificates(&certs); - let connector = hyper_rustls::HttpsConnectorBuilder::new() + let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config( tokio_rustls::rustls::ClientConfig::builder() .with_safe_defaults() @@ -115,7 +115,7 @@ pub fn http2_client() -> PokemonClient { let base_url = PokemonServiceVariant::Http2.base_url(); let config = Config::builder() - .http_connector(Adapter::builder().build(connector)) + .http_client(HyperClientBuilder::new().build(tls_connector)) .endpoint_url(base_url) .build(); Client::from_conf(config) diff --git a/rust-runtime/aws-smithy-runtime-api/src/shared.rs b/rust-runtime/aws-smithy-runtime-api/src/shared.rs index b3c778edb1..f3f8630494 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/shared.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/shared.rs @@ -180,7 +180,7 @@ macro_rules! impl_shared_conversions { }; } -// TODO(TODO): Move these impls once aws-smithy-async is merged into aws-smithy-runtime-api +// TODO(https://github.com/awslabs/smithy-rs/issues/3016): Move these impls once aws-smithy-async is merged into aws-smithy-runtime-api mod async_impls { use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 0ab6992620..8072c9684a 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -19,7 +19,6 @@ rt-tokio = ["tokio/rt"] [dependencies] aws-smithy-async = { path = "../aws-smithy-async" } -aws-smithy-client = { path = "../aws-smithy-client" } aws-smithy-http = { path = "../aws-smithy-http" } aws-smithy-protocol-test = { path = "../aws-smithy-protocol-test", optional = true } aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" } diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 47e013b4e8..03550d0ada 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -935,7 +935,7 @@ mod timeout_middleware { } } -#[cfg(test)] +#[cfg(all(test, feature = "test-util"))] mod test { use super::*; use crate::client::http::test_util::NeverTcpConnector; From d2a6d1c15b3edd797729188c6024e5b5c6cb51e8 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 2 Oct 2023 16:10:35 -0700 Subject: [PATCH 03/23] Fix S3 wire mock tests --- aws/sdk/integration-tests/s3/Cargo.toml | 2 +- .../integration-tests/s3/tests/reconnects.rs | 6 +- .../timestreamquery/tests/endpoint_disco.rs | 27 +- rust-runtime/aws-smithy-runtime/Cargo.toml | 5 +- .../src/client/http/hyper_014.rs | 2 +- .../src/client/http/test_util.rs | 3 + .../src/client/http/test_util/wire.rs | 356 ++++++++++++++++++ 7 files changed, 384 insertions(+), 17 deletions(-) create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index a435389577..a525f69815 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -21,7 +21,7 @@ aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util", "rt-tokio"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } -aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util", "wire-mock"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } diff --git a/aws/sdk/integration-tests/s3/tests/reconnects.rs b/aws/sdk/integration-tests/s3/tests/reconnects.rs index 83583395dd..5390cb2850 100644 --- a/aws/sdk/integration-tests/s3/tests/reconnects.rs +++ b/aws/sdk/integration-tests/s3/tests/reconnects.rs @@ -6,10 +6,12 @@ use aws_sdk_s3::config::retry::{ReconnectMode, RetryConfig}; use aws_sdk_s3::config::{Credentials, Region, SharedAsyncSleep}; use aws_smithy_async::rt::sleep::TokioSleep; +use aws_smithy_runtime::client::http::test_util::wire::{ReplayedEvent, WireMockServer}; +use aws_smithy_runtime::{ev, match_events}; #[tokio::test] async fn test_disable_reconnect_on_503() { - let mock = WireLevelTestConnection::spinup(vec![ + let mock = WireMockServer::start(vec![ ReplayedEvent::status(503), ReplayedEvent::status(503), ReplayedEvent::with_body("here-is-your-object"), @@ -49,7 +51,7 @@ async fn test_disable_reconnect_on_503() { #[tokio::test] async fn test_enabling_reconnect_on_503() { - let mock = WireLevelTestConnection::spinup(vec![ + let mock = WireMockServer::start(vec![ ReplayedEvent::status(503), ReplayedEvent::status(503), ReplayedEvent::with_body("here-is-your-object"), diff --git a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs index 57a087b316..4bff784ade 100644 --- a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs +++ b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_smithy_runtime::client::http::test_util::dvr::{MediaType, ReplayingClient}; + #[tokio::test] async fn do_endpoint_discovery() { use aws_credential_types::provider::SharedCredentialsProvider; @@ -17,7 +19,7 @@ async fn do_endpoint_discovery() { let _logs = aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/traffic.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/traffic.json").unwrap(); //let conn = aws_smithy_client::dvr::RecordingConnection::new(conn); let start = UNIX_EPOCH + Duration::from_secs(1234567890); let (ts, sleep, mut gate) = controlled_time_and_sleep(start); @@ -64,15 +66,16 @@ async fn do_endpoint_discovery() { .unwrap(); // if you want to update this test: // conn.dump_to_file("tests/traffic.json").unwrap(); - conn.validate_body_and_headers( - Some(&[ - "x-amz-security-token", - "x-amz-date", - "content-type", - "x-amz-target", - ]), - MediaType::Json, - ) - .await - .unwrap(); + http_client + .validate_body_and_headers( + Some(&[ + "x-amz-security-token", + "x-amz-date", + "content-type", + "x-amz-target", + ]), + MediaType::Json, + ) + .await + .unwrap(); } diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 8072c9684a..dcf796b308 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -12,11 +12,14 @@ repository = "https://github.com/awslabs/smithy-rs" [features] client = ["aws-smithy-runtime-api/client"] http-auth = ["aws-smithy-runtime-api/http-auth"] -test-util = ["aws-smithy-runtime-api/test-util", "dep:aws-smithy-protocol-test", "dep:tracing-subscriber", "dep:serde", "dep:serde_json"] connector-hyper-0-14-x = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] tls-rustls = ["dep:hyper-rustls", "dep:rustls"] rt-tokio = ["tokio/rt"] +# Features for testing +test-util = ["aws-smithy-runtime-api/test-util", "dep:aws-smithy-protocol-test", "dep:tracing-subscriber", "dep:serde", "dep:serde_json"] +wire-mock = ["test-util", "connector-hyper-0-14-x", "hyper?/server"] + [dependencies] aws-smithy-async = { path = "../aws-smithy-async" } aws-smithy-http = { path = "../aws-smithy-http" } diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 03550d0ada..132a388ea2 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -538,7 +538,7 @@ impl HyperClientBuilder { }) } - #[cfg(test)] + #[cfg(all(test, feature = "test-util"))] fn build_with_fn(self, tcp_connector_fn: F) -> SharedHttpClient where F: Fn() -> C + Send + Sync + 'static, diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs index e615f84911..67ab903137 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs @@ -42,3 +42,6 @@ pub use never::NeverClient; #[cfg(feature = "connector-hyper-0-14-x")] pub use never::NeverTcpConnector; + +#[cfg(all(feature = "connector-hyper-0-14-x", feature = "wire-mock"))] +pub mod wire; diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs new file mode 100644 index 0000000000..c59e7de006 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs @@ -0,0 +1,356 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Utilities for mocking at the socket level +//! +//! Other tools in this module actually operate at the `http::Request` / `http::Response` level. This +//! is useful, but it shortcuts the HTTP implementation (e.g. Hyper). [`wire_mock::WireLevelTestConnection`] binds +//! to an actual socket on the host +//! +//! # Examples +//! ```no_run +//! use aws_smithy_runtime_api::client::http::HttpConnectorSettings; +//! use aws_smithy_runtime::client::http::test_util::wire::{check_matches, ReplayedEvent, WireMockServer}; +//! use aws_smithy_runtime::{match_events, ev}; +//! # async fn example() { +//! +//! // This connection binds to a local address +//! let mock = WireMockServer::start(vec![ +//! ReplayedEvent::status(503), +//! ReplayedEvent::status(200) +//! ]).await; +//! +//! # /* +//! // Create a client using the wire mock +//! let config = my_generated_client::Config::builder() +//! .http_client(mock.http_client()) +//! .build(); +//! let client = Client::from_conf(config); +//! +//! // ... do something with +//! # */ +//! +//! // assert that you got the events you expected +//! match_events!(ev!(dns), ev!(connect), ev!(http(200)))(&mock.events()); +//! # } +//! ``` + +#![allow(missing_docs)] + +use crate::client::http::hyper_014::HyperClientBuilder; +use aws_smithy_async::future::never::Never; +use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::shared::IntoShared; +use bytes::Bytes; +use http::{Request, Response}; +use hyper::client::connect::dns::Name; +use hyper::server::conn::AddrStream; +use hyper::service::{make_service_fn, service_fn, Service}; +use hyper::{Body, Server}; +use std::collections::HashSet; +use std::convert::Infallible; +use std::error::Error; +use std::future::Future; +use std::iter::Once; +use std::net::{SocketAddr, TcpListener}; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use tokio::spawn; +use tokio::sync::oneshot; + +/// An event recorded by [`WireLevelTestConnection`] +#[non_exhaustive] +#[derive(Debug, Clone)] +pub enum RecordedEvent { + DnsLookup(String), + NewConnection, + Response(ReplayedEvent), +} + +type Matcher = ( + Box Result<(), Box>>, + &'static str, +); + +/// This method should only be used by the macro +#[doc(hidden)] +pub fn check_matches(events: &[RecordedEvent], matchers: &[Matcher]) { + let mut events_iter = events.iter(); + let mut matcher_iter = matchers.iter(); + let mut idx = -1; + loop { + idx += 1; + let bail = |err: Box| panic!("failed on event {}:\n {}", idx, err); + match (events_iter.next(), matcher_iter.next()) { + (Some(event), Some((matcher, _msg))) => matcher(event).unwrap_or_else(bail), + (None, None) => return, + (Some(event), None) => { + bail(format!("got {:?} but no more events were expected", event).into()) + } + (None, Some((_expect, msg))) => { + bail(format!("expected {:?} but no more events were expected", msg).into()) + } + } + } +} + +#[macro_export] +macro_rules! matcher { + ($expect:tt) => { + ( + Box::new( + |event: &$crate::client::http::test_util::wire::RecordedEvent| { + if !matches!(event, $expect) { + return Err(format!( + "expected `{}` but got {:?}", + stringify!($expect), + event + ) + .into()); + } + Ok(()) + }, + ), + stringify!($expect), + ) + }; +} + +/// Helper macro to generate a series of test expectations +#[macro_export] +macro_rules! match_events { + ($( $expect:pat),*) => { + |events| { + $crate::client::http::test_util::wire::check_matches(events, &[$( $crate::matcher!($expect) ),*]); + } + }; + } + +/// Helper to generate match expressions for events +#[macro_export] +macro_rules! ev { + (http($status:expr)) => { + $crate::client::http::test_util::wire::RecordedEvent::Response( + $crate::client::http::test_util::wire::ReplayedEvent::HttpResponse { + status: $status, + .. + }, + ) + }; + (dns) => { + $crate::client::http::test_util::wire::RecordedEvent::DnsLookup(_) + }; + (connect) => { + $crate::client::http::test_util::wire::RecordedEvent::NewConnection + }; + (timeout) => { + $crate::client::http::test_util::wire::RecordedEvent::Response( + $crate::client::http::test_util::wire::ReplayedEvent::Timeout, + ) + }; +} + +pub use {ev, match_events, matcher}; + +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ReplayedEvent { + Timeout, + HttpResponse { status: u16, body: Bytes }, +} + +impl ReplayedEvent { + pub fn ok() -> Self { + Self::HttpResponse { + status: 200, + body: Bytes::new(), + } + } + + pub fn with_body(body: &str) -> Self { + Self::HttpResponse { + status: 200, + body: Bytes::copy_from_slice(body.as_ref()), + } + } + + pub fn status(status: u16) -> Self { + Self::HttpResponse { + status, + body: Bytes::new(), + } + } +} + +/// Test server that binds to 127.0.0.1:0 +/// +/// See the [module docs](crate::client::http::test_util::wire) for a usage example. +/// +/// Usage: +/// - Call [`WireMockServer::start`] to start the server +/// - Use [`WireMockServer::http_client`] or [`dns_resolver`](WireMockServer::dns_resolver) to configure your client. +/// - Make requests to [`endpoint_url`](WireMockServer::endpoint_url). +/// - Once the test is complete, retrieve a list of events from [`WireMockServer::events`] +#[derive(Debug)] +pub struct WireMockServer { + event_log: Arc>>, + bind_addr: SocketAddr, + // when the sender is dropped, that stops the server + shutdown_hook: oneshot::Sender<()>, +} + +impl WireMockServer { + /// Start a wire mock server with the given events to replay. + pub async fn start(mut response_events: Vec) -> Self { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let (tx, rx) = oneshot::channel(); + let listener_addr = listener.local_addr().unwrap(); + response_events.reverse(); + let response_events = Arc::new(Mutex::new(response_events)); + let handler_events = response_events; + let wire_events = Arc::new(Mutex::new(vec![])); + let wire_log_for_service = wire_events.clone(); + let poisoned_conns: Arc>> = Default::default(); + let make_service = make_service_fn(move |connection: &AddrStream| { + let poisoned_conns = poisoned_conns.clone(); + let events = handler_events.clone(); + let wire_log = wire_log_for_service.clone(); + let remote_addr = connection.remote_addr(); + tracing::info!("established connection: {:?}", connection); + wire_log.lock().unwrap().push(RecordedEvent::NewConnection); + async move { + Ok::<_, Infallible>(service_fn(move |_: Request| { + if poisoned_conns.lock().unwrap().contains(&remote_addr) { + tracing::error!("poisoned connection {:?} was reused!", &remote_addr); + panic!("poisoned connection was reused!"); + } + let next_event = events.clone().lock().unwrap().pop(); + let wire_log = wire_log.clone(); + let poisoned_conns = poisoned_conns.clone(); + async move { + let next_event = next_event + .unwrap_or_else(|| panic!("no more events! Log: {:?}", wire_log)); + wire_log + .lock() + .unwrap() + .push(RecordedEvent::Response(next_event.clone())); + if next_event == ReplayedEvent::Timeout { + tracing::info!("{} is poisoned", remote_addr); + poisoned_conns.lock().unwrap().insert(remote_addr); + } + tracing::debug!("replying with {:?}", next_event); + let event = generate_response_event(next_event).await; + dbg!(event) + } + })) + } + }); + let server = Server::from_tcp(listener) + .unwrap() + .serve(make_service) + .with_graceful_shutdown(async { + rx.await.ok(); + tracing::info!("server shutdown!"); + }); + spawn(server); + Self { + event_log: wire_events, + bind_addr: listener_addr, + shutdown_hook: tx, + } + } + + /// Retrieve the events recorded by this connection + pub fn events(&self) -> Vec { + self.event_log.lock().unwrap().clone() + } + + fn bind_addr(&self) -> SocketAddr { + self.bind_addr + } + + pub fn dns_resolver(&self) -> LoggingDnsResolver { + let event_log = self.event_log.clone(); + let bind_addr = self.bind_addr; + LoggingDnsResolver { + log: event_log, + socket_addr: bind_addr, + } + } + + /// Prebuilt [`HttpClient`] with correctly wired DNS resolver + /// + /// **Note**: This must be used in tandem with [`Self::dns_resolver`] + pub fn http_client(&self) -> SharedHttpClient { + HyperClientBuilder::new() + .build(hyper::client::HttpConnector::new_with_resolver( + self.dns_resolver(), + )) + .into_shared() + } + + /// Endpoint to use when connecting + /// + /// This works in tandem with the [`Self::dns_resolver`] to bind to the correct local IP Address + pub fn endpoint_url(&self) -> String { + format!( + "http://this-url-is-converted-to-localhost.com:{}", + self.bind_addr().port() + ) + } + + /// Shuts down the mock server. + pub fn shutdown(self) { + let _ = self.shutdown_hook.send(()); + } +} + +async fn generate_response_event(event: ReplayedEvent) -> Result, Infallible> { + let resp = match event { + ReplayedEvent::HttpResponse { status, body } => http::Response::builder() + .status(status) + .body(hyper::Body::from(body)) + .unwrap(), + ReplayedEvent::Timeout => { + Never::new().await; + unreachable!() + } + }; + Ok::<_, Infallible>(resp) +} + +type BoxFuture = Pin> + Send>>; + +/// DNS resolver that keeps a log of all lookups +/// +/// Regardless of what hostname is requested, it will always return the same socket address. +#[derive(Clone, Debug)] +pub struct LoggingDnsResolver { + log: Arc>>, + socket_addr: SocketAddr, +} + +impl Service for LoggingDnsResolver { + type Response = Once; + type Error = Infallible; + type Future = BoxFuture; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Name) -> Self::Future { + let socket_addr = self.socket_addr; + let log = self.log.clone(); + Box::pin(async move { + println!("looking up {:?}, replying with {:?}", req, socket_addr); + log.lock() + .unwrap() + .push(RecordedEvent::DnsLookup(req.to_string())); + Ok(std::iter::once(socket_addr)) + }) + } +} From 3029d92f6362e91b5d396931616c846acde40e54 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 2 Oct 2023 16:51:33 -0700 Subject: [PATCH 04/23] Fix doc issues --- .../aws-smithy-runtime/src/client/http/test_util/wire.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs index c59e7de006..2d31dc0104 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs @@ -6,8 +6,8 @@ //! Utilities for mocking at the socket level //! //! Other tools in this module actually operate at the `http::Request` / `http::Response` level. This -//! is useful, but it shortcuts the HTTP implementation (e.g. Hyper). [`wire_mock::WireLevelTestConnection`] binds -//! to an actual socket on the host +//! is useful, but it shortcuts the HTTP implementation (e.g. Hyper). [`WireMockServer`] binds +//! to an actual socket on the host. //! //! # Examples //! ```no_run @@ -61,7 +61,7 @@ use std::task::{Context, Poll}; use tokio::spawn; use tokio::sync::oneshot; -/// An event recorded by [`WireLevelTestConnection`] +/// An event recorded by [`WireMockServer`]. #[non_exhaustive] #[derive(Debug, Clone)] pub enum RecordedEvent { @@ -281,7 +281,7 @@ impl WireMockServer { } } - /// Prebuilt [`HttpClient`] with correctly wired DNS resolver + /// Prebuilt [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) with correctly wired DNS resolver. /// /// **Note**: This must be used in tandem with [`Self::dns_resolver`] pub fn http_client(&self) -> SharedHttpClient { From 0524199feee3926edf887fd29109f1817dcf38c7 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 2 Oct 2023 17:30:05 -0700 Subject: [PATCH 05/23] Fix time assertion in DynamoDB test --- .../integration-tests/dynamodb/tests/movies.rs | 18 ++++++------------ .../src/client/runtime_plugin.rs | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs index 85edad45bc..4d6cb8babe 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/movies.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -4,6 +4,7 @@ */ use aws_sdk_dynamodb as dynamodb; +use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; @@ -129,17 +130,6 @@ async fn wait_for_ready_table(client: &Client, table_name: &str) { } } -/// Validate that time has passed with a 5ms tolerance -/// -/// This is to account for some non-determinism in the Tokio timer -fn assert_time_passed(initial: Instant, passed: Duration) { - let now = tokio::time::Instant::now(); - let delta = now - initial; - if (delta.as_millis() as i128 - passed.as_millis() as i128).abs() > 5 { - assert_eq!(delta, passed) - } -} - /// A partial reimplementation of https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/GettingStarted.Ruby.html /// in Rust /// @@ -165,7 +155,11 @@ async fn movies_it() { let waiter_start = tokio::time::Instant::now(); wait_for_ready_table(&client, table_name).await; - assert_time_passed(waiter_start, Duration::from_secs(4)); + assert_elapsed!( + waiter_start, + Duration::from_secs(4), + Duration::from_millis(10) + ); // data.json contains 2 movies from 2013 let data = match serde_json::from_str(include_str!("data.json")).expect("should be valid JSON") { diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs index d0e529b67f..2412080ab9 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs @@ -265,7 +265,7 @@ impl RuntimePlugins { } } -#[cfg(test)] +#[cfg(all(test, feature = "test-util"))] mod tests { use super::{RuntimePlugin, RuntimePlugins}; use crate::client::http::{ From 4ee26bb5e6d784b00637d98b8b86441d3127e447 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Mon, 2 Oct 2023 17:54:47 -0700 Subject: [PATCH 06/23] More CI fixes --- aws/rust-runtime/aws-credential-types/external-types.toml | 1 + aws/sdk/integration-tests/dynamodb/tests/movies.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/rust-runtime/aws-credential-types/external-types.toml b/aws/rust-runtime/aws-credential-types/external-types.toml index e65c743b10..ec66391fc1 100644 --- a/aws/rust-runtime/aws-credential-types/external-types.toml +++ b/aws/rust-runtime/aws-credential-types/external-types.toml @@ -1,5 +1,6 @@ allowed_external_types = [ "aws_smithy_async::rt::sleep::SharedAsyncSleep", + "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", "aws_smithy_types::config_bag::storable::Storer", diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs index 4d6cb8babe..ce1f9d357f 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/movies.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -20,7 +20,6 @@ use http::Uri; use serde_json::Value; use std::collections::HashMap; use std::time::Duration; -use tokio::time::Instant; async fn create_table(client: &Client, table_name: &str) { client From 70e5cdafa51911f285674449f5f3a32a07f97bbd Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 15:11:19 -0700 Subject: [PATCH 07/23] Improve doc comment links --- .../aws-smithy-runtime-api/src/client/http.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs index 4a2c363e02..66d7ddc3f4 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs @@ -12,21 +12,20 @@ //! request. //! //! This is slightly different from what a connector is in other libraries such as -//! [`hyper`](https://crates.io/crates/hyper). In hyper 0.x, the connector is a -//! [`tower`](https://crates.io/crates/tower) `Service` that takes a `Uri` and returns +//! [`hyper`]. In hyper 0.x, the connector is a [`tower`] `Service` that takes a `Uri` and returns //! a future with something that implements `AsyncRead + AsyncWrite`. //! //! The [`HttpConnector`] is designed to be a layer on top of //! whole HTTP libraries, such as hyper, which allows Smithy clients to be agnostic to the underlying HTTP //! transport layer. This also makes it easy to write tests with a fake HTTP connector, and several -//! such test connector implementations are available in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) +//! such test connector implementations are available in [`aws-smithy-runtime`] //! with the `test-util` feature enabled. //! //! # Responsibilities of a connector //! //! A connector primarily makes HTTP requests, but is also the place where connect and read timeouts are -//! implemented. The `HyperConnector` in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) -//! is an example where timeouts are implemented as part of the connector. +//! implemented. The `HyperConnector` in [`aws-smithy-runtime`] is an example where timeouts are implemented +//! as part of the connector. //! //! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction. //! The Smithy clients have no knowledge of such concepts. @@ -46,6 +45,10 @@ //! //! Some of these aren't implemented yet, but they will appear in the [`HttpConnectorSettings`] struct //! once they are. +//! +//! [`hyper`]: https://crates.io/crates/hyper +//! [`tower`]: https://crates.io/crates/tower +//! [`aws-smithy-runtime`]: https://crates.io/crates/aws-smithy-runtime use crate::client::orchestrator::{HttpRequest, HttpResponse}; use crate::client::runtime_components::RuntimeComponents; @@ -114,7 +117,7 @@ impl StdFuture for HttpConnectorFuture { /// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper), /// and any associated HTTPS implementation alongside it to service requests. /// -/// However, it can also be useful to create fake connectors implementing this trait +/// However, it can also be useful to create fake/mock connectors implementing this trait /// for testing. pub trait HttpConnector: Send + Sync + fmt::Debug { /// Asynchronously converts a request into a response. From e257ac9c91cdb9b5a13c14e395c3e23e11b0a1c0 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 15:36:13 -0700 Subject: [PATCH 08/23] Simplify arguments to `ImdsCommonRuntimePlugin` --- aws/rust-runtime/aws-config/src/imds/client.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index 245c020118..3afb1ebe11 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -13,8 +13,6 @@ use crate::provider_config::ProviderConfig; use crate::PKG_VERSION; use aws_http::user_agent::{ApiMetadata, AwsUserAgent}; use aws_runtime::user_agent::UserAgentInterceptor; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::SharedTimeSource; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_http::result::SdkError; @@ -34,7 +32,6 @@ use aws_smithy_types::endpoint::Endpoint; use aws_smithy_types::retry::{ErrorKind, RetryConfig}; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::os_shim_internal::Env; -use aws_types::sdk_config::SharedHttpClient; use http::Uri; use std::borrow::Cow; use std::error::Error as _; @@ -235,12 +232,10 @@ struct ImdsCommonRuntimePlugin { impl ImdsCommonRuntimePlugin { fn new( - http_client: Option, + config: &ProviderConfig, endpoint_resolver: ImdsEndpointResolver, retry_config: &RetryConfig, timeout_config: TimeoutConfig, - time_source: SharedTimeSource, - sleep_impl: Option, ) -> Self { let mut layer = Layer::new("ImdsCommonRuntimePlugin"); layer.store_put(AuthSchemeOptionResolverParams::new(())); @@ -252,15 +247,15 @@ impl ImdsCommonRuntimePlugin { Self { config: layer.freeze(), components: RuntimeComponentsBuilder::new("ImdsCommonRuntimePlugin") - .with_http_client(http_client) + .with_http_client(config.http_client()) .with_endpoint_resolver(Some(endpoint_resolver)) .with_interceptor(UserAgentInterceptor::new()) .with_retry_classifiers(Some( RetryClassifiers::new().with_classifier(ImdsResponseRetryClassifier), )) .with_retry_strategy(Some(StandardRetryStrategy::new(retry_config))) - .with_time_source(Some(time_source)) - .with_sleep_impl(sleep_impl), + .with_time_source(Some(config.time_source())) + .with_sleep_impl(config.sleep_impl()), } } } @@ -424,12 +419,10 @@ impl Builder { let retry_config = RetryConfig::standard() .with_max_attempts(self.max_attempts.unwrap_or(DEFAULT_ATTEMPTS)); let common_plugin = SharedRuntimePlugin::new(ImdsCommonRuntimePlugin::new( - config.http_client(), + &config, endpoint_resolver, &retry_config, timeout_config, - config.time_source(), - config.sleep_impl(), )); let operation = Operation::builder() .service_name("imds") From 9981efd6e35fe609a1139552377eccb812bef528 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 16:43:36 -0700 Subject: [PATCH 09/23] Rename `EventClient` --- aws/rust-runtime/aws-config/src/ecs.rs | 53 ++- .../src/http_credential_provider.rs | 78 ++-- .../aws-config/src/imds/client.rs | 60 +-- .../aws-config/src/imds/credentials.rs | 79 ++-- .../aws-config/src/imds/region.rs | 46 +-- .../aws-config/src/sts/assume_role.rs | 10 +- .../rustsdk/EndpointBuiltInsDecoratorTest.kt | 15 +- .../dynamodb/tests/movies.rs | 368 +++++++++--------- .../dynamodb/tests/paginators.rs | 111 +++--- .../retries-with-client-rate-limiting.rs | 35 +- .../integration-tests/ec2/tests/paginators.rs | 49 +-- .../kms/tests/integration.rs | 19 +- .../qldbsession/tests/integration.rs | 7 +- .../integration-tests/s3/tests/checksums.rs | 13 +- .../s3/tests/ignore-invalid-xml-body-root.rs | 9 +- .../integration-tests/s3/tests/signing-it.rs | 7 +- .../s3control/tests/signing-it.rs | 7 +- .../customizations/HttpAuthDecoratorTest.kt | 45 +-- .../src/client/http/test_util.rs | 33 +- .../http/test_util/{event.rs => replay.rs} | 135 ++++--- .../src/client/orchestrator/operation.rs | 49 ++- 21 files changed, 597 insertions(+), 631 deletions(-) rename rust-runtime/aws-smithy-runtime/src/client/http/test_util/{event.rs => replay.rs} (65%) diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index 6b4d6acf4b..45ce7c342d 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -444,7 +444,7 @@ mod test { use aws_smithy_async::future::never::Never; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime_api::client::dns::DnsFuture; use aws_smithy_runtime_api::shared::IntoShared; use aws_types::os_shim_internal::Env; @@ -637,13 +637,10 @@ mod test { ("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials"), ("AWS_CONTAINER_AUTHORIZATION_TOKEN", "Basic password"), ]); - let http_client = EventClient::new( - vec![ConnectionEvent::new( - creds_request("http://169.254.170.2/credentials", Some("Basic password")), - ok_creds_response(), - )], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + creds_request("http://169.254.170.2/credentials", Some("Basic password")), + ok_creds_response(), + )]); let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() @@ -656,22 +653,19 @@ mod test { #[tokio::test] async fn retry_5xx() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - creds_request("http://169.254.170.2/credentials", None), - http::Response::builder() - .status(500) - .body(SdkBody::empty()) - .unwrap(), - ), - ConnectionEvent::new( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - ), - ], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + creds_request("http://169.254.170.2/credentials", None), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), + ), + ReplayEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + ), + ]); tokio::time::pause(); let provider = provider(env, http_client.clone()); let creds = provider @@ -684,13 +678,10 @@ mod test { #[tokio::test] async fn load_valid_creds_no_auth() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let http_client = EventClient::new( - vec![ConnectionEvent::new( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - )], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + )]); let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() 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 ea5fce0603..64227bdf58 100644 --- a/aws/rust-runtime/aws-config/src/http_credential_provider.rs +++ b/aws/rust-runtime/aws-config/src/http_credential_provider.rs @@ -217,13 +217,14 @@ impl ClassifyRetry for HttpCredentialRetryClassifier { mod test { use super::*; use aws_credential_types::provider::error::CredentialsError; - use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::{Request, Response, Uri}; use std::time::SystemTime; - async fn provide_creds(http_client: EventClient) -> Result { + async fn provide_creds( + http_client: StaticReplayClient, + ) -> Result { let provider_config = ProviderConfig::default().with_http_client(http_client.clone()); let provider = HttpCredentialProvider::builder() .configure(&provider_config) @@ -231,8 +232,8 @@ mod test { provider.credentials(None).await } - fn successful_req_resp() -> ConnectionEvent { - ConnectionEvent::new( + fn successful_req_resp() -> ReplayEvent { + ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -253,7 +254,7 @@ mod test { #[tokio::test] async fn successful_response() { - let http_client = EventClient::new(vec![successful_req_resp()], TokioSleep::new()); + let http_client = StaticReplayClient::new(vec![successful_req_resp()]); let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); assert_eq!("/7PC5om....", creds.secret_access_key()); @@ -267,22 +268,19 @@ mod test { #[tokio::test] async fn retry_nonparseable_response() { - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(200) - .body(SdkBody::from(r#"not json"#)) - .unwrap(), - ), - successful_req_resp(), - ], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(200) + .body(SdkBody::from(r#"not json"#)) + .unwrap(), + ), + successful_req_resp(), + ]); let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); http_client.assert_requests_match(&[]); @@ -290,22 +288,19 @@ mod test { #[tokio::test] async fn retry_error_code() { - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(500) - .body(SdkBody::from(r#"it broke"#)) - .unwrap(), - ), - successful_req_resp(), - ], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(500) + .body(SdkBody::from(r#"it broke"#)) + .unwrap(), + ), + successful_req_resp(), + ]); let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); http_client.assert_requests_match(&[]); @@ -313,8 +308,7 @@ mod test { #[tokio::test] async fn explicit_error_not_retriable() { - let http_client = EventClient::new( - vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -325,9 +319,7 @@ mod test { r#"{ "Code": "Error", "Message": "There was a problem, it was your fault" }"#, )) .unwrap(), - )], - TokioSleep::new(), - ); + )]); let err = provide_creds(http_client.clone()) .await .expect_err("it should fail"); diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index 3afb1ebe11..bb0eb06c95 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -586,7 +586,7 @@ pub(crate) mod test { use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_runtime::client::http::test_util::{ - capture_request, ConnectionEvent, EventClient, + capture_request, ReplayEvent, StaticReplayClient, }; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_runtime_api::client::interceptors::context::{ @@ -658,7 +658,7 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn make_client(http_client: &EventClient) -> super::Client { + pub(crate) fn make_client(http_client: &StaticReplayClient) -> super::Client { tokio::time::pause(); super::Client::builder() .configure( @@ -669,8 +669,8 @@ pub(crate) mod test { .build() } - fn event_client(events: Vec) -> (Client, EventClient) { - let http_client = EventClient::new(events, TokioSleep::new()); + fn event_client(events: Vec) -> (Client, StaticReplayClient) { + let http_client = StaticReplayClient::new(events); let client = make_client(&http_client); (client, http_client) } @@ -678,15 +678,15 @@ pub(crate) mod test { #[tokio::test] async fn client_caches_token() { let (client, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output"#), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata2", TOKEN_A), imds_response("output2"), ), @@ -703,19 +703,19 @@ pub(crate) mod test { #[tokio::test] async fn token_can_expire() { let (_, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), - ConnectionEvent::new( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output2"#), ), @@ -745,26 +745,26 @@ pub(crate) mod test { #[tokio::test] async fn token_refresh_buffer() { let (_, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), // t = 0 - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), // t = 400 (no refresh) - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output2"#), ), // t = 550 (within buffer) - ConnectionEvent::new( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output3"#), ), @@ -798,18 +798,18 @@ pub(crate) mod test { #[traced_test] async fn retry_500() { let (client, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(500) .body(SdkBody::empty()) .unwrap(), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), @@ -835,18 +835,18 @@ pub(crate) mod test { #[traced_test] async fn retry_token_failure() { let (client, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), http::Response::builder() .status(500) .body(SdkBody::empty()) .unwrap(), ), - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), @@ -867,22 +867,22 @@ pub(crate) mod test { #[traced_test] async fn retry_metadata_401() { let (client, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(0, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(401) .body(SdkBody::empty()) .unwrap(), ), - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_B), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_B), imds_response("ok"), ), @@ -902,7 +902,7 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn no_403_retry() { - let (client, http_client) = event_client(vec![ConnectionEvent::new( + let (client, http_client) = event_client(vec![ReplayEvent::new( token_request("http://169.254.169.254", 21600), http::Response::builder() .status(403) @@ -934,7 +934,7 @@ pub(crate) mod test { // since tokens are sent as headers, the tokens need to be valid header values #[tokio::test] async fn invalid_token() { - let (client, http_client) = event_client(vec![ConnectionEvent::new( + let (client, http_client) = event_client(vec![ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, "invalid\nheader\nvalue\0"), )]); @@ -946,11 +946,11 @@ pub(crate) mod test { #[tokio::test] async fn non_utf8_response() { let (client, http_client) = event_client(vec![ - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A).map(SdkBody::from), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(200) diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 2dd5693b1b..b84ae7b066 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -288,10 +288,9 @@ mod test { }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; - use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; @@ -299,28 +298,28 @@ mod test { #[tokio::test] async fn profile_is_not_cached() { - let http_client = EventClient::new(vec![ - ConnectionEvent::new( - token_request("http://169.254.169.254", 21600), - token_response(21600, TOKEN_A), - ), - ConnectionEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"profile-name"#), - ), - ConnectionEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), - imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), - ), - ConnectionEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"different-profile"#), - ), - ConnectionEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A), - imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST2\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), - ), - ], TokioSleep::new()); + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"profile-name"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"different-profile"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST2\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]); let client = ImdsCredentialsProvider::builder() .imds_client(make_client(&http_client)) .build(); @@ -334,20 +333,20 @@ mod test { #[tokio::test] #[traced_test] async fn credentials_not_stale_should_be_used_as_they_are() { - let http_client = EventClient::new(vec![ - ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ], TokioSleep::new()); + ]); // set to 2021-09-21T04:16:50Z that makes returned credentials' expiry (2021-09-21T04:16:53Z) // not stale @@ -379,20 +378,20 @@ mod test { #[tokio::test] #[traced_test] async fn expired_credentials_should_be_extended() { - let http_client = EventClient::new(vec![ - ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ], TokioSleep::new()); + ]); // set to 2021-09-21T17:41:25Z that renders fetched credentials already expired (2021-09-21T04:16:53Z) let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632246085); @@ -487,28 +486,28 @@ mod test { #[tokio::test] async fn fallback_credentials_should_be_used_when_imds_returns_500_during_credentials_refresh() { - let http_client = EventClient::new(vec![ + let http_client = StaticReplayClient::new(vec![ // The next three request/response pairs will correspond to the first call to `provide_credentials`. // During the call, it populates last_retrieved_credentials. - ConnectionEvent::new( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), // The following request/response pair corresponds to the second call to `provide_credentials`. // During the call, IMDS returns response code 500. - ConnectionEvent::new( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), http::Response::builder().status(500).body(SdkBody::empty()).unwrap(), ), - ], TokioSleep::new()); + ]); let provider = ImdsCredentialsProvider::builder() .imds_client(make_client(&http_client)) .build(); diff --git a/aws/rust-runtime/aws-config/src/imds/region.rs b/aws/rust-runtime/aws-config/src/imds/region.rs index 4f48448e99..98bb196e0d 100644 --- a/aws/rust-runtime/aws-config/src/imds/region.rs +++ b/aws/rust-runtime/aws-config/src/imds/region.rs @@ -113,27 +113,24 @@ mod test { use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use tracing_test::traced_test; #[tokio::test] async fn load_region() { - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - token_request("http://169.254.169.254", 21600), - token_response(21600, "token"), - ), - ConnectionEvent::new( - imds_request( - "http://169.254.169.254/latest/meta-data/placement/region", - "token", - ), - imds_response("eu-west-1"), + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, "token"), + ), + ReplayEvent::new( + imds_request( + "http://169.254.169.254/latest/meta-data/placement/region", + "token", ), - ], - TokioSleep::new(), - ); + imds_response("eu-west-1"), + ), + ]); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() @@ -150,16 +147,13 @@ mod test { #[traced_test] #[tokio::test] async fn no_region_imds_disabled() { - let http_client = EventClient::new( - vec![ConnectionEvent::new( - token_request("http://169.254.169.254", 21600), - http::Response::builder() - .status(403) - .body(SdkBody::empty()) - .unwrap(), - )], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + http::Response::builder() + .status(403) + .body(SdkBody::empty()) + .unwrap(), + )]); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() 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 05d9497060..2162e6daae 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -351,7 +351,7 @@ mod test { use aws_smithy_async::time::StaticTimeSource; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime::client::http::test_util::{ - capture_request, ConnectionEvent, EventClient, + capture_request, ReplayEvent, StaticReplayClient, }; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_types::os_shim_internal::Env; @@ -462,16 +462,16 @@ mod test { #[tokio::test] async fn provider_does_not_cache_credentials_by_default() { - let http_client = EventClient::new(vec![ - ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n" )).unwrap()), - ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), + ReplayEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n TESTSECRET\n tokencorrect\n 2009-02-13T23:33:30Z\n \n \n \n c2e971c2-702d-4124-9b1f-1670febbea18\n \n\n" )).unwrap()), - ], TokioSleep::new()); + ]); let (testing_time_source, sleep) = instant_time_and_sleep( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), // 1234567890 since UNIX_EPOCH is 2009-02-13T23:31:30Z diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt index 3fe721b239..68bfab92fe 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt @@ -87,15 +87,14 @@ class EndpointBuiltInsDecoratorTest { ##[#{tokio}::test] async fn endpoint_url_built_in_works() { - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .uri("https://RIGHT/SomeOperation") .body(#{SdkBody}::empty()) .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap() )], - #{TokioSleep}::new(), ); let config = Config::builder() .http_client(http_client.clone()) @@ -108,13 +107,11 @@ class EndpointBuiltInsDecoratorTest { } """, "tokio" to CargoDependency.Tokio.toDevDependency().withFeature("rt").withFeature("macros").toType(), - "EventClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() - .resolve("client::http::test_util::EventClient"), - "ConnectionEvent" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() - .resolve("client::http::test_util::ConnectionEvent"), + "StaticReplayClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::StaticReplayClient"), + "ReplayEvent" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::ReplayEvent"), "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), - "TokioSleep" to RuntimeType.smithyAsync(codegenContext.runtimeConfig) - .resolve("rt::sleep::TokioSleep"), ) } } diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs index ce1f9d357f..981562c05a 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/movies.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -5,9 +5,8 @@ use aws_sdk_dynamodb as dynamodb; use aws_smithy_async::assert_elapsed; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use dynamodb::config::{Credentials, Region}; use dynamodb::operation::query::QueryOutput; use dynamodb::types::{ @@ -194,186 +193,187 @@ async fn movies_it() { /// Test connection for the movies IT /// headers are signed with actual creds, at some point we could replace them with verifiable test /// credentials, but there are plenty of other tests that target signing -fn movies_it_test_connection() -> EventClient { - EventClient::new(vec![ConnectionEvent::new( - http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.CreateTable") - .header("content-length", "313") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=4a832eba37651836b524b587986be607607b077ad133c57b4bf7300d2e02f476") - .header("x-amz-date", "20210308T155118Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"AttributeDefinitions":[{"AttributeName":"year","AttributeType":"N"},{"AttributeName":"title","AttributeType":"S"}],"TableName":"Movies-5","KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "572") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "3715137447") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=01b0129a2a4fb3af14559fde8163d59de9c43907152a12479002b3a7c75fa0df") - .header("x-amz-date", "20210308T155119Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=7f3a743bb460f26296640ae775d282f0153eda750855ec00ace1815becfd2de5") - .header("x-amz-date", "20210308T155120Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")).body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:20 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=46a148c560139bc0da171bd915ea8c0b96a7012629f5db7b6bf70fcd1a66fd24") - .header("x-amz-date", "20210308T155121Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:21 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=15bb7c9b2350747d62349091b3ea59d9e1800d1dca04029943329259bba85cb4") - .header("x-amz-date", "20210308T155122Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:22 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=6d0a78087bc112c68a91b4b2d457efd8c09149b85b8f998f8c4b3f9916c8a743") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "559") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "24113616") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.PutItem") - .header("content-length", "619") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=85fc7d2064a0e6d9c38d64751d39d311ad415ae4079ef21ef254b23ecf093519") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"rating":{"N":"6.2"},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"release_date":{"S":"2013-01-18T00:00:00Z"},"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"running_time_secs":{"N":"5215"},"rank":{"N":"11"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]}}},"title":{"S":"Turn It Down, Or Else!"},"year":{"N":"2013"}}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "2") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "2745614147") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.PutItem") - .header("content-length", "636") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=e4b1658c9f5129b3656381f6592a30e0061b1566263fbf27d982817ea79483f6") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"rating":{"N":"8.3"},"rank":{"N":"2"},"release_date":{"S":"2013-09-02T00:00:00Z"},"directors":{"L":[{"S":"Ron Howard"}]},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"running_time_secs":{"N":"7380"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]}}},"title":{"S":"Rush"},"year":{"N":"2013"}}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "2") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "2745614147") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.Query") - .header("content-length", "156") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=c9a0fdd0c7c3a792faddabca1fc154c8fbb54ddee7b06a8082e1c587615198b5") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2222"}}}"##)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "39") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "3413411624") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Count":0,"Items":[],"ScannedCount":0}"#)).unwrap()), - ConnectionEvent::new(http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.Query") - .header("content-length", "156") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=504d6b4de7093b20255b55057085937ec515f62f3c61da68c03bff3f0ce8a160") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2013"}}}"##)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "1231") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "624725176") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(SdkBody::from(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#)).unwrap()) - ], TokioSleep::new()) +fn movies_it_test_connection() -> StaticReplayClient { + StaticReplayClient::new(vec![ + ReplayEvent::new( + http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.CreateTable") + .header("content-length", "313") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=4a832eba37651836b524b587986be607607b077ad133c57b4bf7300d2e02f476") + .header("x-amz-date", "20210308T155118Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"AttributeDefinitions":[{"AttributeName":"year","AttributeType":"N"},{"AttributeName":"title","AttributeType":"S"}],"TableName":"Movies-5","KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "572") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3715137447") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=01b0129a2a4fb3af14559fde8163d59de9c43907152a12479002b3a7c75fa0df") + .header("x-amz-date", "20210308T155119Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=7f3a743bb460f26296640ae775d282f0153eda750855ec00ace1815becfd2de5") + .header("x-amz-date", "20210308T155120Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")).body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:20 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=46a148c560139bc0da171bd915ea8c0b96a7012629f5db7b6bf70fcd1a66fd24") + .header("x-amz-date", "20210308T155121Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:21 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=15bb7c9b2350747d62349091b3ea59d9e1800d1dca04029943329259bba85cb4") + .header("x-amz-date", "20210308T155122Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:22 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=6d0a78087bc112c68a91b4b2d457efd8c09149b85b8f998f8c4b3f9916c8a743") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "559") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "24113616") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "619") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=85fc7d2064a0e6d9c38d64751d39d311ad415ae4079ef21ef254b23ecf093519") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"rating":{"N":"6.2"},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"release_date":{"S":"2013-01-18T00:00:00Z"},"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"running_time_secs":{"N":"5215"},"rank":{"N":"11"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]}}},"title":{"S":"Turn It Down, Or Else!"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "636") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=e4b1658c9f5129b3656381f6592a30e0061b1566263fbf27d982817ea79483f6") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"rating":{"N":"8.3"},"rank":{"N":"2"},"release_date":{"S":"2013-09-02T00:00:00Z"},"directors":{"L":[{"S":"Ron Howard"}]},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"running_time_secs":{"N":"7380"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]}}},"title":{"S":"Rush"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=c9a0fdd0c7c3a792faddabca1fc154c8fbb54ddee7b06a8082e1c587615198b5") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2222"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "39") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3413411624") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Count":0,"Items":[],"ScannedCount":0}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=504d6b4de7093b20255b55057085937ec515f62f3c61da68c03bff3f0ce8a160") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2013"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "1231") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "624725176") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#)).unwrap()) + ]) } diff --git a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs index 53bbc4ebbf..2a129a7d6b 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs @@ -9,10 +9,11 @@ use std::iter::FromIterator; use aws_credential_types::Credentials; use aws_sdk_dynamodb::types::AttributeValue; use aws_sdk_dynamodb::{Client, Config}; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; -use aws_smithy_runtime::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ + capture_request, ReplayEvent, StaticReplayClient, +}; use aws_smithy_runtime_api::shared::IntoShared; use aws_types::region::Region; use aws_types::sdk_config::SharedHttpClient; @@ -59,12 +60,11 @@ fn mk_response(body: &'static str) -> http::Response { #[tokio::test(flavor = "current_thread")] async fn paginators_loop_until_completion() { - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response( - r#"{ + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response( + r#"{ "Count": 1, "Items": [{ "PostedBy": { @@ -75,14 +75,14 @@ async fn paginators_loop_until_completion() { "PostedBy": { "S": "joe@example.com" } } }"#, - ), ), - ConnectionEvent::new( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, - ), - mk_response( - r#"{ + ), + ReplayEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, + ), + mk_response( + r#"{ "Count": 1, "Items": [{ "PostedBy": { @@ -90,11 +90,9 @@ async fn paginators_loop_until_completion() { } }] }"#, - ), ), - ], - TokioSleep::new(), - ); + ), + ]); let client = Client::from_conf(stub_config(http_client.clone())); let mut paginator = client .scan() @@ -141,11 +139,10 @@ async fn paginators_loop_until_completion() { #[tokio::test] async fn paginators_handle_errors() { // LastEvaluatedKey is set but there is only one response in the test connection - let http_client = EventClient::new( - vec![ConnectionEvent::new( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response( - r#"{ + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response( + r#"{ "Count": 1, "Items": [{ "PostedBy": { @@ -156,10 +153,8 @@ async fn paginators_handle_errors() { "PostedBy": { "S": "joe@example.com" } } }"#, - ), - )], - TokioSleep::new(), - ); + ), + )]); let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() @@ -194,21 +189,18 @@ async fn paginators_stop_on_duplicate_token_by_default() { } }"#; // send the same response twice with the same pagination token - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response(response), - ), - ConnectionEvent::new( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, - ), - mk_response(response), + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response(response), + ), + ReplayEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), - ], - TokioSleep::new(), - ); + mk_response(response), + ), + ]); let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() @@ -250,27 +242,24 @@ async fn paginators_can_continue_on_duplicate_token() { } }"#; // send the same response twice with the same pagination token - let http_client = EventClient::new( - vec![ - ConnectionEvent::new( - mk_request(r#"{"TableName":"test-table","Limit":32}"#), - mk_response(response), - ), - ConnectionEvent::new( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, - ), - mk_response(response), + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + mk_request(r#"{"TableName":"test-table","Limit":32}"#), + mk_response(response), + ), + ReplayEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), - ConnectionEvent::new( - mk_request( - r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, - ), - mk_response(response), + mk_response(response), + ), + ReplayEvent::new( + mk_request( + r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), - ], - TokioSleep::new(), - ); + mk_response(response), + ), + ]); let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() 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 caf2fe2093..3e95a87c35 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 @@ -5,11 +5,10 @@ use aws_sdk_dynamodb::config::{Credentials, Region, SharedAsyncSleep}; use aws_sdk_dynamodb::{config::retry::RetryConfig, error::ProvideErrorMetadata}; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_async::time::SharedTimeSource; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime::client::retries::RetryPartition; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; use std::time::{Duration, SystemTime}; @@ -52,20 +51,20 @@ async fn test_adaptive_retries_with_no_throttling_errors() { let events = vec![ // First operation - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), // Second operation - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), // Third operation will fail, only errors - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), ]; - let http_client = EventClient::new(events, TokioSleep::new()); + let http_client = StaticReplayClient::new(events); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) @@ -112,15 +111,15 @@ async fn test_adaptive_retries_with_throttling_errors() { let events = vec![ // First operation - ConnectionEvent::new(req(), throttling_err()), - ConnectionEvent::new(req(), throttling_err()), - ConnectionEvent::new(req(), ok()), + ReplayEvent::new(req(), throttling_err()), + ReplayEvent::new(req(), throttling_err()), + ReplayEvent::new(req(), ok()), // Second operation - ConnectionEvent::new(req(), err()), - ConnectionEvent::new(req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), ]; - let http_client = EventClient::new(events, TokioSleep::new()); + let http_client = StaticReplayClient::new(events); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) diff --git a/aws/sdk/integration-tests/ec2/tests/paginators.rs b/aws/sdk/integration-tests/ec2/tests/paginators.rs index 19da3d85c1..42c1bc6c31 100644 --- a/aws/sdk/integration-tests/ec2/tests/paginators.rs +++ b/aws/sdk/integration-tests/ec2/tests/paginators.rs @@ -4,9 +4,8 @@ */ use aws_sdk_ec2::{config::Credentials, config::Region, types::InstanceType, Client, Config}; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::shared::IntoShared; @@ -30,19 +29,16 @@ async fn paginators_handle_empty_tokens() { "#; - let http_client = EventClient::new( - vec![ConnectionEvent::new( - http::Request::builder() - .uri("https://ec2.us-east-1.amazonaws.com/") - .body(request.into()) - .unwrap(), - http::Response::builder() - .status(200) - .body(SdkBody::from(response)) - .unwrap(), - )], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + http::Request::builder() + .uri("https://ec2.us-east-1.amazonaws.com/") + .body(request.into()) + .unwrap(), + http::Response::builder() + .status(200) + .body(SdkBody::from(response)) + .unwrap(), + )]); let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client @@ -69,19 +65,16 @@ async fn paginators_handle_unset_tokens() { edf3e86c-4baf-47c1-9228-9a5ea09542e8 "#; - let http_client = EventClient::new( - vec![ConnectionEvent::new( - http::Request::builder() - .uri("https://ec2.us-east-1.amazonaws.com/") - .body(request.into()) - .unwrap(), - http::Response::builder() - .status(200) - .body(SdkBody::from(response)) - .unwrap(), - )], - TokioSleep::new(), - ); + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( + http::Request::builder() + .uri("https://ec2.us-east-1.amazonaws.com/") + .body(request.into()) + .unwrap(), + http::Response::builder() + .status(200) + .body(SdkBody::from(response)) + .unwrap(), + )]); let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs index 1db0bb1331..d550337ec5 100644 --- a/aws/sdk/integration-tests/kms/tests/integration.rs +++ b/aws/sdk/integration-tests/kms/tests/integration.rs @@ -5,10 +5,9 @@ use aws_sdk_kms as kms; use aws_sdk_kms::operation::RequestId; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::SdkError; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::header::AUTHORIZATION; use http::Uri; use kms::config::{Config, Credentials, Region}; @@ -21,14 +20,14 @@ use std::time::{Duration, UNIX_EPOCH}; /// Validate that for CN regions we set the URI correctly #[tokio::test] async fn generate_random_cn() { - let http_client= EventClient::new(vec![ConnectionEvent::new( + let http_client= StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .uri(Uri::from_static("https://kms.cn-north-1.amazonaws.com.cn/")) .body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) - ], TokioSleep::new()); + ]); let conf = Config::builder() .http_client(http_client.clone()) .region(Region::new("cn-north-1")) @@ -48,7 +47,7 @@ async fn generate_random_cn() { #[tokio::test] async fn generate_random() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -63,7 +62,7 @@ async fn generate_random() { http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) - ], TokioSleep::new()); + ]); let conf = Config::builder() .http_client(http_client.clone()) .region(Region::new("us-east-1")) @@ -100,13 +99,13 @@ async fn generate_random() { #[tokio::test] async fn generate_random_malformed_response() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder().body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) // last `}` replaced with a space, invalid JSON .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA==" "#)).unwrap()) - ], TokioSleep::new()); + ]); let conf = Config::builder() .http_client(http_client.clone()) .region(Region::new("us-east-1")) @@ -123,7 +122,7 @@ async fn generate_random_malformed_response() { #[tokio::test] async fn generate_random_keystore_not_found() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -145,7 +144,7 @@ async fn generate_random_keystore_not_found() { .header("content-type", "application/x-amz-json-1.1") .header("content-length", "44") .body(SdkBody::from(r#"{"__type":"CustomKeyStoreNotFoundException"}"#)).unwrap()) - ], TokioSleep::new()); + ]); let conf = Config::builder() .http_client(http_client.clone()) .region(Region::new("us-east-1")) diff --git a/aws/sdk/integration-tests/qldbsession/tests/integration.rs b/aws/sdk/integration-tests/qldbsession/tests/integration.rs index 47d241904a..3c7a7424fb 100644 --- a/aws/sdk/integration-tests/qldbsession/tests/integration.rs +++ b/aws/sdk/integration-tests/qldbsession/tests/integration.rs @@ -6,15 +6,14 @@ use aws_sdk_qldbsession::config::{Config, Credentials, Region}; use aws_sdk_qldbsession::types::StartSessionRequest; use aws_sdk_qldbsession::Client; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::Uri; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn signv4_use_correct_service_name() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "QLDBSession.SendCommand") @@ -29,7 +28,7 @@ async fn signv4_use_correct_service_name() { http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) .body(SdkBody::from(r#"{}"#)).unwrap()), - ], TokioSleep::new()); + ]); let conf = Config::builder() .http_client(http_client.clone()) .region(Region::new("us-east-1")) diff --git a/aws/sdk/integration-tests/s3/tests/checksums.rs b/aws/sdk/integration-tests/s3/tests/checksums.rs index 00f44be8cf..e397ec09ae 100644 --- a/aws/sdk/integration-tests/s3/tests/checksums.rs +++ b/aws/sdk/integration-tests/s3/tests/checksums.rs @@ -9,9 +9,10 @@ use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::types::ChecksumMode; use aws_sdk_s3::Client; use aws_sdk_s3::{operation::get_object::GetObjectOutput, types::ChecksumAlgorithm}; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ + capture_request, ReplayEvent, StaticReplayClient, +}; use http::header::AUTHORIZATION; use http::{HeaderValue, Uri}; use std::time::{Duration, UNIX_EPOCH}; @@ -23,9 +24,9 @@ use tracing_test::traced_test; fn new_checksum_validated_response_test_connection( checksum_header_name: &'static str, checksum_header_value: &'static str, -) -> EventClient { - EventClient::new(vec![ - ConnectionEvent::new(http::Request::builder() +) -> StaticReplayClient { + StaticReplayClient::new(vec![ + ReplayEvent::new(http::Request::builder() .header("x-amz-checksum-mode", "ENABLED") .header("user-agent", "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -47,7 +48,7 @@ fn new_checksum_validated_response_test_connection( .header("accept-ranges", "bytes") .status(http::StatusCode::from_u16(200).unwrap()) .body(SdkBody::from(r#"Hello world"#)).unwrap()), - ], TokioSleep::new()) + ]) } async fn test_checksum_on_streaming_response( diff --git a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs index a0d5d8c370..b120a34188 100644 --- a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs +++ b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs @@ -5,9 +5,8 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{config::Credentials, config::Region, types::ObjectAttributes, Client}; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_types::SdkConfig; use http::header::AUTHORIZATION; use std::time::{Duration, UNIX_EPOCH}; @@ -16,8 +15,8 @@ const RESPONSE_BODY_XML: &[u8] = b"\n< #[tokio::test] async fn ignore_invalid_xml_body_root() { - let http_client = EventClient::new(vec![ - ConnectionEvent::new(http::Request::builder() + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new(http::Request::builder() .header("x-amz-object-attributes", "Checksum") .header("x-amz-user-agent", "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -40,7 +39,7 @@ async fn ignore_invalid_xml_body_root() { .status(200) .body(SdkBody::from(RESPONSE_BODY_XML)) .unwrap()) - ], TokioSleep::new()); + ]); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( diff --git a/aws/sdk/integration-tests/s3/tests/signing-it.rs b/aws/sdk/integration-tests/s3/tests/signing-it.rs index 4c0c9fbcc4..d739bec6c1 100644 --- a/aws/sdk/integration-tests/s3/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3/tests/signing-it.rs @@ -7,21 +7,20 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210618/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=ae78f74d26b6b0c3a403d9e8cc7ec3829d6264a2b33db672bf2b151bbb901786") .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=prefix~") .body(SdkBody::empty()) .unwrap(), http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), - )], TokioSleep::new()); + )]); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), diff --git a/aws/sdk/integration-tests/s3control/tests/signing-it.rs b/aws/sdk/integration-tests/s3control/tests/signing-it.rs index 4db551c7b3..7a1a585d1c 100644 --- a/aws/sdk/integration-tests/s3control/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3control/tests/signing-it.rs @@ -6,15 +6,14 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3control::config::{Credentials, Region}; use aws_sdk_s3control::Client; -use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_types::SdkConfig; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let http_client = EventClient::new(vec![ConnectionEvent::new( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20211112/us-east-1/s3/aws4_request, \ @@ -24,7 +23,7 @@ async fn test_signer() { .body(SdkBody::empty()) .unwrap(), http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), - )], TokioSleep::new()); + )]); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt index d81a808645..56be3c3778 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt @@ -17,15 +17,13 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class HttpAuthDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( - "ConnectionEvent" to CargoDependency.smithyRuntime(runtimeConfig) + "ReplayEvent" to CargoDependency.smithyRuntime(runtimeConfig) .toDevDependency().withFeature("test-util").toType() - .resolve("client::http::test_util::ConnectionEvent"), - "EventClient" to CargoDependency.smithyRuntime(runtimeConfig) + .resolve("client::http::test_util::ReplayEvent"), + "StaticReplayClient" to CargoDependency.smithyRuntime(runtimeConfig) .toDevDependency().withFeature("test-util").toType() - .resolve("client::http::test_util::EventClient"), + .resolve("client::http::test_util::StaticReplayClient"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), - "TokioSleep" to CargoDependency.smithyAsync(runtimeConfig).withFeature("rt-tokio").toType() - .resolve("rt::sleep::TokioSleep"), ) @Test @@ -39,15 +37,14 @@ class HttpAuthDecoratorTest { async fn use_api_key_auth_when_api_key_provided() { use aws_smithy_runtime_api::client::identity::http::Token; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") .body(#{SdkBody}::empty()) .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -71,8 +68,8 @@ class HttpAuthDecoratorTest { async fn use_basic_auth_when_basic_auth_login_provided() { use aws_smithy_runtime_api::client::identity::http::Login; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") .uri("http://localhost:1234/SomeOperation") @@ -80,7 +77,6 @@ class HttpAuthDecoratorTest { .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -113,15 +109,14 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_query_string() { use aws_smithy_runtime_api::client::identity::http::Token; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") .body(#{SdkBody}::empty()) .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -154,8 +149,8 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_headers() { use aws_smithy_runtime_api::client::identity::http::Token; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .header("authorization", "ApiKey some-api-key") .uri("http://localhost:1234/SomeOperation") @@ -163,7 +158,6 @@ class HttpAuthDecoratorTest { .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -196,8 +190,8 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Login; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") .uri("http://localhost:1234/SomeOperation") @@ -205,7 +199,6 @@ class HttpAuthDecoratorTest { .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -238,8 +231,8 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Token; - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .header("authorization", "Bearer some-token") .uri("http://localhost:1234/SomeOperation") @@ -247,7 +240,6 @@ class HttpAuthDecoratorTest { .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() @@ -278,15 +270,14 @@ class HttpAuthDecoratorTest { rustTemplate( """ async fn optional_auth() { - let http_client = #{EventClient}::new( - vec![#{ConnectionEvent}::new( + let http_client = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( http::Request::builder() .uri("http://localhost:1234/SomeOperation") .body(#{SdkBody}::empty()) .unwrap(), http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), )], - #{TokioSleep}::new(), ); let config = $moduleName::Config::builder() diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs index 67ab903137..c05b3dca54 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -//! Various fake clients for testing. +//! Various fake/mock clients for testing. //! //! Each test client is useful for different test use cases: //! - [`capture_request`](capture_request::capture_request): If you don't care what the @@ -11,28 +11,39 @@ //! then use `capture_request`. Or, alternatively, if you don't care what the request //! is, but want to always respond with a given response, then capture request can also //! be useful since you can optionally give it a response to return. -//! - [`dvr`]: If you want to record real-world traffic and then replay it later, then DVR's +#![cfg_attr( + feature = "connector-hyper-0-14-x", + doc = "- [`dvr`]: If you want to record real-world traffic and then replay it later, then DVR's" +)] //! [`RecordingClient`](dvr::RecordingClient) and [`ReplayingClient`](dvr::ReplayingClient) //! can accomplish this, and the recorded traffic can be saved to JSON and checked in. Note: if //! the traffic recording has sensitive information in it, such as signatures or authorization, //! you will need to manually scrub this out if you intend to store the recording alongside //! your tests. -//! - [`EventClient`]: If you want to have a set list of requests and their responses in a test, -//! then the event connector will be useful. On construction, it takes a list of tuples that represent -//! each expected request and the response for that request. At the end of the test, you can ask the -//! connector to verify that the requests matched the expectations. -//! - [`infallible_client_fn`]: Allows you to create a connector from an infallible function +//! - [`StaticReplayClient`]: If you want to have a set list of requests and their responses in a test, +//! then the static replay client will be useful. On construction, it takes a list of request/response +//! pairs that represent each expected request and the response for that test. At the end of the test, +//! you can ask the client to verify that the requests matched the expectations. +//! - [`infallible_client_fn`]: Allows you to create a client from an infallible function //! that takes a request and returns a response. -//! - [`NeverClient`]: Useful for testing timeouts, where you want the connector to never respond. - +//! - [`NeverClient`]: Useful for testing timeouts, where you want the client to never respond. +//! +#![cfg_attr( + feature = "connector-hyper-0-14-x", + doc = " +There is also the [`NeverTcpConnector`], which makes it easy to test connect/read timeouts. + +Finally, for socket-level mocking, see the [`wire`] module. +" +)] mod capture_request; pub use capture_request::{capture_request, CaptureRequestHandler, CaptureRequestReceiver}; #[cfg(feature = "connector-hyper-0-14-x")] pub mod dvr; -mod event; -pub use event::{ConnectionEvent, EventClient}; +mod replay; +pub use replay::{ReplayEvent, StaticReplayClient}; mod infallible; pub use infallible::infallible_client_fn; diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs similarity index 65% rename from rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs index 9b5ead9e97..4e713e1dd4 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; -use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; use aws_smithy_runtime_api::client::http::{ @@ -16,52 +14,39 @@ use aws_smithy_runtime_api::shared::IntoShared; use http::header::{HeaderName, CONTENT_TYPE}; use std::ops::Deref; use std::sync::{Arc, Mutex, MutexGuard}; -use std::time::Duration; -type ConnectionEvents = Vec; +type ReplayEvents = Vec; -/// Test data for the [`EventClient`]. +/// Test data for the [`StaticReplayClient`]. /// -/// Each `ConnectionEvent` represents one HTTP request and response -/// through the connector. Optionally, a latency value can be set to simulate -/// network latency (done via async sleep in the `EventClient`). +/// Each `ReplayEvent` represents one HTTP request and response +/// through the connector. #[derive(Debug)] -pub struct ConnectionEvent { - latency: Duration, - req: HttpRequest, - res: HttpResponse, +pub struct ReplayEvent { + request: HttpRequest, + response: HttpResponse, } -impl ConnectionEvent { - /// Creates a new `ConnectionEvent`. - pub fn new(req: HttpRequest, res: HttpResponse) -> Self { - Self { - res, - req, - latency: Duration::from_secs(0), - } - } - - /// Add simulated latency to this `ConnectionEvent` - pub fn with_latency(mut self, latency: Duration) -> Self { - self.latency = latency; - self +impl ReplayEvent { + /// Creates a new `ReplayEvent`. + pub fn new(request: HttpRequest, response: HttpResponse) -> Self { + Self { request, response } } /// Returns the test request. pub fn request(&self) -> &HttpRequest { - &self.req + &self.request } /// Returns the test response. pub fn response(&self) -> &HttpResponse { - &self.res + &self.response } } -impl From<(HttpRequest, HttpResponse)> for ConnectionEvent { - fn from((req, res): (HttpRequest, HttpResponse)) -> Self { - Self::new(req, res) +impl From<(HttpRequest, HttpResponse)> for ReplayEvent { + fn from((request, response): (HttpRequest, HttpResponse)) -> Self { + Self::new(request, response) } } @@ -115,26 +100,65 @@ impl ValidateRequest { } } -/// Request/response event-driven client for use in tests. +/// Request/response replaying client for use in tests. +/// +/// This mock client takes a list of request/response pairs named [`ReplayEvent`]. While the client +/// is in use, the responses will be given in the order they appear in the list regardless of what +/// the actual request was. The actual request is recorded, but otherwise not validated against what +/// is in the [`ReplayEvent`]. Later, after the client is finished being used, the +/// [`assert_requests_match`] method can be used to validate the requests. +/// +/// This utility is simpler than [DVR], and thus, is good for tests that don't need +/// to record and replay real traffic. +/// +/// # Example +/// +/// ```no_run +/// use aws_smithy_runtime::client::http::test_util::StaticReplayClient; +/// use aws_smithy_runtime_api::shared::IntoShared; +/// +/// let http_client = StaticReplayClient::new( +/// vec![ +/// // Event that covers the first request/response +/// ReplayEvent::new( +/// // If `assert_requests_match` is called later, then this request will be matched +/// // against the actual request that was made. +/// http::Request::builder().uri("http://localhost:1234/foo").body(SdkBody::empty()).unwrap(), +/// // This response will be given to the first request regardless of whether it matches the request above. +/// http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), +/// ), +/// // The next ReplayEvent covers the second request/response pair... +/// ] +/// ).into_shared(); +/// +/// # /* +/// let config = my_generated_client::Config::builder() +/// .http_client(http_client.clone()) +/// .build(); +/// let client = my_generated_client::Client::from_conf(config); +/// # */ +/// +/// // Do stuff with client... +/// +/// // When you're done, assert the requests match what you expected +/// http_client.assert_requests_match(&[]); +/// ``` /// -/// A basic test connection. It will: -/// - Respond to requests with a preloaded series of responses -/// - Record requests for future examination +/// [`assert_requests_match`]: crate::client::http::test_util::StaticReplayClient::assert_requests_match +/// [DVR]: crate::client::http::test_util::dvr #[derive(Clone, Debug)] -pub struct EventClient { - data: Arc>, +pub struct StaticReplayClient { + data: Arc>, requests: Arc>>, - sleep_impl: SharedAsyncSleep, } -impl EventClient { +impl StaticReplayClient { /// Creates a new event connector. - pub fn new(mut data: ConnectionEvents, sleep_impl: impl IntoShared) -> Self { + pub fn new(mut data: ReplayEvents) -> Self { data.reverse(); - EventClient { + StaticReplayClient { data: Arc::new(Mutex::new(data)), requests: Default::default(), - sleep_impl: sleep_impl.into_shared(), } } @@ -207,34 +231,27 @@ impl EventClient { } } -impl HttpConnector for EventClient { +impl HttpConnector for StaticReplayClient { fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - let (res, simulated_latency) = if let Some(event) = self.data.lock().unwrap().pop() { + let res = if let Some(event) = self.data.lock().unwrap().pop() { self.requests.lock().unwrap().push(ValidateRequest { - expected: event.req, + expected: event.request, actual: request, }); - (Ok(event.res.map(SdkBody::from)), event.latency) + Ok(event.response) } else { - ( - Err(ConnectorError::other( - "EventClient: no more test data available to respond with".into(), - None, - )), - Duration::from_secs(0), - ) + Err(ConnectorError::other( + "StaticReplayClient: no more test data available to respond with".into(), + None, + )) }; - let sleep = self.sleep_impl.sleep(simulated_latency); - HttpConnectorFuture::new(async move { - sleep.await; - res - }) + HttpConnectorFuture::new(async move { res }) } } -impl HttpClient for EventClient { +impl HttpClient for StaticReplayClient { fn http_connector( &self, _: &HttpConnectorSettings, diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index c7638ef5d8..ebf62f7cf8 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -375,7 +375,7 @@ impl OperationBuilder { #[cfg(all(test, feature = "test-util"))] mod tests { use super::*; - use crate::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; + use crate::client::http::test_util::{capture_request, ReplayEvent, StaticReplayClient}; use crate::client::retries::classifier::HttpStatusCodeClassifier; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; @@ -424,31 +424,28 @@ mod tests { #[tokio::test] async fn operation_retries() { - let connector = EventClient::new( - vec![ - ConnectionEvent::new( - http::Request::builder() - .uri("http://localhost:1234/") - .body(SdkBody::from(&b"what are you?"[..])) - .unwrap(), - http::Response::builder() - .status(503) - .body(SdkBody::from(&b""[..])) - .unwrap(), - ), - ConnectionEvent::new( - http::Request::builder() - .uri("http://localhost:1234/") - .body(SdkBody::from(&b"what are you?"[..])) - .unwrap(), - http::Response::builder() - .status(418) - .body(SdkBody::from(&b"I'm a teapot!"[..])) - .unwrap(), - ), - ], - SharedAsyncSleep::new(TokioSleep::new()), - ); + let connector = StaticReplayClient::new(vec![ + ReplayEvent::new( + http::Request::builder() + .uri("http://localhost:1234/") + .body(SdkBody::from(&b"what are you?"[..])) + .unwrap(), + http::Response::builder() + .status(503) + .body(SdkBody::from(&b""[..])) + .unwrap(), + ), + ReplayEvent::new( + http::Request::builder() + .uri("http://localhost:1234/") + .body(SdkBody::from(&b"what are you?"[..])) + .unwrap(), + http::Response::builder() + .status(418) + .body(SdkBody::from(&b"I'm a teapot!"[..])) + .unwrap(), + ), + ]); let operation = Operation::builder() .service_name("test") .operation_name("test") From b6b428f0ae814564451c87e19e54c34ead540e28 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 17:15:51 -0700 Subject: [PATCH 10/23] New-type DNS resolution error --- aws/rust-runtime/aws-config/src/ecs.rs | 9 ++--- .../aws-smithy-runtime-api/src/client/dns.rs | 39 ++++++++++++++++--- .../aws-smithy-runtime/src/client/dns.rs | 9 +++-- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index 45ce7c342d..719c17506c 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -50,8 +50,7 @@ use crate::http_credential_provider::HttpCredentialProvider; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_smithy_http::endpoint::apply_endpoint; -use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::dns::{DnsResolver, SharedDnsResolver}; +use aws_smithy_runtime_api::client::dns::{DnsResolver, ResolveDnsError, SharedDnsResolver}; use aws_smithy_runtime_api::client::http::HttpConnectorSettings; use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; @@ -330,7 +329,7 @@ enum InvalidFullUriErrorKind { NotLoopback, /// DNS lookup failed when attempting to resolve the host to an IP Address for validation. - DnsLookupFailed(BoxError), + DnsLookupFailed(ResolveDnsError), } /// Invalid Full URI @@ -366,7 +365,7 @@ impl Error for InvalidFullUriError { use InvalidFullUriErrorKind::*; match &self.kind { InvalidUri(err) => Some(err), - DnsLookupFailed(err) => Some(&**err as _), + DnsLookupFailed(err) => Some(err as _), _ => None, } } @@ -402,7 +401,7 @@ async fn validate_full_uri( let dns = dns.ok_or(InvalidFullUriErrorKind::NoDnsResolver)?; dns.resolve_dns(host.to_owned()) .await - .map_err(InvalidFullUriErrorKind::DnsLookupFailed)? + .map_err(|err| InvalidFullUriErrorKind::DnsLookupFailed(ResolveDnsError::new(err)))? .iter() .all(|addr| { if !addr.is_loopback() { diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs index 5dc237b82a..ce00e13f57 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs @@ -6,9 +6,9 @@ //! Interfaces for resolving DNS use crate::box_error::BoxError; -use crate::client::orchestrator::BoxFuture; use crate::impl_shared_conversions; use aws_smithy_async::future::now_or_later::NowOrLater; +use std::error::Error as StdError; use std::fmt; use std::future::Future; use std::net::IpAddr; @@ -16,23 +16,52 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +/// Error that occurs when failing to perform a DNS lookup. +#[derive(Debug)] +pub struct ResolveDnsError { + source: BoxError, +} + +impl ResolveDnsError { + /// Creates a new `DnsLookupFailed` error. + pub fn new(source: impl Into) -> Self { + ResolveDnsError { + source: source.into(), + } + } +} + +impl fmt::Display for ResolveDnsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to perform DNS lookup") + } +} + +impl StdError for ResolveDnsError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&*self.source as _) + } +} + +type BoxFuture = Pin> + Send>>; + /// New-type for the future returned by the [`DnsResolver`] trait. -pub struct DnsFuture(NowOrLater, BoxError>, BoxFuture>>); +pub struct DnsFuture(NowOrLater, ResolveDnsError>, BoxFuture>>); impl DnsFuture { /// Create a new `DnsFuture` pub fn new( - future: impl Future, BoxError>> + Send + 'static, + future: impl Future, ResolveDnsError>> + Send + 'static, ) -> Self { Self(NowOrLater::new(Box::pin(future))) } /// Create a `DnsFuture` that is immediately ready - pub fn ready(result: Result, BoxError>) -> Self { + pub fn ready(result: Result, ResolveDnsError>) -> Self { Self(NowOrLater::ready(result)) } } impl Future for DnsFuture { - type Output = Result, BoxError>; + type Output = Result, ResolveDnsError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.as_mut(); diff --git a/rust-runtime/aws-smithy-runtime/src/client/dns.rs b/rust-runtime/aws-smithy-runtime/src/client/dns.rs index 8f4da84b6c..e7eb561c2b 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/dns.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/dns.rs @@ -7,7 +7,7 @@ #[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] mod tokio { - use aws_smithy_runtime_api::client::dns::{DnsFuture, DnsResolver}; + use aws_smithy_runtime_api::client::dns::{DnsFuture, DnsResolver, ResolveDnsError}; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::net::ToSocketAddrs; @@ -30,11 +30,14 @@ mod tokio { DnsFuture::new(async move { let result = tokio::task::spawn_blocking(move || (name, 0).to_socket_addrs()).await; match result { - Err(join_failure) => Err(IoError::new(IoErrorKind::Other, join_failure).into()), + Err(join_failure) => Err(ResolveDnsError::new(IoError::new( + IoErrorKind::Other, + join_failure, + ))), Ok(Ok(dns_result)) => { Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) } - Ok(Err(dns_failure)) => Err(dns_failure.into()), + Ok(Err(dns_failure)) => Err(ResolveDnsError::new(dns_failure)), } }) } From 4e1021cf8a8756e0f6d080173c947b454c3bc4b3 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 17:54:55 -0700 Subject: [PATCH 11/23] Add doc comments around usage of `IntoShared` arguments --- aws/rust-runtime/aws-config/src/ecs.rs | 3 ++ aws/rust-runtime/aws-config/src/lib.rs | 28 ++++++++-- .../aws-config/src/provider_config.rs | 9 ++++ .../src/cache/lazy_caching.rs | 6 +++ aws/rust-runtime/aws-types/src/sdk_config.rs | 11 +++- .../src/client/runtime_components.rs | 54 +++++++++++++++++++ .../src/client/runtime_plugin.rs | 8 +++ .../src/client/http/hyper_014.rs | 3 ++ .../src/client/http/test_util/dvr/record.rs | 3 ++ 9 files changed, 118 insertions(+), 7 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index 719c17506c..757b0547d2 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -274,6 +274,9 @@ impl Builder { /// /// URIs must refer to loopback addresses. The [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) /// is used to retrieve IP addresses for a given domain. + /// + /// Takes an implementation of [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn dns(mut self, dns: impl IntoShared) -> Self { self.dns = Some(dns.into_shared()); self diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 1ac4d51c5c..f668064629 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -242,6 +242,7 @@ mod loader { } /// Override the timeout config used to build [`SdkConfig`](aws_types::SdkConfig). + /// /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials /// provider chain are configured separately. /// @@ -266,26 +267,43 @@ mod loader { self } - /// Override the sleep implementation for this [`ConfigLoader`]. The sleep implementation - /// is used to create timeout futures. + /// Override the sleep implementation for this [`ConfigLoader`]. + /// + /// The sleep implementation is used to create timeout futures. + /// You generally won't need to change this unless you're using an async runtime other + /// than Tokio. + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn sleep_impl(mut self, sleep: impl IntoShared) -> Self { // it's possible that we could wrapping an `Arc in an `Arc` and that's OK self.sleep = Some(sleep.into_shared()); self } - /// Set the time source used for tasks like signing requests + /// Set the time source used for tasks like signing requests. + /// + /// You generally won't need to change this unless you're compiling for a target + /// that can't provide a default, such as WASM, or unless you're writing a test against + /// the client that needs a fixed time. + /// + /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn time_source(mut self, time_source: impl IntoShared) -> Self { self.time_source = Some(time_source.into_shared()); self } - /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this - /// [`ConfigLoader`]. The HTTP client will be used for both AWS services and credentials providers. + /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this [`ConfigLoader`]. + /// + /// The HTTP client will be used for both AWS services and credentials providers. /// /// If you wish to use a separate HTTP client for credentials providers when creating clients, /// then override the HTTP client set with this function on the client-specific `Config`s. /// + /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) + /// as an argument. All implementations of this trait implement `IntoShared`. + /// /// ## Examples /// /// ```no_run diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index da36ddb628..3c6b104868 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -290,6 +290,9 @@ impl ProviderConfig { } /// Override the time source for this configuration + /// + /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_time_source(self, time_source: impl IntoShared) -> Self { ProviderConfig { time_source: time_source.into_shared(), @@ -298,6 +301,9 @@ impl ProviderConfig { } /// Override the HTTP client for this configuration + /// + /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_http_client(self, http_client: impl IntoShared) -> Self { ProviderConfig { http_client: Some(http_client.into_shared()), @@ -306,6 +312,9 @@ impl ProviderConfig { } /// Override the sleep implementation for this configuration + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_sleep_impl(self, sleep_impl: impl IntoShared) -> Self { ProviderConfig { sleep_impl: Some(sleep_impl.into_shared()), diff --git a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs index 8d09a13a8e..0ec98aefca 100644 --- a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs @@ -178,6 +178,9 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { self.set_sleep_impl(Some(sleep_impl.into_shared())); self @@ -188,6 +191,9 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { self.sleep_impl = sleep_impl; self diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index fcb1285b28..f92e6f3e34 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -231,8 +231,12 @@ impl Builder { self } - /// Set the sleep implementation for the builder. The sleep implementation is used to create - /// timeout futures. + /// Set the sleep implementation for the builder. + /// + /// The sleep implementation is used to create timeout futures. + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. /// /// _Note:_ If you're using the Tokio runtime, a `TokioSleep` implementation is available in /// the `aws-smithy-async` crate. @@ -402,6 +406,9 @@ impl Builder { /// Sets the HTTP client to use when making requests. /// + /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) + /// as an argument. All implementations of this trait implement `IntoShared`. + /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs index a0ef4a8023..6f6e5c7b05 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs @@ -272,6 +272,9 @@ impl RuntimeComponentsBuilder { } /// Sets the auth scheme option resolver. + /// + /// Takes an implementation of [`AuthSchemeOptionResolver`](crate::client::auth::AuthSchemeOptionResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_auth_scheme_option_resolver( &mut self, auth_scheme_option_resolver: Option>, @@ -282,6 +285,9 @@ impl RuntimeComponentsBuilder { } /// Sets the auth scheme option resolver. + /// + /// Takes an implementation of [`AuthSchemeOptionResolver`](crate::client::auth::AuthSchemeOptionResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_auth_scheme_option_resolver( mut self, auth_scheme_option_resolver: Option>, @@ -296,6 +302,9 @@ impl RuntimeComponentsBuilder { } /// Sets the HTTP client. + /// + /// Takes an implementation of [`HttpClient`](crate::client::http::HttpClient) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_http_client( &mut self, connector: Option>, @@ -305,6 +314,9 @@ impl RuntimeComponentsBuilder { } /// Sets the HTTP client. + /// + /// Takes an implementation of [`HttpClient`](crate::client::http::HttpClient) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_http_client( mut self, connector: Option>, @@ -319,6 +331,9 @@ impl RuntimeComponentsBuilder { } /// Sets the endpoint resolver. + /// + /// Takes an implementation of [`EndpointResolver`](crate::client::endpoint::EndpointResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_endpoint_resolver( &mut self, endpoint_resolver: Option>, @@ -329,6 +344,9 @@ impl RuntimeComponentsBuilder { } /// Sets the endpoint resolver. + /// + /// Takes an implementation of [`EndpointResolver`](crate::client::endpoint::EndpointResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_endpoint_resolver( mut self, endpoint_resolver: Option>, @@ -343,6 +361,9 @@ impl RuntimeComponentsBuilder { } /// Adds an auth scheme. + /// + /// Takes an implementation of [`AuthScheme`](crate::client::auth::AuthScheme) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn push_auth_scheme( &mut self, auth_scheme: impl IntoShared, @@ -353,12 +374,18 @@ impl RuntimeComponentsBuilder { } /// Adds an auth scheme. + /// + /// Takes an implementation of [`AuthScheme`](crate::client::auth::AuthScheme) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_auth_scheme(mut self, auth_scheme: impl IntoShared) -> Self { self.push_auth_scheme(auth_scheme); self } /// Adds an identity resolver. + /// + /// Takes an implementation of [`IdentityResolver`](crate::client::identity::IdentityResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn push_identity_resolver( &mut self, scheme_id: AuthSchemeId, @@ -372,6 +399,9 @@ impl RuntimeComponentsBuilder { } /// Adds an identity resolver. + /// + /// Takes an implementation of [`IdentityResolver`](crate::client::identity::IdentityResolver) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_identity_resolver( mut self, scheme_id: AuthSchemeId, @@ -397,6 +427,9 @@ impl RuntimeComponentsBuilder { } /// Adds an interceptor. + /// + /// Takes an implementation of [`Interceptor`](crate::client::interceptors::Interceptor) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn push_interceptor( &mut self, interceptor: impl IntoShared, @@ -407,6 +440,9 @@ impl RuntimeComponentsBuilder { } /// Adds an interceptor. + /// + /// Takes an implementation of [`Interceptor`](crate::client::interceptors::Interceptor) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_interceptor(mut self, interceptor: impl IntoShared) -> Self { self.push_interceptor(interceptor); self @@ -458,6 +494,9 @@ impl RuntimeComponentsBuilder { } /// Sets the retry strategy. + /// + /// Takes an implementation of [`RetryStrategy`](crate::client::retries::RetryStrategy) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_retry_strategy( &mut self, retry_strategy: Option>, @@ -468,6 +507,9 @@ impl RuntimeComponentsBuilder { } /// Sets the retry strategy. + /// + /// Takes an implementation of [`RetryStrategy`](crate::client::retries::RetryStrategy) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_retry_strategy( mut self, retry_strategy: Option>, @@ -483,12 +525,18 @@ impl RuntimeComponentsBuilder { } /// Sets the async sleep implementation. + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s)); self } /// Sets the async sleep implementation. + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_sleep_impl( mut self, sleep_impl: Option>, @@ -503,12 +551,18 @@ impl RuntimeComponentsBuilder { } /// Sets the time source. + /// + /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_time_source(&mut self, time_source: Option) -> &mut Self { self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s)); self } /// Sets the time source. + /// + /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_time_source( mut self, time_source: Option>, diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs index 2412080ab9..c0ab32d042 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs @@ -240,11 +240,19 @@ impl RuntimePlugins { Default::default() } + /// Adds a client-level runtime plugin. + /// + /// Takes an implementation of [`RuntimePlugin`](crate::client::runtime_plugin::RuntimePlugin) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_client_plugin(mut self, plugin: impl IntoShared) -> Self { insert_plugin!(self.client_plugins, plugin.into_shared()); self } + /// Adds an operation-level runtime plugin. + /// + /// Takes an implementation of [`RuntimePlugin`](crate::client::runtime_plugin::RuntimePlugin) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_operation_plugin(mut self, plugin: impl IntoShared) -> Self { insert_plugin!(self.operation_plugins, plugin.into_shared()); self diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 132a388ea2..94c1cdf957 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -248,6 +248,9 @@ impl HyperConnectorBuilder { /// /// Calling this is only necessary for testing or to use something other than /// [`default_async_sleep`]. + /// + /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { self.sleep_impl = Some(sleep_impl.into_shared()); self diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs index 8ab4e56d3e..6675598b69 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs @@ -46,6 +46,9 @@ impl RecordingClient { impl RecordingClient { /// Create a new recording connection from a connection + /// + /// Takes an implementation of [`HttpConnector`](aws_smithy_runtime_api::client::http::HttpConnector) + /// as an argument. All implementations of this trait implement `IntoShared`. pub fn new(underlying_connector: impl IntoShared) -> Self { Self { data: Default::default(), From d5f8332469182111602658887827d1ec169d5431 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 3 Oct 2023 18:02:53 -0700 Subject: [PATCH 12/23] Fix more CI things --- aws/rust-runtime/aws-runtime/additional-ci | 12 ++++++++ .../aws-runtime/src/invocation_id.rs | 3 +- .../tests/client-construction.rs | 10 ++++--- rust-runtime/aws-smithy-async/Cargo.toml | 2 +- .../aws-smithy-async/src/test_util.rs | 1 - .../src/client/http/test_util/replay.rs | 28 +++++++++---------- .../src/client/orchestrator.rs | 4 +-- .../src/client/orchestrator/operation.rs | 2 +- 8 files changed, 37 insertions(+), 25 deletions(-) create mode 100755 aws/rust-runtime/aws-runtime/additional-ci diff --git a/aws/rust-runtime/aws-runtime/additional-ci b/aws/rust-runtime/aws-runtime/additional-ci new file mode 100755 index 0000000000..b44c6c05be --- /dev/null +++ b/aws/rust-runtime/aws-runtime/additional-ci @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +# This script contains additional CI checks to run for this specific package + +set -e + +echo "### Testing every combination of features (excluding --all-features)" +cargo hack test --feature-powerset --exclude-all-features diff --git a/aws/rust-runtime/aws-runtime/src/invocation_id.rs b/aws/rust-runtime/aws-runtime/src/invocation_id.rs index 18fcac7c4a..7a8b47feb0 100644 --- a/aws/rust-runtime/aws-runtime/src/invocation_id.rs +++ b/aws/rust-runtime/aws-runtime/src/invocation_id.rs @@ -219,7 +219,7 @@ mod tests { }; use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; - use aws_smithy_types::config_bag::{ConfigBag, Layer}; + use aws_smithy_types::config_bag::ConfigBag; use http::HeaderValue; fn expect_header<'a>( @@ -258,6 +258,7 @@ mod tests { #[cfg(feature = "test-util")] #[test] fn custom_id_generator() { + use aws_smithy_types::config_bag::Layer; let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); let mut ctx = InterceptorContext::new(Input::doesnt_matter()); ctx.enter_serialization_phase(); 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 f90ca59fb1..e7d11dbc31 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 @@ -12,7 +12,9 @@ use std::time::Duration; // If this test doesn't panic, you may have accidentally unified features, resulting in // the connector being enabled transitively #[tokio::test] -#[should_panic(expected = "Enable the `rustls` crate feature or set a connector to fix this.")] +#[should_panic( + expected = "Enable the `rustls` crate feature or configure a HTTP client to fix this." +)] async fn test_clients_from_sdk_config() { aws_config::load_from_env().await; } @@ -42,10 +44,10 @@ async fn test_clients_from_service_config() { .list_buckets() .send() .await - .expect_err("it should fail to send a request because there is no connector"); + .expect_err("it should fail to send a request because there is no HTTP client"); 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.'" + msg.contains("No HTTP client was available to send this request. Enable the `rustls` crate feature or configure a HTTP client to fix this."), + "expected '{msg}' to contain 'No HTTP client was available to send this request. Enable the `rustls` crate feature or set a HTTP client to fix this.'" ); } diff --git a/rust-runtime/aws-smithy-async/Cargo.toml b/rust-runtime/aws-smithy-async/Cargo.toml index fd51b4fb1e..12e72c4fea 100644 --- a/rust-runtime/aws-smithy-async/Cargo.toml +++ b/rust-runtime/aws-smithy-async/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/awslabs/smithy-rs" [features] rt-tokio = ["tokio/time"] -test-util = [] +test-util = ["rt-tokio"] [dependencies] pin-project-lite = "0.2" diff --git a/rust-runtime/aws-smithy-async/src/test_util.rs b/rust-runtime/aws-smithy-async/src/test_util.rs index e323478d84..e6128b76b3 100644 --- a/rust-runtime/aws-smithy-async/src/test_util.rs +++ b/rust-runtime/aws-smithy-async/src/test_util.rs @@ -22,7 +22,6 @@ pub struct ManualTimeSource { log: Arc>>, } -#[cfg(feature = "test-util")] impl ManualTimeSource { /// Get the number of seconds since the UNIX Epoch as an f64. /// diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs index 4e713e1dd4..55bd50434c 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs @@ -114,22 +114,20 @@ impl ValidateRequest { /// # Example /// /// ```no_run -/// use aws_smithy_runtime::client::http::test_util::StaticReplayClient; -/// use aws_smithy_runtime_api::shared::IntoShared; +/// use aws_smithy_http::body::SdkBody; +/// use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; /// -/// let http_client = StaticReplayClient::new( -/// vec![ -/// // Event that covers the first request/response -/// ReplayEvent::new( -/// // If `assert_requests_match` is called later, then this request will be matched -/// // against the actual request that was made. -/// http::Request::builder().uri("http://localhost:1234/foo").body(SdkBody::empty()).unwrap(), -/// // This response will be given to the first request regardless of whether it matches the request above. -/// http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), -/// ), -/// // The next ReplayEvent covers the second request/response pair... -/// ] -/// ).into_shared(); +/// let http_client = StaticReplayClient::new(vec![ +/// // Event that covers the first request/response +/// ReplayEvent::new( +/// // If `assert_requests_match` is called later, then this request will be matched +/// // against the actual request that was made. +/// http::Request::builder().uri("http://localhost:1234/foo").body(SdkBody::empty()).unwrap(), +/// // This response will be given to the first request regardless of whether it matches the request above. +/// http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), +/// ), +/// // The next ReplayEvent covers the second request/response pair... +/// ]); /// /// # /* /// let config = my_generated_client::Config::builder() diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index e66e3b268d..576eab5cf3 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -358,8 +358,8 @@ async fn try_attempt( let request = ctx.take_request().expect("set during serialization"); trace!(request = ?request, "transmitting request"); let http_client = halt_on_err!([ctx] => runtime_components.http_client().ok_or_else(|| - OrchestratorError::other("No HTTP connector was available to send this request. \ - Enable the `rustls` crate feature or set a connector to fix this.") + OrchestratorError::other("No HTTP client was available to send this request. \ + Enable the `rustls` crate feature or configure a HTTP client to fix this.") )); let timeout_config = cfg.load::().expect("timeout config must be set"); let settings = { diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index ebf62f7cf8..dfeaeced77 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -335,7 +335,7 @@ impl OperationBuilder { assert!( components.http_client().is_some(), - "a http_client is required" + "a http_client is required. Enable the `rustls` crate feature or configure a HTTP client to fix this." ); assert!( components.endpoint_resolver().is_some(), From 2c009d502a9105aa5ac1908cfd95bd5ef64dcbda Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 4 Oct 2023 10:49:28 -0700 Subject: [PATCH 13/23] Fix external type checks --- .../aws-inlineable/external-types.toml | 17 +++-------------- aws/rust-runtime/aws-types/external-types.toml | 8 ++------ aws/sdk/sdk-external-types.toml | 12 +----------- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/aws/rust-runtime/aws-inlineable/external-types.toml b/aws/rust-runtime/aws-inlineable/external-types.toml index a261a6c437..0d2b9f397c 100644 --- a/aws/rust-runtime/aws-inlineable/external-types.toml +++ b/aws/rust-runtime/aws-inlineable/external-types.toml @@ -1,22 +1,11 @@ allowed_external_types = [ "aws_credential_types::provider::ProvideCredentials", - "aws_endpoint::*", - "aws_http::*", - "aws_sig_auth::*", - "aws_smithy_client::*", "aws_smithy_http::*", - "aws_smithy_http_tower::*", - "aws_smithy_types::*", - "aws_types::*", + + "http::error::Error", "http::header::map::HeaderMap", "http::header::value::HeaderValue", + "http::method::Method", "http::request::Request", - "http::error::Error", "http::uri::Uri", - "http::method::Method", - - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if we want to continue exposing tower_layer - "tower_layer::Layer", - "tower_layer::identity::Identity", - "tower_layer::stack::Stack", ] diff --git a/aws/rust-runtime/aws-types/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index e43510d69e..3e415f1084 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -4,15 +4,11 @@ allowed_external_types = [ "aws_smithy_async::rt::sleep::SharedAsyncSleep", "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", - "aws_smithy_client::http_connector", - "aws_smithy_client::http_connector::HttpConnector", - "aws_smithy_http::endpoint::Endpoint", - "aws_smithy_http::endpoint::EndpointPrefix", - "aws_smithy_http::endpoint::error::InvalidEndpointError", + "aws_smithy_runtime_api::client::http::SharedHttpClient", + "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", "aws_smithy_types::config_bag::storable::Storer", "aws_smithy_types::retry::RetryConfig", "aws_smithy_types::timeout::TimeoutConfig", - "http::uri::Uri", ] diff --git a/aws/sdk/sdk-external-types.toml b/aws/sdk/sdk-external-types.toml index b484544c27..4705c4e295 100644 --- a/aws/sdk/sdk-external-types.toml +++ b/aws/sdk/sdk-external-types.toml @@ -1,17 +1,15 @@ # These are the allowed external types in the `aws-sdk-*` generated crates, checked by CI. allowed_external_types = [ "aws_credential_types::*", - "aws_endpoint::*", "aws_http::*", "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", "http::header::value::HeaderValue", "http::request::Request", @@ -19,14 +17,6 @@ allowed_external_types = [ "http::uri::Uri", "http::method::Method", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Switch to AsyncIterator once standardized - "futures_core::stream::Stream", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `event-stream` feature "aws_smithy_eventstream::*", - - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if we want to continue exposing tower_layer - "tower_layer::Layer", - "tower_layer::identity::Identity", - "tower_layer::stack::Stack", ] From 1b0e0bd758ccc0fdea0372e5bc6919964eecf3e5 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 4 Oct 2023 14:03:50 -0700 Subject: [PATCH 14/23] Retain old http_connector methods as hints for upgrade guidance --- aws/rust-runtime/aws-config/src/lib.rs | 8 + aws/rust-runtime/aws-inlineable/Cargo.toml | 4 - .../aws-inlineable/src/glacier_checksums.rs | 227 ---------- .../src/http_body_checksum_middleware.rs | 417 ------------------ aws/rust-runtime/aws-inlineable/src/lib.rs | 7 - .../aws-inlineable/src/s3_request_id.rs | 2 +- .../rustsdk/IntegrationTestDependencies.kt | 5 +- .../HttpConnectorConfigDecorator.kt | 29 +- 8 files changed, 34 insertions(+), 665 deletions(-) delete mode 100644 aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs delete mode 100644 aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index f16f64f32a..ff25472e51 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -294,6 +294,14 @@ mod loader { self } + /// Deprecated. Don't use. + #[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(self, http_client: impl IntoShared) -> Self { + self.http_client(http_client) + } + /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this [`ConfigLoader`]. /// /// The HTTP client will be used for both AWS services and credentials providers. diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml index 6c70fb8e5f..28f4aeb212 100644 --- a/aws/rust-runtime/aws-inlineable/Cargo.toml +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -13,15 +13,11 @@ repository = "https://github.com/awslabs/smithy-rs" [dependencies] aws-credential-types = { path = "../aws-credential-types" } -aws-endpoint = { path = "../aws-endpoint" } aws-http = { path = "../aws-http" } aws-runtime = { path = "../aws-runtime" } aws-sigv4 = { path = "../aws-sigv4" } -aws-sig-auth = { path = "../aws-sig-auth" } aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" } -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } -aws-smithy-http-tower = { path = "../../../rust-runtime/aws-smithy-http-tower" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } diff --git a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs b/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs deleted file mode 100644 index bf95910e00..0000000000 --- a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// TODO(enableNewSmithyRuntimeCleanup): Delete this file when cleaning up middleware - -use aws_sig_auth::signer::SignableBody; -use aws_smithy_http::body::SdkBody; -use aws_smithy_http::byte_stream::{self, ByteStream}; -use aws_smithy_http::operation::Request; - -use bytes::Buf; -use bytes_utils::SegmentedBuf; -use http::header::HeaderName; -use ring::digest::{Context, Digest, SHA256}; - -const TREE_HASH_HEADER: &str = "x-amz-sha256-tree-hash"; -const X_AMZ_CONTENT_SHA256: &str = "x-amz-content-sha256"; - -/// Adds a glacier tree hash checksum to the HTTP Request -/// -/// This handles two cases: -/// 1. A body which is retryable: the body will be streamed through a digest calculator, limiting memory usage. -/// 2. A body which is not retryable: the body will be converted into `Bytes`, then streamed through a digest calculator. -/// -/// The actual checksum algorithm will first compute a SHA256 checksum for each 1MB chunk. Then, a tree -/// will be assembled, recursively pairing neighboring chunks and computing their combined checksum. The 1 leftover -/// chunk (if it exists) is paired at the end. -/// -/// See for more information. -pub async fn add_checksum_treehash(request: &mut Request) -> Result<(), byte_stream::error::Error> { - let cloneable = request.http().body().try_clone(); - let http_request = request.http_mut(); - let body_to_process = if let Some(cloned_body) = cloneable { - // we can stream the body - cloned_body - } else { - let body = std::mem::replace(http_request.body_mut(), SdkBody::taken()); - let loaded_body = ByteStream::new(body).collect().await?.into_bytes(); - *http_request.body_mut() = SdkBody::from(loaded_body.clone()); - SdkBody::from(loaded_body) - }; - let (full_body, hashes) = compute_hashes(body_to_process, MEGABYTE).await?; - let tree_hash = hex::encode(compute_hash_tree(hashes)); - let complete_hash = hex::encode(full_body); - if !http_request.headers().contains_key(TREE_HASH_HEADER) { - http_request.headers_mut().insert( - HeaderName::from_static(TREE_HASH_HEADER), - tree_hash.parse().expect("hash must be valid header"), - ); - } - if !http_request.headers().contains_key(X_AMZ_CONTENT_SHA256) { - http_request.headers_mut().insert( - HeaderName::from_static(X_AMZ_CONTENT_SHA256), - complete_hash.parse().expect("hash must be valid header"), - ); - } - // if we end up hitting the signer later, no need to recompute the checksum - request - .properties_mut() - .insert(SignableBody::Precomputed(complete_hash)); - // for convenience & protocol tests, write it in directly here as well - Ok(()) -} - -const MEGABYTE: usize = 1024 * 1024; -async fn compute_hashes( - body: SdkBody, - chunk_size: usize, -) -> Result<(Digest, Vec), byte_stream::error::Error> { - let mut hashes = vec![]; - let mut remaining_in_chunk = chunk_size; - let mut body = ByteStream::new(body); - let mut local = Context::new(&SHA256); - let mut full_body = Context::new(&SHA256); - let mut segmented = SegmentedBuf::new(); - while let Some(data) = body.try_next().await? { - segmented.push(data); - while segmented.has_remaining() { - let next = segmented.chunk(); - let len = next.len().min(remaining_in_chunk); - local.update(&next[..len]); - full_body.update(&next[..len]); - segmented.advance(len); - remaining_in_chunk -= len; - if remaining_in_chunk == 0 { - hashes.push(local.finish()); - local = Context::new(&SHA256); - remaining_in_chunk = chunk_size; - } - } - } - if remaining_in_chunk != chunk_size || hashes.is_empty() { - hashes.push(local.finish()); - } - Ok((full_body.finish(), hashes)) -} - -/// Compute the glacier tree hash for a vector of hashes. -/// -/// Adjacent hashes are combined into a single hash. This process occurs recursively until only 1 hash remains. -/// -/// See for more information. -fn compute_hash_tree(mut hashes: Vec) -> Digest { - assert!( - !hashes.is_empty(), - "even an empty file will produce a digest. this function assumes that hashes is non-empty" - ); - while hashes.len() > 1 { - let next = hashes.chunks(2).map(|chunk| match *chunk { - [left, right] => { - let mut ctx = Context::new(&SHA256); - ctx.update(left.as_ref()); - ctx.update(right.as_ref()); - ctx.finish() - } - [last] => last, - _ => unreachable!(), - }); - hashes = next.collect(); - } - hashes[0] -} - -#[cfg(test)] -mod test { - use crate::glacier_checksums::{ - add_checksum_treehash, compute_hash_tree, compute_hashes, MEGABYTE, TREE_HASH_HEADER, - }; - use aws_smithy_http::body::SdkBody; - use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_http::operation::Request; - - #[tokio::test] - async fn compute_digests() { - { - let body = SdkBody::from("1234"); - let hashes = compute_hashes(body, 1).await.expect("succeeds").1; - assert_eq!(hashes.len(), 4); - } - { - let body = SdkBody::from("1234"); - let hashes = compute_hashes(body, 2).await.expect("succeeds").1; - assert_eq!(hashes.len(), 2); - } - { - let body = SdkBody::from("12345"); - let hashes = compute_hashes(body, 3).await.expect("succeeds").1; - assert_eq!(hashes.len(), 2); - } - { - let body = SdkBody::from("11221122"); - let hashes = compute_hashes(body, 2).await.expect("succeeds").1; - assert_eq!(hashes[0].as_ref(), hashes[2].as_ref()); - } - } - - #[tokio::test] - async fn empty_body_computes_digest() { - let body = SdkBody::from(""); - let (_, hashes) = compute_hashes(body, 2).await.expect("succeeds"); - assert_eq!(hashes.len(), 1); - } - - #[tokio::test] - async fn compute_tree_digest() { - macro_rules! hash { - ($($inp:expr),*) => { - { - let mut ctx = ring::digest::Context::new(&ring::digest::SHA256); - $( - ctx.update($inp.as_ref()); - )* - ctx.finish() - } - } - } - let body = SdkBody::from("1234567891011"); - let (complete, hashes) = compute_hashes(body, 3).await.expect("succeeds"); - assert_eq!(hashes.len(), 5); - assert_eq!(complete.as_ref(), hash!("1234567891011").as_ref()); - let final_digest = compute_hash_tree(hashes); - let expected_digest = hash!( - hash!( - hash!(hash!("123"), hash!("456")), - hash!(hash!("789"), hash!("101")) - ), - hash!("1") - ); - assert_eq!(expected_digest.as_ref(), final_digest.as_ref()); - } - - #[tokio::test] - async fn integration_test() { - // the test data consists of an 11 byte sequence, repeated. Since the sequence length is - // relatively prime with 1 megabyte, we can ensure that chunks will all have different hashes. - let base_seq = b"01245678912"; - let total_size = MEGABYTE * 101 + 500; - let mut test_data = vec![]; - while test_data.len() < total_size { - test_data.extend_from_slice(base_seq) - } - let target = tempfile::NamedTempFile::new().unwrap(); - tokio::fs::write(target.path(), test_data).await.unwrap(); - let body = ByteStream::from_path(target.path()) - .await - .expect("should be valid") - .into_inner(); - - let mut http_req = Request::new( - http::Request::builder() - .uri("http://example.com/hello") - .body(body) - .unwrap(), - ); - - add_checksum_treehash(&mut http_req) - .await - .expect("should succeed"); - // hash value verified with AWS CLI - assert_eq!( - http_req.http().headers().get(TREE_HASH_HEADER).unwrap(), - "3d417484359fc9f5a3bafd576dc47b8b2de2bf2d4fdac5aa2aff768f2210d386" - ); - } -} diff --git a/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs b/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs deleted file mode 100644 index f71e8708e8..0000000000 --- a/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Functions for modifying requests and responses for the purposes of checksum validation - -use aws_smithy_http::operation::error::BuildError; - -/// Errors related to constructing checksum-validated HTTP requests -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum Error { - /// Only request bodies with a known size can be checksum validated - UnsizedRequestBody, - ChecksumHeadersAreUnsupportedForStreamingBody, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnsizedRequestBody => write!( - f, - "Only request bodies with a known size can be checksum validated." - ), - Self::ChecksumHeadersAreUnsupportedForStreamingBody => write!( - f, - "Checksum header insertion is only supported for non-streaming HTTP bodies. \ - To checksum validate a streaming body, the checksums must be sent as trailers." - ), - } - } -} - -impl std::error::Error for Error {} - -/// Given a `&mut http::request::Request` and a `aws_smithy_checksums::ChecksumAlgorithm`, -/// calculate a checksum and modify the request to include the checksum as a header -/// (for in-memory request bodies) or a trailer (for streaming request bodies.) Streaming bodies -/// must be sized or this will return an error. -#[allow(dead_code)] -pub(crate) fn add_checksum_calculation_to_request( - request: &mut http::request::Request, - property_bag: &mut aws_smithy_http::property_bag::PropertyBag, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, -) -> Result<(), BuildError> { - match request.body().bytes() { - // Body is in-memory: read it and insert the checksum as a header. - Some(data) => { - let mut checksum = checksum_algorithm.into_impl(); - checksum.update(data); - - request - .headers_mut() - .insert(checksum.header_name(), checksum.header_value()); - } - // Body is streaming: wrap the body so it will emit a checksum as a trailer. - None => { - wrap_streaming_request_body_in_checksum_calculating_body( - request, - property_bag, - checksum_algorithm, - )?; - } - } - - Ok(()) -} - -#[allow(dead_code)] -fn wrap_streaming_request_body_in_checksum_calculating_body( - request: &mut http::request::Request, - property_bag: &mut aws_smithy_http::property_bag::PropertyBag, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, -) -> Result<(), BuildError> { - use aws_http::content_encoding::{AwsChunkedBody, AwsChunkedBodyOptions}; - use aws_smithy_checksums::{body::calculate, http::HttpChecksum}; - use http_body::Body; - - let original_body_size = request - .body() - .size_hint() - .exact() - .ok_or_else(|| BuildError::other(Error::UnsizedRequestBody))?; - - // Streaming request bodies with trailers require special signing - property_bag.insert(aws_sig_auth::signer::SignableBody::StreamingUnsignedPayloadTrailer); - - let mut body = { - let body = std::mem::replace(request.body_mut(), aws_smithy_http::body::SdkBody::taken()); - - body.map(move |body| { - let checksum = checksum_algorithm.into_impl(); - let trailer_len = HttpChecksum::size(checksum.as_ref()); - let body = calculate::ChecksumBody::new(body, checksum); - let aws_chunked_body_options = - AwsChunkedBodyOptions::new(original_body_size, vec![trailer_len]); - - let body = AwsChunkedBody::new(body, aws_chunked_body_options); - - aws_smithy_http::body::SdkBody::from_dyn(aws_smithy_http::body::BoxBody::new(body)) - }) - }; - - let encoded_content_length = body - .size_hint() - .exact() - .ok_or_else(|| BuildError::other(Error::UnsizedRequestBody))?; - - let headers = request.headers_mut(); - - headers.insert( - http::header::HeaderName::from_static("x-amz-trailer"), - // Convert into a `HeaderName` and then into a `HeaderValue` - http::header::HeaderName::from(checksum_algorithm).into(), - ); - - headers.insert( - http::header::CONTENT_LENGTH, - http::HeaderValue::from(encoded_content_length), - ); - headers.insert( - http::header::HeaderName::from_static("x-amz-decoded-content-length"), - http::HeaderValue::from(original_body_size), - ); - headers.insert( - http::header::CONTENT_ENCODING, - http::HeaderValue::from_str(aws_http::content_encoding::header_value::AWS_CHUNKED) - .map_err(BuildError::other) - .expect("\"aws-chunked\" will always be a valid HeaderValue"), - ); - - std::mem::swap(request.body_mut(), &mut body); - - Ok(()) -} - -/// Given an `SdkBody`, a `aws_smithy_checksums::ChecksumAlgorithm`, and a pre-calculated checksum, -/// return an `SdkBody` where the body will processed with the checksum algorithm and checked -/// against the pre-calculated checksum. -#[allow(dead_code)] -pub(crate) fn wrap_body_with_checksum_validator( - body: aws_smithy_http::body::SdkBody, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, - precalculated_checksum: bytes::Bytes, -) -> aws_smithy_http::body::SdkBody { - use aws_smithy_checksums::body::validate; - use aws_smithy_http::body::{BoxBody, SdkBody}; - - body.map(move |body| { - SdkBody::from_dyn(BoxBody::new(validate::ChecksumBody::new( - body, - checksum_algorithm.into_impl(), - precalculated_checksum.clone(), - ))) - }) -} - -/// Given a `HeaderMap`, extract any checksum included in the headers as `Some(Bytes)`. -/// If no checksum header is set, return `None`. If multiple checksum headers are set, the one that -/// is fastest to compute will be chosen. -#[allow(dead_code)] -pub(crate) fn check_headers_for_precalculated_checksum( - headers: &http::HeaderMap, - response_algorithms: &[&str], -) -> Option<(aws_smithy_checksums::ChecksumAlgorithm, bytes::Bytes)> { - let checksum_algorithms_to_check = - aws_smithy_checksums::http::CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER - .into_iter() - // Process list of algorithms, from fastest to slowest, that may have been used to checksum - // the response body, ignoring any that aren't marked as supported algorithms by the model. - .flat_map(|algo| { - // For loop is necessary b/c the compiler doesn't infer the correct lifetimes for iter().find() - for res_algo in response_algorithms { - if algo.eq_ignore_ascii_case(res_algo) { - return Some(algo); - } - } - - None - }); - - for checksum_algorithm in checksum_algorithms_to_check { - let checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm = checksum_algorithm.parse().expect( - "CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER only contains valid checksum algorithm names", - ); - if let Some(precalculated_checksum) = - headers.get(http::HeaderName::from(checksum_algorithm)) - { - let base64_encoded_precalculated_checksum = precalculated_checksum - .to_str() - .expect("base64 uses ASCII characters"); - - // S3 needs special handling for checksums of objects uploaded with `MultiPartUpload`. - if is_part_level_checksum(base64_encoded_precalculated_checksum) { - tracing::warn!( - more_info = "See https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#large-object-checksums for more information.", - "This checksum is a part-level checksum which can't be validated by the Rust SDK. Disable checksum validation for this request to fix this warning.", - ); - - return None; - } - - let precalculated_checksum = match aws_smithy_types::base64::decode( - base64_encoded_precalculated_checksum, - ) { - Ok(decoded_checksum) => decoded_checksum.into(), - Err(_) => { - tracing::error!("Checksum received from server could not be base64 decoded. No checksum validation will be performed."); - return None; - } - }; - - return Some((checksum_algorithm, precalculated_checksum)); - } - } - - None -} - -fn is_part_level_checksum(checksum: &str) -> bool { - let mut found_number = false; - let mut found_dash = false; - - for ch in checksum.chars().rev() { - // this could be bad - if ch.is_ascii_digit() { - found_number = true; - continue; - } - - // Yup, it's a part-level checksum - if ch == '-' { - if found_dash { - // Found a second dash?? This isn't a part-level checksum. - return false; - } - - found_dash = true; - continue; - } - - break; - } - - found_number && found_dash -} - -#[cfg(test)] -mod tests { - use super::{is_part_level_checksum, wrap_body_with_checksum_validator}; - use aws_smithy_checksums::ChecksumAlgorithm; - use aws_smithy_http::body::SdkBody; - use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_types::error::display::DisplayErrorContext; - use bytes::{Bytes, BytesMut}; - use http_body::Body; - use std::sync::Once; - use tempfile::NamedTempFile; - - static INIT_LOGGER: Once = Once::new(); - fn init_logger() { - INIT_LOGGER.call_once(|| { - tracing_subscriber::fmt::init(); - }); - } - - #[tokio::test] - async fn test_checksum_body_is_retryable() { - let input_text = "Hello world"; - let precalculated_checksum = Bytes::from_static(&[0x8b, 0xd6, 0x9e, 0x52]); - let body = SdkBody::retryable(move || SdkBody::from(input_text)); - - // ensure original SdkBody is retryable - assert!(body.try_clone().is_some()); - - let body = body.map(move |sdk_body| { - let checksum_algorithm: ChecksumAlgorithm = "crc32".parse().unwrap(); - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - // ensure wrapped SdkBody is retryable - let mut body = body.try_clone().expect("body is retryable"); - - let mut validated_body = BytesMut::new(); - - loop { - match body.data().await { - Some(Ok(data)) => validated_body.extend_from_slice(&data), - Some(Err(err)) => panic!("{}", err), - None => { - break; - } - } - } - - let body = std::str::from_utf8(&validated_body).unwrap(); - - // ensure that the wrapped body passes checksum validation - assert_eq!(input_text, body); - } - - #[tokio::test] - async fn test_checksum_body_from_file_is_retryable() { - use std::io::Write; - let mut file = NamedTempFile::new().unwrap(); - let checksum_algorithm: ChecksumAlgorithm = "crc32c".parse().unwrap(); - let mut crc32c_checksum = checksum_algorithm.into_impl(); - - for i in 0..10000 { - let line = format!("This is a large file created for testing purposes {}", i); - file.as_file_mut().write_all(line.as_bytes()).unwrap(); - crc32c_checksum.update(line.as_bytes()); - } - - let body = ByteStream::read_from() - .path(&file) - .buffer_size(1024) - .build() - .await - .unwrap(); - - let precalculated_checksum = crc32c_checksum.finalize(); - let expected_checksum = precalculated_checksum.clone(); - - let body = body.map(move |sdk_body| { - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - // ensure wrapped SdkBody is retryable - let mut body = body.into_inner().try_clone().expect("body is retryable"); - - let mut validated_body = BytesMut::new(); - - // If this loop completes, then it means the body's checksum was valid, but let's calculate - // a checksum again just in case. - let mut redundant_crc32c_checksum = checksum_algorithm.into_impl(); - loop { - match body.data().await { - Some(Ok(data)) => { - redundant_crc32c_checksum.update(&data); - validated_body.extend_from_slice(&data); - } - Some(Err(err)) => panic!("{}", err), - None => { - break; - } - } - } - - let actual_checksum = redundant_crc32c_checksum.finalize(); - assert_eq!(expected_checksum, actual_checksum); - - // Ensure the file's checksum isn't the same as an empty checksum. This way, we'll know that - // data was actually processed. - let unexpected_checksum = checksum_algorithm.into_impl().finalize(); - assert_ne!(unexpected_checksum, actual_checksum); - } - - #[tokio::test] - async fn test_build_checksum_validated_body_works() { - init_logger(); - - let checksum_algorithm = "crc32".parse().unwrap(); - let input_text = "Hello world"; - let precalculated_checksum = Bytes::from_static(&[0x8b, 0xd6, 0x9e, 0x52]); - let body = ByteStream::new(SdkBody::from(input_text)); - - let body = body.map(move |sdk_body| { - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - let mut validated_body = Vec::new(); - if let Err(e) = tokio::io::copy(&mut body.into_async_read(), &mut validated_body).await { - tracing::error!("{}", DisplayErrorContext(&e)); - panic!("checksum validation has failed"); - }; - let body = std::str::from_utf8(&validated_body).unwrap(); - - assert_eq!(input_text, body); - } - - #[test] - fn test_is_multipart_object_checksum() { - // These ARE NOT part-level checksums - assert!(!is_part_level_checksum("abcd")); - assert!(!is_part_level_checksum("abcd=")); - assert!(!is_part_level_checksum("abcd==")); - assert!(!is_part_level_checksum("1234")); - assert!(!is_part_level_checksum("1234=")); - assert!(!is_part_level_checksum("1234==")); - // These ARE part-level checksums - assert!(is_part_level_checksum("abcd-1")); - assert!(is_part_level_checksum("abcd=-12")); - assert!(is_part_level_checksum("abcd12-134")); - assert!(is_part_level_checksum("abcd==-10000")); - // These are gibberish and shouldn't be regarded as a part-level checksum - assert!(!is_part_level_checksum("")); - assert!(!is_part_level_checksum("Spaces? In my header values?")); - assert!(!is_part_level_checksum("abcd==-134!#{!#")); - assert!(!is_part_level_checksum("abcd==-")); - assert!(!is_part_level_checksum("abcd==--11")); - assert!(!is_part_level_checksum("abcd==-AA")); - } -} diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs index 591d95741d..8ff2f5f478 100644 --- a/aws/rust-runtime/aws-inlineable/src/lib.rs +++ b/aws/rust-runtime/aws-inlineable/src/lib.rs @@ -34,9 +34,6 @@ pub mod presigning_interceptors; /// Special logic for extracting request IDs from S3's responses. pub mod s3_request_id; -/// Glacier-specific checksumming behavior -pub mod glacier_checksums; - /// Glacier-specific behavior pub mod glacier_interceptors; @@ -49,10 +46,6 @@ pub mod route53_resource_id_preprocessor; pub mod http_request_checksum; pub mod http_response_checksum; -// TODO(enableNewSmithyRuntimeCleanup): Delete this module -/// Convert a streaming `SdkBody` into an aws-chunked streaming body with checksum trailers -pub mod http_body_checksum_middleware; - #[allow(dead_code)] pub mod endpoint_discovery; diff --git a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs index 057b659c2e..d2e64a7038 100644 --- a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs +++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs @@ -101,8 +101,8 @@ fn extract_extended_request_id(headers: &HeaderMap) -> Option<&str> #[cfg(test)] mod test { use super::*; - use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; + use aws_smithy_http::result::SdkError; use http::Response; #[test] 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 95553d8006..e6d4a3f442 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 @@ -84,17 +84,14 @@ class IntegrationTestDependencies( if (hasTests) { val smithyAsync = CargoDependency.smithyAsync(codegenContext.runtimeConfig) .copy(features = setOf("test-util"), scope = DependencyScope.Dev) - val smithyClient = CargoDependency.smithyClient(codegenContext.runtimeConfig) - .copy(features = setOf("test-util", "wiremock"), scope = DependencyScope.Dev) val smithyTypes = CargoDependency.smithyTypes(codegenContext.runtimeConfig) .copy(features = setOf("test-util"), scope = DependencyScope.Dev) addDependency(awsRuntime(runtimeConfig).toDevDependency().withFeature("test-util")) addDependency(FuturesUtil) addDependency(SerdeJson) addDependency(smithyAsync) - addDependency(smithyClient) addDependency(smithyProtocolTestHelpers(codegenContext.runtimeConfig)) - addDependency(smithyRuntime(runtimeConfig).copy(features = setOf("test-util"), scope = DependencyScope.Dev)) + addDependency(smithyRuntime(runtimeConfig).copy(features = setOf("test-util", "wire-mock"), scope = DependencyScope.Dev)) addDependency(smithyRuntimeApi(runtimeConfig).copy(features = setOf("test-util"), scope = DependencyScope.Dev)) addDependency(smithyTypes) addDependency(Tokio) 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 1ebc8049ef..ab311abc6a 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 @@ -46,6 +46,14 @@ private class HttpConnectorConfigCustomization( is ServiceConfig.ConfigImpl -> writable { rustTemplate( """ + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(&self) -> Option<#{SharedHttpClient}> { + self.runtime_components.http_client() + } + /// Return the [`SharedHttpClient`](#{SharedHttpClient}) to use when making requests, if any. pub fn http_client(&self) -> Option<#{SharedHttpClient}> { self.runtime_components.http_client() @@ -58,6 +66,22 @@ private class HttpConnectorConfigCustomization( ServiceConfig.BuilderImpl -> writable { rustTemplate( """ + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(self, http_client: impl #{IntoShared}<#{SharedHttpClient}>) -> Self { + self.http_client(http_client) + } + + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn set_http_connector(&mut self, http_client: Option<#{SharedHttpClient}>) -> &mut Self { + self.set_http_client(http_client) + } + /// Sets the HTTP client to use when making requests. /// /// ## Examples @@ -121,11 +145,6 @@ private class HttpConnectorConfigCustomization( /// ## } /// ## } /// ``` - """, - *codegenScope, - ) - rustTemplate( - """ pub fn set_http_client(&mut self, http_client: Option<#{SharedHttpClient}>) -> &mut Self { self.runtime_components.set_http_client(http_client); self From 671e9c87641a2a679cec57ddc0350bb071b40edb Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 4 Oct 2023 15:17:42 -0700 Subject: [PATCH 15/23] Retain old with_tcp_connector method as a hint for upgrade guidance --- aws/rust-runtime/aws-config/src/provider_config.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index 4d17f4f4d5..eba3dc2c59 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -348,6 +348,14 @@ impl ProviderConfig { } } + /// Deprecated. Don't use. + #[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn with_tcp_connector(self, http_client: impl IntoShared) -> Self { + self.with_http_client(http_client) + } + /// Override the HTTP client for this configuration /// /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) From e487a47d06e0a12eb220186ee5e80a7188d679d8 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 4 Oct 2023 15:40:15 -0700 Subject: [PATCH 16/23] Other feedback --- .../aws-config/src/imds/client.rs | 28 +++++++++---------- .../aws-config/src/imds/credentials.rs | 6 ++-- .../aws-smithy-async/src/test_util.rs | 7 +++++ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index bb0eb06c95..0c9ac3b28f 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -582,7 +582,7 @@ pub(crate) mod test { use crate::imds::client::{Client, EndpointMode, ImdsResponseRetryClassifier}; use crate::provider_config::ProviderConfig; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_async::test_util::instant_time_and_sleep; + use aws_smithy_async::test_util::{instant_time_and_sleep, InstantSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_runtime::client::http::test_util::{ @@ -658,26 +658,26 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn make_client(http_client: &StaticReplayClient) -> super::Client { + pub(crate) fn make_imds_client(http_client: &StaticReplayClient) -> super::Client { tokio::time::pause(); super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_sleep_impl(TokioSleep::new()) + .with_sleep_impl(InstantSleep::unlogged()) .with_http_client(http_client.clone()), ) .build() } - fn event_client(events: Vec) -> (Client, StaticReplayClient) { + fn mock_imds_client(events: Vec) -> (Client, StaticReplayClient) { let http_client = StaticReplayClient::new(events); - let client = make_client(&http_client); + let client = make_imds_client(&http_client); (client, http_client) } #[tokio::test] async fn client_caches_token() { - let (client, http_client) = event_client(vec![ + let (client, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), @@ -702,7 +702,7 @@ pub(crate) mod test { #[tokio::test] async fn token_can_expire() { - let (_, http_client) = event_client(vec![ + let (_, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), @@ -744,7 +744,7 @@ pub(crate) mod test { /// Tokens are refreshed up to 120 seconds early to avoid using an expired token. #[tokio::test] async fn token_refresh_buffer() { - let (_, http_client) = event_client(vec![ + let (_, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), @@ -797,7 +797,7 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_500() { - let (client, http_client) = event_client(vec![ + let (client, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), @@ -834,7 +834,7 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_token_failure() { - let (client, http_client) = event_client(vec![ + let (client, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://169.254.169.254", 21600), http::Response::builder() @@ -866,7 +866,7 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_metadata_401() { - let (client, http_client) = event_client(vec![ + let (client, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(0, TOKEN_A), @@ -902,7 +902,7 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn no_403_retry() { - let (client, http_client) = event_client(vec![ReplayEvent::new( + let (client, http_client) = mock_imds_client(vec![ReplayEvent::new( token_request("http://169.254.169.254", 21600), http::Response::builder() .status(403) @@ -934,7 +934,7 @@ pub(crate) mod test { // since tokens are sent as headers, the tokens need to be valid header values #[tokio::test] async fn invalid_token() { - let (client, http_client) = event_client(vec![ReplayEvent::new( + let (client, http_client) = mock_imds_client(vec![ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, "invalid\nheader\nvalue\0"), )]); @@ -945,7 +945,7 @@ pub(crate) mod test { #[tokio::test] async fn non_utf8_response() { - let (client, http_client) = event_client(vec![ + let (client, http_client) = mock_imds_client(vec![ ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A).map(SdkBody::from), diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index b84ae7b066..52cf0bb6af 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -284,7 +284,7 @@ impl ImdsCredentialsProvider { mod test { use super::*; use crate::imds::client::test::{ - imds_request, imds_response, make_client, token_request, token_response, + imds_request, imds_response, make_imds_client, token_request, token_response, }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; @@ -321,7 +321,7 @@ mod test { ), ]); let client = ImdsCredentialsProvider::builder() - .imds_client(make_client(&http_client)) + .imds_client(make_imds_client(&http_client)) .build(); let creds1 = client.provide_credentials().await.expect("valid creds"); let creds2 = client.provide_credentials().await.expect("valid creds"); @@ -509,7 +509,7 @@ mod test { ), ]); let provider = ImdsCredentialsProvider::builder() - .imds_client(make_client(&http_client)) + .imds_client(make_imds_client(&http_client)) .build(); let creds1 = provider.provide_credentials().await.expect("valid creds"); assert_eq!(creds1.access_key_id(), "ASIARTEST"); diff --git a/rust-runtime/aws-smithy-async/src/test_util.rs b/rust-runtime/aws-smithy-async/src/test_util.rs index e6128b76b3..dd7eacfd89 100644 --- a/rust-runtime/aws-smithy-async/src/test_util.rs +++ b/rust-runtime/aws-smithy-async/src/test_util.rs @@ -138,6 +138,13 @@ impl InstantSleep { Self { log } } + /// Create an `InstantSleep` without passing in a shared log. + pub fn unlogged() -> Self { + Self { + log: Default::default(), + } + } + /// Return the sleep durations that were logged by this `InstantSleep`. pub fn logs(&self) -> Vec { self.log.lock().unwrap().iter().cloned().collect() From 370d0ff3afbc3ea509dc822215fb6407bc407d5c Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 4 Oct 2023 16:27:17 -0700 Subject: [PATCH 17/23] Add changelog entries --- CHANGELOG.next.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index d9ed616628..53ee76961a 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -11,6 +11,18 @@ # meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"} # author = "rcoh" +[[smithy-rs]] +message = "HTTP connector configuration has changed significantly. See the [upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3022) for details." +references = ["smithy-rs#3011"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[aws-sdk-rust]] +message = "HTTP connector configuration has changed significantly. See the [upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3022) for details." +references = ["smithy-rs#3011"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + [[smithy-rs]] message = "It's now possible to nest runtime components with the `RuntimePlugin` trait. A `current_components` argument was added to the `runtime_components` method so that components configured from previous runtime plugins can be referenced in the current runtime plugin. Ordering of runtime plugins was also introduced via a new `RuntimePlugin::order` method." references = ["smithy-rs#2909"] From 9386c25b5a3b2b88ae5e2fc91e1dbb643295f262 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 5 Oct 2023 12:25:42 -0700 Subject: [PATCH 18/23] Remove `IntoShared` argument wrappers --- .../aws-config/external-types.toml | 1 - aws/rust-runtime/aws-config/src/ecs.rs | 12 +- aws/rust-runtime/aws-config/src/lib.rs | 21 +-- .../aws-config/src/provider_config.rs | 21 +-- .../aws-credential-types/external-types.toml | 1 - .../src/cache/lazy_caching.rs | 14 +- .../aws-types/external-types.toml | 1 - aws/rust-runtime/aws-types/src/sdk_config.rs | 12 +- .../dynamodb/tests/paginators.rs | 2 +- .../integration-tests/ec2/tests/paginators.rs | 2 +- .../HttpConnectorConfigDecorator.kt | 5 +- .../ResiliencyConfigCustomization.kt | 5 +- .../customizations/TimeSourceCustomization.kt | 5 +- .../src/client/runtime_components.rs | 131 ++++-------------- .../src/client/runtime_plugin.rs | 20 +-- .../aws-smithy-runtime-api/src/shared.rs | 11 +- .../src/client/http/hyper_014.rs | 7 +- .../src/client/http/test_util/dvr/record.rs | 5 +- .../src/client/orchestrator/operation.rs | 20 +-- 19 files changed, 93 insertions(+), 203 deletions(-) diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index 38d857870c..6a223eafdd 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -18,7 +18,6 @@ allowed_external_types = [ "aws_smithy_http::result::SdkError", "aws_smithy_runtime_api::client::dns::SharedDnsResolver", "aws_smithy_runtime_api::client::http::SharedHttpClient", - "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::retry", "aws_smithy_types::retry::*", "aws_smithy_types::timeout", diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index 757b0547d2..ef63015e69 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -274,10 +274,7 @@ impl Builder { /// /// URIs must refer to loopback addresses. The [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) /// is used to retrieve IP addresses for a given domain. - /// - /// Takes an implementation of [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn dns(mut self, dns: impl IntoShared) -> Self { + pub fn dns(mut self, dns: impl DnsResolver + 'static) -> Self { self.dns = Some(dns.into_shared()); self } @@ -448,9 +445,9 @@ mod test { use aws_smithy_http::body::SdkBody; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime_api::client::dns::DnsFuture; + use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::shared::IntoShared; use aws_types::os_shim_internal::Env; - use aws_types::sdk_config::SharedHttpClient; use futures_util::FutureExt; use http::header::AUTHORIZATION; use http::Uri; @@ -461,10 +458,7 @@ mod test { use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; - fn provider( - env: Env, - http_client: impl IntoShared, - ) -> EcsCredentialsProvider { + fn provider(env: Env, http_client: impl HttpClient + 'static) -> EcsCredentialsProvider { let provider_config = ProviderConfig::empty() .with_env(env) .with_http_client(http_client) diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index ff25472e51..4edec7d839 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -157,8 +157,10 @@ mod loader { use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; + use aws_sdk_sso::config::AsyncSleep; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; - use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; + use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; @@ -272,10 +274,7 @@ mod loader { /// The sleep implementation is used to create timeout futures. /// You generally won't need to change this unless you're using an async runtime other /// than Tokio. - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn sleep_impl(mut self, sleep: impl IntoShared) -> Self { + pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self { // it's possible that we could wrapping an `Arc in an `Arc` and that's OK self.sleep = Some(sleep.into_shared()); self @@ -286,10 +285,7 @@ mod loader { /// You generally won't need to change this unless you're compiling for a target /// that can't provide a default, such as WASM, or unless you're writing a test against /// the client that needs a fixed time. - /// - /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { self.time_source = Some(time_source.into_shared()); self } @@ -298,7 +294,7 @@ mod loader { #[deprecated( note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." )] - pub fn http_connector(self, http_client: impl IntoShared) -> Self { + pub fn http_connector(self, http_client: impl HttpClient + 'static) -> Self { self.http_client(http_client) } @@ -309,9 +305,6 @@ mod loader { /// If you wish to use a separate HTTP client for credentials providers when creating clients, /// then override the HTTP client set with this function on the client-specific `Config`s. /// - /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) - /// as an argument. All implementations of this trait implement `IntoShared`. - /// /// ## Examples /// /// ```no_run @@ -336,7 +329,7 @@ mod loader { /// .await; /// # } /// ``` - pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self { self.http_client = Some(http_client.into_shared()); self } diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index eba3dc2c59..7bc85ccb8d 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -8,8 +8,10 @@ use crate::profile; use crate::profile::profile_file::ProfileFiles; use crate::profile::{ProfileFileLoadError, ProfileSet}; +use aws_sdk_sso::config::AsyncSleep; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; -use aws_smithy_async::time::SharedTimeSource; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::retry::RetryConfig; @@ -338,10 +340,7 @@ impl ProviderConfig { } /// Override the time source for this configuration - /// - /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_time_source(self, time_source: impl IntoShared) -> Self { + pub fn with_time_source(self, time_source: impl TimeSource + 'static) -> Self { ProviderConfig { time_source: time_source.into_shared(), ..self @@ -352,15 +351,12 @@ impl ProviderConfig { #[deprecated( note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." )] - pub fn with_tcp_connector(self, http_client: impl IntoShared) -> Self { + pub fn with_tcp_connector(self, http_client: impl HttpClient + 'static) -> Self { self.with_http_client(http_client) } /// Override the HTTP client for this configuration - /// - /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_http_client(self, http_client: impl IntoShared) -> Self { + pub fn with_http_client(self, http_client: impl HttpClient + 'static) -> Self { ProviderConfig { http_client: Some(http_client.into_shared()), ..self @@ -368,10 +364,7 @@ impl ProviderConfig { } /// Override the sleep implementation for this configuration - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_sleep_impl(self, sleep_impl: impl IntoShared) -> Self { + pub fn with_sleep_impl(self, sleep_impl: impl AsyncSleep + 'static) -> Self { ProviderConfig { sleep_impl: Some(sleep_impl.into_shared()), ..self diff --git a/aws/rust-runtime/aws-credential-types/external-types.toml b/aws/rust-runtime/aws-credential-types/external-types.toml index ec66391fc1..e65c743b10 100644 --- a/aws/rust-runtime/aws-credential-types/external-types.toml +++ b/aws/rust-runtime/aws-credential-types/external-types.toml @@ -1,6 +1,5 @@ allowed_external_types = [ "aws_smithy_async::rt::sleep::SharedAsyncSleep", - "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", "aws_smithy_types::config_bag::storable::Storer", diff --git a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs index 0ec98aefca..d919278db8 100644 --- a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs @@ -136,8 +136,8 @@ mod builder { use crate::cache::{CredentialsCache, Inner}; use crate::provider::SharedCredentialsProvider; - use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; - use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use super::{ LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_BUFFER_TIME_JITTER_FRACTION, @@ -178,10 +178,7 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self { self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -191,16 +188,13 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { self.sleep_impl = sleep_impl; self } #[doc(hidden)] // because they only exist for tests - pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { self.set_time_source(Some(time_source.into_shared())); self } diff --git a/aws/rust-runtime/aws-types/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index 3e415f1084..1b2eb6ab5d 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -5,7 +5,6 @@ allowed_external_types = [ "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", "aws_smithy_runtime_api::client::http::SharedHttpClient", - "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", "aws_smithy_types::config_bag::storable::Storer", diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index f92e6f3e34..e97a58cc3d 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -15,8 +15,10 @@ use crate::region::Region; pub use aws_credential_types::cache::CredentialsCache; pub use aws_credential_types::provider::SharedCredentialsProvider; +use aws_smithy_async::rt::sleep::AsyncSleep; pub use aws_smithy_async::rt::sleep::SharedAsyncSleep; pub use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +use aws_smithy_runtime_api::client::http::HttpClient; pub use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::shared::IntoShared; pub use aws_smithy_types::retry::RetryConfig; @@ -235,9 +237,6 @@ impl Builder { /// /// The sleep implementation is used to create timeout futures. /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - /// /// _Note:_ If you're using the Tokio runtime, a `TokioSleep` implementation is available in /// the `aws-smithy-async` crate. /// @@ -259,7 +258,7 @@ impl Builder { /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = SdkConfig::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self { self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -406,9 +405,6 @@ impl Builder { /// Sets the HTTP client to use when making requests. /// - /// Takes an implementation of [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) - /// as an argument. All implementations of this trait implement `IntoShared`. - /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] @@ -439,7 +435,7 @@ impl Builder { /// .build(); /// # } /// ``` - pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self { self.set_http_client(Some(http_client.into_shared())); self } diff --git a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs index 2a129a7d6b..54f441df91 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs @@ -18,7 +18,7 @@ use aws_smithy_runtime_api::shared::IntoShared; use aws_types::region::Region; use aws_types::sdk_config::SharedHttpClient; -fn stub_config(http_client: impl IntoShared) -> Config { +fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) diff --git a/aws/sdk/integration-tests/ec2/tests/paginators.rs b/aws/sdk/integration-tests/ec2/tests/paginators.rs index 42c1bc6c31..25b4e42cee 100644 --- a/aws/sdk/integration-tests/ec2/tests/paginators.rs +++ b/aws/sdk/integration-tests/ec2/tests/paginators.rs @@ -9,7 +9,7 @@ use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClien use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::shared::IntoShared; -fn stub_config(http_client: impl IntoShared) -> Config { +fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) 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 ab311abc6a..91f08b80be 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 @@ -34,6 +34,7 @@ private class HttpConnectorConfigCustomization( *preludeScope, "Connection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::Connection"), "ConnectorSettings" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::ConnectorSettings"), + "HttpClient" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::http::HttpClient"), "IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"), "Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"), "SharedAsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::SharedAsyncSleep"), @@ -70,7 +71,7 @@ private class HttpConnectorConfigCustomization( ##[deprecated( note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." )] - pub fn http_connector(self, http_client: impl #{IntoShared}<#{SharedHttpClient}>) -> Self { + pub fn http_connector(self, http_client: impl #{HttpClient} + 'static) -> Self { self.http_client(http_client) } @@ -111,7 +112,7 @@ private class HttpConnectorConfigCustomization( /// ## } /// ## } /// ``` - pub fn http_client(mut self, http_client: impl #{IntoShared}<#{SharedHttpClient}>) -> Self { + pub fn http_client(mut self, http_client: impl #{HttpClient} + 'static) -> Self { self.set_http_client(#{Some}(#{IntoShared}::into_shared(http_client))); self } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt index 799aff043d..6f588f11ac 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt @@ -29,6 +29,7 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon private val moduleUseName = codegenContext.moduleUseName() private val codegenScope = arrayOf( *preludeScope, + "AsyncSleep" to sleepModule.resolve("AsyncSleep"), "ClientRateLimiter" to retries.resolve("ClientRateLimiter"), "ClientRateLimiterPartition" to retries.resolve("ClientRateLimiterPartition"), "debug" to RuntimeType.Tracing.resolve("debug"), @@ -151,8 +152,8 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = Config::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: impl #{IntoShared}<#{SharedAsyncSleep}>) -> Self { - self.set_sleep_impl(Some(sleep_impl.into_shared())); + pub fn sleep_impl(mut self, sleep_impl: impl #{AsyncSleep} + 'static) -> Self { + self.set_sleep_impl(Some(#{IntoShared}::into_shared(sleep_impl))); self } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt index 4930e0e45e..67b3664688 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt @@ -21,6 +21,7 @@ class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCust "IntoShared" to RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig).resolve("shared::IntoShared"), "SharedTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::SharedTimeSource"), "StaticTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::StaticTimeSource"), + "TimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::TimeSource"), "UNIX_EPOCH" to RuntimeType.std.resolve("time::UNIX_EPOCH"), "Duration" to RuntimeType.std.resolve("time::Duration"), ) @@ -47,9 +48,9 @@ class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCust /// Sets the time source used for this service pub fn time_source( mut self, - time_source: impl #{IntoShared}<#{SharedTimeSource}>, + time_source: impl #{TimeSource} + 'static, ) -> Self { - self.set_time_source(#{Some}(time_source.into_shared())); + self.set_time_source(#{Some}(#{IntoShared}::into_shared(time_source))); self } """, diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs index 6f6e5c7b05..0f12d407a5 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs @@ -12,16 +12,19 @@ //! [`RuntimeComponents`](RuntimeComponents). use crate::client::auth::{ - AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver, + AuthScheme, AuthSchemeId, AuthSchemeOptionResolver, SharedAuthScheme, + SharedAuthSchemeOptionResolver, }; -use crate::client::endpoint::SharedEndpointResolver; -use crate::client::http::SharedHttpClient; -use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver}; -use crate::client::interceptors::SharedInterceptor; -use crate::client::retries::{RetryClassifiers, SharedRetryStrategy}; +use crate::client::endpoint::{EndpointResolver, SharedEndpointResolver}; +use crate::client::http::{HttpClient, SharedHttpClient}; +use crate::client::identity::{ + ConfiguredIdentityResolver, IdentityResolver, SharedIdentityResolver, +}; +use crate::client::interceptors::{Interceptor, SharedInterceptor}; +use crate::client::retries::{RetryClassifiers, RetryStrategy, SharedRetryStrategy}; use crate::shared::IntoShared; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::SharedTimeSource; +use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use std::fmt; pub(crate) static EMPTY_RUNTIME_COMPONENTS_BUILDER: RuntimeComponentsBuilder = @@ -272,12 +275,9 @@ impl RuntimeComponentsBuilder { } /// Sets the auth scheme option resolver. - /// - /// Takes an implementation of [`AuthSchemeOptionResolver`](crate::client::auth::AuthSchemeOptionResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_auth_scheme_option_resolver( &mut self, - auth_scheme_option_resolver: Option>, + auth_scheme_option_resolver: Option, ) -> &mut Self { self.auth_scheme_option_resolver = auth_scheme_option_resolver.map(|r| Tracked::new(self.builder_name, r.into_shared())); @@ -285,12 +285,9 @@ impl RuntimeComponentsBuilder { } /// Sets the auth scheme option resolver. - /// - /// Takes an implementation of [`AuthSchemeOptionResolver`](crate::client::auth::AuthSchemeOptionResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_auth_scheme_option_resolver( mut self, - auth_scheme_option_resolver: Option>, + auth_scheme_option_resolver: Option, ) -> Self { self.set_auth_scheme_option_resolver(auth_scheme_option_resolver); self @@ -302,25 +299,13 @@ impl RuntimeComponentsBuilder { } /// Sets the HTTP client. - /// - /// Takes an implementation of [`HttpClient`](crate::client::http::HttpClient) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn set_http_client( - &mut self, - connector: Option>, - ) -> &mut Self { + pub fn set_http_client(&mut self, connector: Option) -> &mut Self { self.http_client = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); self } /// Sets the HTTP client. - /// - /// Takes an implementation of [`HttpClient`](crate::client::http::HttpClient) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_http_client( - mut self, - connector: Option>, - ) -> Self { + pub fn with_http_client(mut self, connector: Option) -> Self { self.set_http_client(connector); self } @@ -331,12 +316,9 @@ impl RuntimeComponentsBuilder { } /// Sets the endpoint resolver. - /// - /// Takes an implementation of [`EndpointResolver`](crate::client::endpoint::EndpointResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_endpoint_resolver( &mut self, - endpoint_resolver: Option>, + endpoint_resolver: Option, ) -> &mut Self { self.endpoint_resolver = endpoint_resolver.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -344,12 +326,9 @@ impl RuntimeComponentsBuilder { } /// Sets the endpoint resolver. - /// - /// Takes an implementation of [`EndpointResolver`](crate::client::endpoint::EndpointResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_endpoint_resolver( mut self, - endpoint_resolver: Option>, + endpoint_resolver: Option, ) -> Self { self.set_endpoint_resolver(endpoint_resolver); self @@ -361,35 +340,23 @@ impl RuntimeComponentsBuilder { } /// Adds an auth scheme. - /// - /// Takes an implementation of [`AuthScheme`](crate::client::auth::AuthScheme) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn push_auth_scheme( - &mut self, - auth_scheme: impl IntoShared, - ) -> &mut Self { + pub fn push_auth_scheme(&mut self, auth_scheme: impl AuthScheme + 'static) -> &mut Self { self.auth_schemes .push(Tracked::new(self.builder_name, auth_scheme.into_shared())); self } /// Adds an auth scheme. - /// - /// Takes an implementation of [`AuthScheme`](crate::client::auth::AuthScheme) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_auth_scheme(mut self, auth_scheme: impl IntoShared) -> Self { + pub fn with_auth_scheme(mut self, auth_scheme: impl AuthScheme + 'static) -> Self { self.push_auth_scheme(auth_scheme); self } /// Adds an identity resolver. - /// - /// Takes an implementation of [`IdentityResolver`](crate::client::identity::IdentityResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn push_identity_resolver( &mut self, scheme_id: AuthSchemeId, - identity_resolver: impl IntoShared, + identity_resolver: impl IdentityResolver + 'static, ) -> &mut Self { self.identity_resolvers.push(Tracked::new( self.builder_name, @@ -399,13 +366,10 @@ impl RuntimeComponentsBuilder { } /// Adds an identity resolver. - /// - /// Takes an implementation of [`IdentityResolver`](crate::client::identity::IdentityResolver) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_identity_resolver( mut self, scheme_id: AuthSchemeId, - identity_resolver: impl IntoShared, + identity_resolver: impl IdentityResolver + 'static, ) -> Self { self.push_identity_resolver(scheme_id, identity_resolver); self @@ -427,23 +391,14 @@ impl RuntimeComponentsBuilder { } /// Adds an interceptor. - /// - /// Takes an implementation of [`Interceptor`](crate::client::interceptors::Interceptor) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn push_interceptor( - &mut self, - interceptor: impl IntoShared, - ) -> &mut Self { + pub fn push_interceptor(&mut self, interceptor: impl Interceptor + 'static) -> &mut Self { self.interceptors .push(Tracked::new(self.builder_name, interceptor.into_shared())); self } /// Adds an interceptor. - /// - /// Takes an implementation of [`Interceptor`](crate::client::interceptors::Interceptor) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_interceptor(mut self, interceptor: impl IntoShared) -> Self { + pub fn with_interceptor(mut self, interceptor: impl Interceptor + 'static) -> Self { self.push_interceptor(interceptor); self } @@ -494,12 +449,9 @@ impl RuntimeComponentsBuilder { } /// Sets the retry strategy. - /// - /// Takes an implementation of [`RetryStrategy`](crate::client::retries::RetryStrategy) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_retry_strategy( &mut self, - retry_strategy: Option>, + retry_strategy: Option, ) -> &mut Self { self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -507,12 +459,9 @@ impl RuntimeComponentsBuilder { } /// Sets the retry strategy. - /// - /// Takes an implementation of [`RetryStrategy`](crate::client::retries::RetryStrategy) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn with_retry_strategy( mut self, - retry_strategy: Option>, + retry_strategy: Option, ) -> Self { self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -525,22 +474,13 @@ impl RuntimeComponentsBuilder { } /// Sets the async sleep implementation. - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s)); self } /// Sets the async sleep implementation. - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_sleep_impl( - mut self, - sleep_impl: Option>, - ) -> Self { + pub fn with_sleep_impl(mut self, sleep_impl: Option) -> Self { self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } @@ -551,22 +491,13 @@ impl RuntimeComponentsBuilder { } /// Sets the time source. - /// - /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) - /// as an argument. All implementations of this trait implement `IntoShared`. pub fn set_time_source(&mut self, time_source: Option) -> &mut Self { self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s)); self } /// Sets the time source. - /// - /// Takes an implementation of [`TimeSource`](aws_smithy_async::time::TimeSource) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_time_source( - mut self, - time_source: Option>, - ) -> Self { + pub fn with_time_source(mut self, time_source: Option) -> Self { self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } @@ -592,15 +523,9 @@ impl RuntimeComponentsBuilder { /// Creates a runtime components builder with all the required components filled in with fake (panicking) implementations. #[cfg(feature = "test-util")] pub fn for_tests() -> Self { - use crate::client::auth::AuthSchemeOptionResolver; - use crate::client::endpoint::{EndpointResolver, EndpointResolverParams}; - use crate::client::http::HttpClient; + use crate::client::endpoint::EndpointResolverParams; use crate::client::identity::Identity; - use crate::client::identity::IdentityResolver; use crate::client::orchestrator::Future; - use crate::client::retries::RetryStrategy; - use aws_smithy_async::rt::sleep::AsyncSleep; - use aws_smithy_async::time::TimeSource; use aws_smithy_types::config_bag::ConfigBag; use aws_smithy_types::endpoint::Endpoint; diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs index c0ab32d042..56596a6087 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs @@ -241,20 +241,20 @@ impl RuntimePlugins { } /// Adds a client-level runtime plugin. - /// - /// Takes an implementation of [`RuntimePlugin`](crate::client::runtime_plugin::RuntimePlugin) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_client_plugin(mut self, plugin: impl IntoShared) -> Self { - insert_plugin!(self.client_plugins, plugin.into_shared()); + pub fn with_client_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self { + insert_plugin!( + self.client_plugins, + IntoShared::::into_shared(plugin) + ); self } /// Adds an operation-level runtime plugin. - /// - /// Takes an implementation of [`RuntimePlugin`](crate::client::runtime_plugin::RuntimePlugin) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn with_operation_plugin(mut self, plugin: impl IntoShared) -> Self { - insert_plugin!(self.operation_plugins, plugin.into_shared()); + pub fn with_operation_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self { + insert_plugin!( + self.operation_plugins, + IntoShared::::into_shared(plugin) + ); self } diff --git a/rust-runtime/aws-smithy-runtime-api/src/shared.rs b/rust-runtime/aws-smithy-runtime-api/src/shared.rs index f3f8630494..80b4f68ebe 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/shared.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/shared.rs @@ -63,9 +63,10 @@ The `IntoShared` trait is useful for making functions that take any `RuntimePlug For example, this function will convert the given `plugin` argument into a `SharedRuntimePlugin`. ```rust,no_run -# use aws_smithy_runtime_api::client::runtime_plugin::{SharedRuntimePlugin, StaticRuntimePlugin}; -# use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared}; -fn take_shared(plugin: impl IntoShared) { +# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin}; +use aws_smithy_runtime_api::shared::IntoShared; + +fn take_shared(plugin: impl RuntimePlugin + 'static) { let _plugin: SharedRuntimePlugin = plugin.into_shared(); } ``` @@ -74,9 +75,9 @@ This can be called with different types, and even if a `SharedRuntimePlugin` is `SharedRuntimePlugin` inside of another `SharedRuntimePlugin`. ```rust,no_run -# use aws_smithy_runtime_api::client::runtime_plugin::{SharedRuntimePlugin, StaticRuntimePlugin}; +# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin, StaticRuntimePlugin}; # use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared}; -# fn take_shared(plugin: impl IntoShared) { +# fn take_shared(plugin: impl RuntimePlugin + 'static) { # let _plugin: SharedRuntimePlugin = plugin.into_shared(); # } // Automatically converts it to `SharedRuntimePlugin(StaticRuntimePlugin)` diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 94c1cdf957..6ddfc8b962 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -4,7 +4,7 @@ */ use aws_smithy_async::future::timeout::TimedOutError; -use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; +use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::connection::{CaptureSmithyConnection, ConnectionMetadata}; use aws_smithy_http::result::ConnectorError; @@ -248,10 +248,7 @@ impl HyperConnectorBuilder { /// /// Calling this is only necessary for testing or to use something other than /// [`default_async_sleep`]. - /// - /// Takes an implementation of [`AsyncSleep`](aws_smithy_async::rt::sleep::AsyncSleep) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self { self.sleep_impl = Some(sleep_impl.into_shared()); self } diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs index 6675598b69..1c682ad7ed 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs @@ -46,10 +46,7 @@ impl RecordingClient { impl RecordingClient { /// Create a new recording connection from a connection - /// - /// Takes an implementation of [`HttpConnector`](aws_smithy_runtime_api::client::http::HttpConnector) - /// as an argument. All implementations of this trait implement `IntoShared`. - pub fn new(underlying_connector: impl IntoShared) -> Self { + pub fn new(underlying_connector: impl HttpConnector + 'static) -> Self { Self { data: Default::default(), num_events: Arc::new(AtomicUsize::new(0)), diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index dfeaeced77..7044bfb4e0 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -8,8 +8,8 @@ use crate::client::http::default_http_client_plugin; use crate::client::identity::no_auth::NoAuthIdentityResolver; use crate::client::orchestrator::endpoints::StaticUriEndpointResolver; use crate::client::retries::strategy::{NeverRetryStrategy, StandardRetryStrategy}; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::SharedTimeSource; +use aws_smithy_async::rt::sleep::AsyncSleep; +use aws_smithy_async::time::TimeSource; use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver; @@ -17,16 +17,16 @@ use aws_smithy_runtime_api::client::auth::{ AuthSchemeOptionResolverParams, SharedAuthScheme, SharedAuthSchemeOptionResolver, }; use aws_smithy_runtime_api::client::endpoint::{EndpointResolverParams, SharedEndpointResolver}; -use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::client::identity::SharedIdentityResolver; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output}; -use aws_smithy_runtime_api::client::interceptors::SharedInterceptor; +use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, OrchestratorError}; use aws_smithy_runtime_api::client::retries::{RetryClassifiers, SharedRetryStrategy}; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{ - RuntimePlugins, SharedRuntimePlugin, StaticRuntimePlugin, + RuntimePlugin, RuntimePlugins, SharedRuntimePlugin, StaticRuntimePlugin, }; use aws_smithy_runtime_api::client::ser_de::{ RequestSerializer, ResponseDeserializer, SharedRequestSerializer, SharedResponseDeserializer, @@ -191,7 +191,7 @@ impl OperationBuilder { self } - pub fn http_client(mut self, connector: impl IntoShared) -> Self { + pub fn http_client(mut self, connector: impl HttpClient + 'static) -> Self { self.runtime_components.set_http_client(Some(connector)); self } @@ -246,24 +246,24 @@ impl OperationBuilder { self } - pub fn sleep_impl(mut self, async_sleep: impl IntoShared) -> Self { + pub fn sleep_impl(mut self, async_sleep: impl AsyncSleep + 'static) -> Self { self.runtime_components .set_sleep_impl(Some(async_sleep.into_shared())); self } - pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { self.runtime_components .set_time_source(Some(time_source.into_shared())); self } - pub fn interceptor(mut self, interceptor: impl IntoShared) -> Self { + pub fn interceptor(mut self, interceptor: impl Interceptor + 'static) -> Self { self.runtime_components.push_interceptor(interceptor); self } - pub fn runtime_plugin(mut self, runtime_plugin: impl IntoShared) -> Self { + pub fn runtime_plugin(mut self, runtime_plugin: impl RuntimePlugin + 'static) -> Self { self.runtime_plugins.push(runtime_plugin.into_shared()); self } From 1d80edfe9ea0a2b3611cb926faf67d11147d23f3 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 5 Oct 2023 12:35:02 -0700 Subject: [PATCH 19/23] Add `BoxFuture` type to `aws_smithy_async::future` --- aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs | 4 +--- rust-runtime/aws-smithy-async/src/future/mod.rs | 6 ++++++ rust-runtime/aws-smithy-runtime-api/src/client/dns.rs | 2 +- rust-runtime/aws-smithy-runtime-api/src/client/http.rs | 2 +- .../aws-smithy-runtime-api/src/client/orchestrator.rs | 4 +--- .../aws-smithy-runtime/src/client/http/test_util/wire.rs | 5 +---- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs index 4196730f09..298899e71b 100644 --- a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs +++ b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs @@ -5,20 +5,18 @@ //! Maintain a cache of discovered endpoints +use aws_smithy_async::future::BoxFuture; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::SharedTimeSource; use aws_smithy_http::endpoint::{ResolveEndpoint, ResolveEndpointError}; use aws_smithy_types::endpoint::Endpoint; use std::fmt::{Debug, Formatter}; use std::future::Future; -use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{Receiver, Sender}; -type BoxFuture = Pin> + Send>>; - /// Endpoint reloader #[must_use] pub struct ReloadEndpoint { diff --git a/rust-runtime/aws-smithy-async/src/future/mod.rs b/rust-runtime/aws-smithy-async/src/future/mod.rs index 44894e0733..d60fc46c77 100644 --- a/rust-runtime/aws-smithy-async/src/future/mod.rs +++ b/rust-runtime/aws-smithy-async/src/future/mod.rs @@ -5,8 +5,14 @@ //! Useful runtime-agnostic future implementations. +use futures_util::Future; +use std::pin::Pin; + pub mod never; pub mod now_or_later; pub mod pagination_stream; pub mod rendezvous; pub mod timeout; + +/// A boxed future that outputs a `Result`. +pub type BoxFuture = Pin> + Send>>; diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs index ce00e13f57..f7dbadec60 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs @@ -43,7 +43,7 @@ impl StdError for ResolveDnsError { } } -type BoxFuture = Pin> + Send>>; +type BoxFuture = aws_smithy_async::future::BoxFuture; /// New-type for the future returned by the [`DnsResolver`] trait. pub struct DnsFuture(NowOrLater, ResolveDnsError>, BoxFuture>>); diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs index 66d7ddc3f4..5347f71247 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs @@ -63,7 +63,7 @@ use std::sync::Arc; use std::task::Poll; use std::time::Duration; -type BoxFuture = Pin> + Send>>; +type BoxFuture = aws_smithy_async::future::BoxFuture; pin_project! { /// Future for [`HttpConnector::call`]. diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs index a034515562..328117648c 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -27,8 +27,6 @@ use aws_smithy_types::config_bag::{Storable, StoreReplace}; use bytes::Bytes; use std::error::Error as StdError; use std::fmt; -use std::future::Future as StdFuture; -use std::pin::Pin; /// Type alias for the HTTP request type that the orchestrator uses. pub type HttpRequest = http::Request; @@ -40,7 +38,7 @@ pub type HttpResponse = http::Response; /// /// See [the Rust blog](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html) for /// more information on async functions in traits. -pub type BoxFuture = Pin> + Send>>; +pub type BoxFuture = aws_smithy_async::future::BoxFuture; /// Type alias for futures that are returned from several traits since async trait functions are not stable yet (as of 2023-07-21). /// diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs index 2d31dc0104..f5dd76e8db 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs @@ -41,6 +41,7 @@ use crate::client::http::hyper_014::HyperClientBuilder; use aws_smithy_async::future::never::Never; +use aws_smithy_async::future::BoxFuture; use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::shared::IntoShared; use bytes::Bytes; @@ -52,10 +53,8 @@ use hyper::{Body, Server}; use std::collections::HashSet; use std::convert::Infallible; use std::error::Error; -use std::future::Future; use std::iter::Once; use std::net::{SocketAddr, TcpListener}; -use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use tokio::spawn; @@ -322,8 +321,6 @@ async fn generate_response_event(event: ReplayedEvent) -> Result, Ok::<_, Infallible>(resp) } -type BoxFuture = Pin> + Send>>; - /// DNS resolver that keeps a log of all lookups /// /// Regardless of what hostname is requested, it will always return the same socket address. From 66e63b8729259eb99ba360bd83123489cb85cc54 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 5 Oct 2023 12:48:34 -0700 Subject: [PATCH 20/23] Misc feedback --- aws/sdk/integration-tests/webassembly/src/http.rs | 2 +- rust-runtime/aws-smithy-runtime/src/client/http.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/aws/sdk/integration-tests/webassembly/src/http.rs b/aws/sdk/integration-tests/webassembly/src/http.rs index 7c45e0258d..ce4cb246eb 100644 --- a/aws/sdk/integration-tests/webassembly/src/http.rs +++ b/aws/sdk/integration-tests/webassembly/src/http.rs @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_sdk_s3::config::RuntimeComponents; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime_api::client::http::{ HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, }; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_runtime_api::shared::IntoShared; pub(crate) fn make_request(_req: http::Request) -> Result, ()> { diff --git a/rust-runtime/aws-smithy-runtime/src/client/http.rs b/rust-runtime/aws-smithy-runtime/src/client/http.rs index b04cb91824..6f5960d073 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http.rs @@ -23,7 +23,6 @@ pub mod test_util; pub mod hyper_014; /// Runtime plugin that provides a default connector. Intended to be used by the generated code. -#[doc(hidden)] pub fn default_http_client_plugin() -> SharedRuntimePlugin { let _default: Option = None; #[cfg(feature = "connector-hyper-0-14-x")] From 355bf24aa4ea412cbf891e421971a56df81e55c0 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 5 Oct 2023 14:26:13 -0700 Subject: [PATCH 21/23] Fix external types checks --- aws/rust-runtime/aws-config/external-types.toml | 2 ++ aws/rust-runtime/aws-credential-types/external-types.toml | 1 + aws/rust-runtime/aws-types/external-types.toml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index 6a223eafdd..450f0b5c9d 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -16,7 +16,9 @@ allowed_external_types = [ "aws_smithy_http::endpoint", "aws_smithy_http::endpoint::error::InvalidEndpointError", "aws_smithy_http::result::SdkError", + "aws_smithy_runtime_api::client::dns::DnsResolver", "aws_smithy_runtime_api::client::dns::SharedDnsResolver", + "aws_smithy_runtime_api::client::http::HttpClient", "aws_smithy_runtime_api::client::http::SharedHttpClient", "aws_smithy_types::retry", "aws_smithy_types::retry::*", diff --git a/aws/rust-runtime/aws-credential-types/external-types.toml b/aws/rust-runtime/aws-credential-types/external-types.toml index e65c743b10..88a5088190 100644 --- a/aws/rust-runtime/aws-credential-types/external-types.toml +++ b/aws/rust-runtime/aws-credential-types/external-types.toml @@ -1,4 +1,5 @@ allowed_external_types = [ + "aws_smithy_async::rt::sleep::AsyncSleep", "aws_smithy_async::rt::sleep::SharedAsyncSleep", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", diff --git a/aws/rust-runtime/aws-types/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index 1b2eb6ab5d..ca85884065 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -1,9 +1,11 @@ allowed_external_types = [ "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::SharedCredentialsProvider", + "aws_smithy_async::rt::sleep::AsyncSleep", "aws_smithy_async::rt::sleep::SharedAsyncSleep", "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", + "aws_smithy_runtime_api::client::http::HttpClient", "aws_smithy_runtime_api::client::http::SharedHttpClient", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", From 365dee6369edea8f2c588d67fc54ba7977f37f8c Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Thu, 5 Oct 2023 16:09:13 -0700 Subject: [PATCH 22/23] Several minor fixes --- aws/rust-runtime/aws-config/src/imds/region.rs | 2 +- aws/rust-runtime/aws-config/src/lib.rs | 3 +-- aws/rust-runtime/aws-config/src/profile/region.rs | 2 +- aws/rust-runtime/aws-config/src/provider_config.rs | 3 +-- aws/rust-runtime/aws-config/src/web_identity_token.rs | 2 +- aws/sdk/integration-tests/dynamodb/tests/paginators.rs | 3 +-- aws/sdk/integration-tests/ec2/tests/paginators.rs | 3 +-- rust-runtime/aws-smithy-runtime/Cargo.toml | 2 +- rust-runtime/aws-smithy-runtime/src/client/dns.rs | 2 +- 9 files changed, 9 insertions(+), 13 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/imds/region.rs b/aws/rust-runtime/aws-config/src/imds/region.rs index 98bb196e0d..bc19642a64 100644 --- a/aws/rust-runtime/aws-config/src/imds/region.rs +++ b/aws/rust-runtime/aws-config/src/imds/region.rs @@ -110,10 +110,10 @@ mod test { use crate::imds::client::test::{imds_request, imds_response, token_request, token_response}; use crate::imds::region::ImdsRegionProvider; use crate::provider_config::ProviderConfig; - use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; + use aws_types::region::Region; use tracing_test::traced_test; #[tokio::test] diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 4edec7d839..ba1cb953d8 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -157,8 +157,7 @@ mod loader { use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; - use aws_sdk_sso::config::AsyncSleep; - use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; + use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::shared::IntoShared; diff --git a/aws/rust-runtime/aws-config/src/profile/region.rs b/aws/rust-runtime/aws-config/src/profile/region.rs index 7b04ad9592..a293733377 100644 --- a/aws/rust-runtime/aws-config/src/profile/region.rs +++ b/aws/rust-runtime/aws-config/src/profile/region.rs @@ -158,8 +158,8 @@ mod test { use crate::profile::ProfileFileRegionProvider; use crate::provider_config::ProviderConfig; use crate::test_case::no_traffic_client; - use aws_sdk_sts::config::Region; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::region::Region; use futures_util::FutureExt; use tracing_test::traced_test; diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index 7bc85ccb8d..dd13270bbc 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -8,8 +8,7 @@ use crate::profile; use crate::profile::profile_file::ProfileFiles; use crate::profile::{ProfileFileLoadError, ProfileSet}; -use aws_sdk_sso::config::AsyncSleep; -use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; +use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::shared::IntoShared; diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs index 534bcfba71..7ae4e2f8b0 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -246,10 +246,10 @@ mod test { Builder, ENV_VAR_ROLE_ARN, ENV_VAR_SESSION_NAME, ENV_VAR_TOKEN_FILE, }; use aws_credential_types::provider::error::CredentialsError; - use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::region::Region; use std::collections::HashMap; #[tokio::test] diff --git a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs index 54f441df91..711feb1e01 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs @@ -14,9 +14,8 @@ use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; use aws_smithy_runtime::client::http::test_util::{ capture_request, ReplayEvent, StaticReplayClient, }; -use aws_smithy_runtime_api::shared::IntoShared; +use aws_smithy_runtime_api::client::http::HttpClient; use aws_types::region::Region; -use aws_types::sdk_config::SharedHttpClient; fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() diff --git a/aws/sdk/integration-tests/ec2/tests/paginators.rs b/aws/sdk/integration-tests/ec2/tests/paginators.rs index 25b4e42cee..a9ab25a4a1 100644 --- a/aws/sdk/integration-tests/ec2/tests/paginators.rs +++ b/aws/sdk/integration-tests/ec2/tests/paginators.rs @@ -6,8 +6,7 @@ use aws_sdk_ec2::{config::Credentials, config::Region, types::InstanceType, Client, Config}; use aws_smithy_http::body::SdkBody; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; -use aws_smithy_runtime_api::client::http::SharedHttpClient; -use aws_smithy_runtime_api::shared::IntoShared; +use aws_smithy_runtime_api::client::http::HttpClient; fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index dcf796b308..7b569d35c8 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/awslabs/smithy-rs" client = ["aws-smithy-runtime-api/client"] http-auth = ["aws-smithy-runtime-api/http-auth"] connector-hyper-0-14-x = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] -tls-rustls = ["dep:hyper-rustls", "dep:rustls"] +tls-rustls = ["dep:hyper-rustls", "dep:rustls", "connector-hyper-0-14-x"] rt-tokio = ["tokio/rt"] # Features for testing diff --git a/rust-runtime/aws-smithy-runtime/src/client/dns.rs b/rust-runtime/aws-smithy-runtime/src/client/dns.rs index e7eb561c2b..3a311ccd98 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/dns.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/dns.rs @@ -44,5 +44,5 @@ mod tokio { } } -#[cfg(feature = "rt-tokio")] +#[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] pub use self::tokio::TokioDnsResolver; From 819adbc55d75baa456ca5c92d77bf3b548a6642d Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Fri, 6 Oct 2023 09:16:13 -0700 Subject: [PATCH 23/23] Fix broken test --- .../aws-smithy-runtime/src/client/orchestrator/operation.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index 334e50033c..9c4e71237c 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -219,6 +219,7 @@ impl OperationBuilder { } pub fn standard_retry(mut self, retry_config: &RetryConfig) -> Self { + self.config.store_put(retry_config.clone()); self.runtime_components .set_retry_strategy(Some(SharedRetryStrategy::new(StandardRetryStrategy::new( retry_config,