diff --git a/aws/rust-runtime/aws-http/src/request_id.rs b/aws/rust-runtime/aws-http/src/request_id.rs index 7713328f7c..692e9cc86f 100644 --- a/aws/rust-runtime/aws-http/src/request_id.rs +++ b/aws/rust-runtime/aws-http/src/request_id.rs @@ -4,7 +4,6 @@ */ 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, @@ -46,12 +45,6 @@ impl RequestId for Unhandled { } } -impl RequestId for operation::Response { - fn request_id(&self) -> Option<&str> { - extract_request_id(self.http().headers()) - } -} - impl RequestId for http::Response { fn request_id(&self) -> Option<&str> { extract_request_id(self.headers()) @@ -106,18 +99,15 @@ mod tests { #[test] fn test_request_id_sdk_error() { - let without_request_id = - || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap()); + let without_request_id = || Response::builder().body(SdkBody::empty()).unwrap(); let with_request_id = || { - operation::Response::new( - Response::builder() - .header( - "x-amzn-requestid", - HeaderValue::from_static("some-request-id"), - ) - .body(SdkBody::empty()) - .unwrap(), - ) + Response::builder() + .header( + "x-amzn-requestid", + HeaderValue::from_static("some-request-id"), + ) + .body(SdkBody::empty()) + .unwrap() }; assert_eq!( None, diff --git a/aws/rust-runtime/aws-http/src/user_agent.rs b/aws/rust-runtime/aws-http/src/user_agent.rs index fe1279b012..1e26da4d4e 100644 --- a/aws/rust-runtime/aws-http/src/user_agent.rs +++ b/aws/rust-runtime/aws-http/src/user_agent.rs @@ -13,9 +13,9 @@ use std::fmt; /// AWS User Agent /// -/// Ths struct should be inserted into the [`PropertyBag`](aws_smithy_http::operation::Request::properties) -/// during operation construction. [`UserAgentStage`](UserAgentStage) reads `AwsUserAgent` -/// from the property bag and sets the `User-Agent` and `x-amz-user-agent` headers. +/// Ths struct should be inserted into the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag) +/// during operation construction. The `UserAgentInterceptor` reads `AwsUserAgent` +/// from the config bag and sets the `User-Agent` and `x-amz-user-agent` headers. #[derive(Clone, Debug)] pub struct AwsUserAgent { sdk_metadata: SdkMetadata, 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 d2e64a7038..226eed5f7a 100644 --- a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs +++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs @@ -4,7 +4,6 @@ */ 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, @@ -47,12 +46,6 @@ impl RequestIdExt for Unhandled { } } -impl RequestIdExt for operation::Response { - fn extended_request_id(&self) -> Option<&str> { - extract_extended_request_id(self.http().headers()) - } -} - impl RequestIdExt for http::Response { fn extended_request_id(&self) -> Option<&str> { extract_extended_request_id(self.headers()) @@ -115,15 +108,12 @@ mod test { #[test] fn test_extended_request_id_sdk_error() { - let without_extended_request_id = - || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap()); + let without_extended_request_id = || Response::builder().body(SdkBody::empty()).unwrap(); let with_extended_request_id = || { - operation::Response::new( - Response::builder() - .header("x-amz-id-2", HeaderValue::from_static("some-request-id")) - .body(SdkBody::empty()) - .unwrap(), - ) + Response::builder() + .header("x-amz-id-2", HeaderValue::from_static("some-request-id")) + .body(SdkBody::empty()) + .unwrap() }; assert_eq!( None, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecorator.kt index 684def389a..375a87a2e6 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecorator.kt @@ -26,7 +26,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.getTrait @@ -51,7 +50,6 @@ private fun codegenScope(runtimeConfig: RuntimeConfig): Array> "HTTP_DIGEST_AUTH_SCHEME_ID" to authHttpApi.resolve("HTTP_DIGEST_AUTH_SCHEME_ID"), "IdentityResolver" to smithyRuntimeApi.resolve("client::identity::IdentityResolver"), "Login" to smithyRuntimeApi.resolve("client::identity::http::Login"), - "PropertyBag" to RuntimeType.smithyHttp(runtimeConfig).resolve("property_bag::PropertyBag"), "SharedAuthScheme" to smithyRuntimeApi.resolve("client::auth::SharedAuthScheme"), "SharedIdentityResolver" to smithyRuntimeApi.resolve("client::identity::SharedIdentityResolver"), "Token" to smithyRuntimeApi.resolve("client::identity::http::Token"), 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 eddb65bc14..b356058437 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 @@ -395,7 +395,6 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun blob(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("Blob") fun byteStream(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("byte_stream::ByteStream") - fun classifyRetry(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("retry::ClassifyRetry") fun dateTime(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("DateTime") fun document(runtimeConfig: RuntimeConfig): RuntimeType = smithyTypes(runtimeConfig).resolve("Document") fun format(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("date_time::Format") @@ -422,11 +421,6 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun labelFormat(runtimeConfig: RuntimeConfig, func: String) = smithyHttp(runtimeConfig).resolve("label::$func") fun operation(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation::Operation") fun operationModule(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation") - fun parseHttpResponse(runtimeConfig: RuntimeConfig) = - smithyHttp(runtimeConfig).resolve("response::ParseHttpResponse") - - fun parseStrictResponse(runtimeConfig: RuntimeConfig) = - smithyHttp(runtimeConfig).resolve("response::ParseStrictResponse") fun protocolTest(runtimeConfig: RuntimeConfig, func: String): RuntimeType = smithyProtocolTest(runtimeConfig).resolve(func) diff --git a/rust-runtime/aws-smithy-http/src/endpoint.rs b/rust-runtime/aws-smithy-http/src/endpoint.rs index 3a4939434e..8496239787 100644 --- a/rust-runtime/aws-smithy-http/src/endpoint.rs +++ b/rust-runtime/aws-smithy-http/src/endpoint.rs @@ -16,7 +16,6 @@ use std::str::FromStr; use std::sync::Arc; pub mod error; -pub mod middleware; pub use error::ResolveEndpointError; diff --git a/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs b/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs deleted file mode 100644 index 1fbb099b11..0000000000 --- a/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! [`MapRequest`]-based middleware for resolving and applying a request's endpoint. - -use crate::endpoint; -use crate::endpoint::{apply_endpoint, EndpointPrefix, ResolveEndpointError}; -use crate::middleware::MapRequest; -use crate::operation::Request; -use http::header::HeaderName; -use http::{HeaderValue, Uri}; -use std::str::FromStr; - -// TODO(enableNewSmithyRuntimeCleanup): Delete this module - -/// Middleware to apply an HTTP endpoint to the request -/// -/// This middleware reads [`aws_smithy_types::endpoint::Endpoint`] out of the request properties and applies -/// it to the HTTP request. -#[non_exhaustive] -#[derive(Default, Debug, Clone)] -pub struct SmithyEndpointStage; -impl SmithyEndpointStage { - /// Create a new `SmithyEndpointStage`. - pub fn new() -> Self { - Self::default() - } -} - -impl MapRequest for SmithyEndpointStage { - type Error = ResolveEndpointError; - - fn name(&self) -> &'static str { - "resolve_endpoint" - } - - fn apply(&self, request: Request) -> Result { - request.augment(|mut http_req, props| { - // we need to do a little dance so that this works with retries. - // the first pass through, we convert the result into just an endpoint, early returning - // the error. Put the endpoint back in the bag in case this request gets retried. - // - // the next pass through, there is no result, so in that case, we'll look for the - // endpoint directly. - // - // In an ideal world, we would do this in make_operation, but it's much easier for - // certain protocol tests if we allow requests with invalid endpoint to be constructed. - if let Some(endpoint) = props.remove::().transpose()? { - props.insert(endpoint); - }; - let endpoint = props.get::(); - let endpoint = - endpoint.ok_or_else(|| ResolveEndpointError::message("no endpoint present"))?; - - let uri: Uri = endpoint.url().parse().map_err(|err| { - ResolveEndpointError::from_source("endpoint did not have a valid uri", err) - })?; - apply_endpoint(http_req.uri_mut(), &uri, props.get::()).map_err( - |err| { - ResolveEndpointError::message(format!( - "failed to apply endpoint `{:?}` to request `{:?}`", - uri, http_req - )) - .with_source(Some(err.into())) - }, - )?; - for (header_name, header_values) in endpoint.headers() { - http_req.headers_mut().remove(header_name); - for value in header_values { - http_req.headers_mut().insert( - HeaderName::from_str(header_name).map_err(|err| { - ResolveEndpointError::message("invalid header name") - .with_source(Some(err.into())) - })?, - HeaderValue::from_str(value).map_err(|err| { - ResolveEndpointError::message("invalid header value") - .with_source(Some(err.into())) - })?, - ); - } - } - Ok(http_req) - }) - } -} diff --git a/rust-runtime/aws-smithy-http/src/http.rs b/rust-runtime/aws-smithy-http/src/http.rs index ad77e951c3..2a988f00f2 100644 --- a/rust-runtime/aws-smithy-http/src/http.rs +++ b/rust-runtime/aws-smithy-http/src/http.rs @@ -27,13 +27,3 @@ impl HttpHeaders for http::Response { self.headers_mut() } } - -impl HttpHeaders for crate::operation::Response { - fn http_headers(&self) -> &HeaderMap { - self.http().http_headers() - } - - fn http_headers_mut(&mut self) -> &mut HeaderMap { - self.http_mut().http_headers_mut() - } -} diff --git a/rust-runtime/aws-smithy-http/src/lib.rs b/rust-runtime/aws-smithy-http/src/lib.rs index 52a259b422..2601a8e762 100644 --- a/rust-runtime/aws-smithy-http/src/lib.rs +++ b/rust-runtime/aws-smithy-http/src/lib.rs @@ -36,15 +36,11 @@ pub mod futures_stream_adapter; pub mod header; pub mod http; pub mod label; -pub mod middleware; pub mod operation; -pub mod property_bag; pub mod query; #[doc(hidden)] pub mod query_writer; -pub mod response; pub mod result; -pub mod retry; #[cfg(feature = "event-stream")] pub mod event_stream; diff --git a/rust-runtime/aws-smithy-http/src/middleware.rs b/rust-runtime/aws-smithy-http/src/middleware.rs deleted file mode 100644 index 3285dec872..0000000000 --- a/rust-runtime/aws-smithy-http/src/middleware.rs +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! This modules defines the core, framework agnostic, HTTP middleware interface -//! used by the SDK -//! -//! smithy-middleware-tower provides Tower-specific middleware utilities (todo) - -use crate::body::SdkBody; -use crate::operation; -use crate::response::ParseHttpResponse; -use crate::result::{SdkError, SdkSuccess}; -use bytes::{Buf, Bytes}; -use http_body::Body; -use pin_utils::pin_mut; -use std::error::Error; -use std::future::Future; -use tracing::{debug_span, trace, Instrument}; - -type BoxError = Box; - -const LOG_SENSITIVE_BODIES: &str = "LOG_SENSITIVE_BODIES"; - -/// [`AsyncMapRequest`] defines an asynchronous middleware that transforms an [`operation::Request`]. -/// -/// Typically, these middleware will read configuration from the `PropertyBag` and use it to -/// augment the request. -/// -/// Most fundamental middleware is expressed as `AsyncMapRequest`'s synchronous cousin, `MapRequest`, -/// including signing & endpoint resolution. `AsyncMapRequest` is used for async credential -/// retrieval (e.g., from AWS STS's AssumeRole operation). -pub trait AsyncMapRequest { - /// The type returned when this [`AsyncMapRequest`] encounters an error. - type Error: Into + 'static; - /// The type returned when [`AsyncMapRequest::apply`] is called. - type Future: Future> + Send + 'static; - - /// Returns the name of this map request operation for inclusion in a tracing span. - fn name(&self) -> &'static str; - - /// Call this middleware, returning a future that resolves to a request or an error. - fn apply(&self, request: operation::Request) -> Self::Future; -} - -/// [`MapRequest`] defines a synchronous middleware that transforms an [`operation::Request`]. -/// -/// Typically, these middleware will read configuration from the `PropertyBag` and use it to -/// augment the request. Most fundamental middleware is expressed as `MapRequest`, including -/// signing & endpoint resolution. -/// -/// ## Examples -/// -/// ```rust -/// # use aws_smithy_http::middleware::MapRequest; -/// # use std::convert::Infallible; -/// # use aws_smithy_http::operation; -/// use http::header::{HeaderName, HeaderValue}; -/// -/// /// Signaling struct added to the request property bag if a header should be added -/// struct NeedsHeader; -/// -/// struct AddHeader(HeaderName, HeaderValue); -/// -/// impl MapRequest for AddHeader { -/// type Error = Infallible; -/// -/// fn name(&self) -> &'static str { -/// "add_header" -/// } -/// -/// fn apply(&self, request: operation::Request) -> Result { -/// request.augment(|mut request, properties| { -/// if properties.get::().is_some() { -/// request.headers_mut().append(self.0.clone(), self.1.clone()); -/// } -/// Ok(request) -/// }) -/// } -/// } -/// ``` -pub trait MapRequest { - /// The Error type returned by this operation. - /// - /// If this middleware never fails use [std::convert::Infallible] or similar. - type Error: Into; - - /// Returns the name of this map request operation for inclusion in a tracing span. - fn name(&self) -> &'static str; - - /// Apply this middleware to a request. - /// - /// Typically, implementations will use [`request.augment`](crate::operation::Request::augment) - /// to be able to transform an owned `http::Request`. - fn apply(&self, request: operation::Request) -> Result; -} - -/// Load a response using `handler` to parse the results. -/// -/// This function is intended to be used on the response side of a middleware chain. -/// -/// Success and failure will be split and mapped into `SdkSuccess` and `SdkError`. -/// Generic Parameters: -/// - `O`: The Http response handler that returns `Result` -/// - `T`/`E`: `Result` returned by `handler`. -pub async fn load_response( - mut response: operation::Response, - handler: &O, -) -> Result, SdkError> -where - O: ParseHttpResponse>, -{ - if let Some(parsed_response) = - debug_span!("parse_unloaded").in_scope(|| handler.parse_unloaded(&mut response)) - { - trace!(response = ?response, "read HTTP headers for streaming response"); - return sdk_result(parsed_response, response); - } - - let (http_response, properties) = response.into_parts(); - let (parts, body) = http_response.into_parts(); - let body = match read_body(body).instrument(debug_span!("read_body")).await { - Ok(body) => body, - Err(err) => { - return Err(SdkError::response_error( - err, - operation::Response::from_parts( - http::Response::from_parts(parts, SdkBody::taken()), - properties, - ), - )); - } - }; - - let http_response = http::Response::from_parts(parts, Bytes::from(body)); - if !handler.sensitive() - || std::env::var(LOG_SENSITIVE_BODIES) - .map(|v| v.eq_ignore_ascii_case("true")) - .unwrap_or_default() - { - trace!(http_response = ?http_response, "read HTTP response body"); - } else { - trace!(http_response = "** REDACTED **. To print, set LOG_SENSITIVE_BODIES=true") - } - debug_span!("parse_loaded").in_scope(move || { - let parsed = handler.parse_loaded(&http_response); - sdk_result( - parsed, - operation::Response::from_parts(http_response.map(SdkBody::from), properties), - ) - }) -} - -async fn read_body(body: B) -> Result, B::Error> { - let mut output = Vec::new(); - pin_mut!(body); - while let Some(buf) = body.data().await { - let mut buf = buf?; - while buf.has_remaining() { - output.extend_from_slice(buf.chunk()); - buf.advance(buf.chunk().len()) - } - } - Ok(output) -} - -/// Convert a `Result` into an `SdkResult` that includes the operation response -fn sdk_result( - parsed: Result, - raw: operation::Response, -) -> Result, SdkError> { - match parsed { - Ok(parsed) => Ok(SdkSuccess { raw, parsed }), - Err(err) => Err(SdkError::service_error(err, raw)), - } -} diff --git a/rust-runtime/aws-smithy-http/src/operation.rs b/rust-runtime/aws-smithy-http/src/operation.rs index 2ddbd28070..b5fb8c9f12 100644 --- a/rust-runtime/aws-smithy-http/src/operation.rs +++ b/rust-runtime/aws-smithy-http/src/operation.rs @@ -6,12 +6,8 @@ //! Types for representing the interaction between a service an a client, referred to as an "operation" in smithy. //! Clients "send" operations to services, which are composed of 1 or more HTTP requests. -use crate::body::SdkBody; -use crate::property_bag::{PropertyBag, SharedPropertyBag}; -use crate::retry::DefaultResponseRetryClassifier; use aws_smithy_types::config_bag::{Storable, StoreReplace}; use std::borrow::Cow; -use std::ops::{Deref, DerefMut}; pub mod error; @@ -48,309 +44,3 @@ impl Metadata { impl Storable for Metadata { type Storer = StoreReplace; } - -/// Non-request parts of an [`Operation`]. -/// -/// Generics: -/// - `H`: Response handler -/// - `R`: Implementation of `ClassifyRetry` -#[non_exhaustive] -#[derive(Clone, Debug)] -pub struct Parts { - /// The response deserializer that will convert the connector's response into an `operation::Response` - pub response_handler: H, - /// The classifier that will determine if an HTTP response indicates that a request failed for a retryable reason. - pub retry_classifier: R, - /// Metadata describing this operation and the service it relates to. - pub metadata: Option, -} - -// TODO(enableNewSmithyRuntimeCleanup): Delete `operation::Operation` when cleaning up middleware -/// An [`Operation`] is a request paired with a response handler, retry classifier, -/// and metadata that identifies the API being called. -/// -/// Generics: -/// - `H`: Response handler -/// - `R`: Implementation of `ClassifyRetry` -#[derive(Debug)] -pub struct Operation { - request: Request, - parts: Parts, -} - -impl Operation { - /// Converts this operation into its parts. - pub fn into_request_response(self) -> (Request, Parts) { - (self.request, self.parts) - } - - /// Constructs an [`Operation`] from a request and [`Parts`] - pub fn from_parts(request: Request, parts: Parts) -> Self { - Self { request, parts } - } - - /// Returns a mutable reference to the request's property bag. - pub fn properties_mut(&mut self) -> impl DerefMut + '_ { - self.request.properties_mut() - } - - /// Returns an immutable reference to the request's property bag. - pub fn properties(&self) -> impl Deref + '_ { - self.request.properties() - } - - /// Gives mutable access to the underlying HTTP request. - pub fn request_mut(&mut self) -> &mut http::Request { - self.request.http_mut() - } - - /// Gives readonly access to the underlying HTTP request. - pub fn request(&self) -> &http::Request { - self.request.http() - } - - /// Attaches metadata to the operation. - pub fn with_metadata(mut self, metadata: Metadata) -> Self { - self.parts.metadata = Some(metadata); - self - } - - /// Replaces the retry classifier on the operation. - pub fn with_retry_classifier(self, retry_classifier: R2) -> Operation { - Operation { - request: self.request, - parts: Parts { - response_handler: self.parts.response_handler, - retry_classifier, - metadata: self.parts.metadata, - }, - } - } - - /// Returns the retry classifier for this operation. - pub fn retry_classifier(&self) -> &R { - &self.parts.retry_classifier - } - - /// Attempts to clone the operation. - /// - /// Will return `None` if the request body is already consumed and can't be replayed. - pub fn try_clone(&self) -> Option - where - H: Clone, - R: Clone, - { - let request = self.request.try_clone()?; - Some(Self { - request, - parts: self.parts.clone(), - }) - } -} - -impl Operation { - /// Creates a new [`Operation`]. - pub fn new( - request: Request, - response_handler: H, - ) -> Operation { - Operation { - request, - parts: Parts { - response_handler, - retry_classifier: DefaultResponseRetryClassifier::new(), - metadata: None, - }, - } - } -} - -// TODO(enableNewSmithyRuntimeCleanup): Delete `operation::Request` when cleaning up middleware -/// Operation request type that associates a property bag with an underlying HTTP request. -/// This type represents the request in the Tower `Service` in middleware so that middleware -/// can share information with each other via the properties. -#[derive(Debug)] -pub struct Request { - /// The underlying HTTP Request - inner: http::Request, - - /// Property bag of configuration options - /// - /// Middleware can read and write from the property bag and use its - /// contents to augment the request (see [`Request::augment`](Request::augment)) - properties: SharedPropertyBag, -} - -impl Request { - /// Creates a new operation `Request` with the given `inner` HTTP request. - pub fn new(inner: http::Request) -> Self { - Request { - inner, - properties: SharedPropertyBag::new(), - } - } - - /// Creates a new operation `Request` from its parts. - pub fn from_parts(inner: http::Request, properties: SharedPropertyBag) -> Self { - Request { inner, properties } - } - - /// Allows modification of the HTTP request and associated properties with a fallible closure. - pub fn augment( - self, - f: impl FnOnce(http::Request, &mut PropertyBag) -> Result, T>, - ) -> Result { - let inner = { - let properties: &mut PropertyBag = &mut self.properties.acquire_mut(); - f(self.inner, properties)? - }; - Ok(Request { - inner, - properties: self.properties, - }) - } - - /// Gives mutable access to the properties. - pub fn properties_mut(&mut self) -> impl DerefMut + '_ { - self.properties.acquire_mut() - } - - /// Gives readonly access to the properties. - pub fn properties(&self) -> impl Deref + '_ { - self.properties.acquire() - } - - /// Gives mutable access to the underlying HTTP request. - pub fn http_mut(&mut self) -> &mut http::Request { - &mut self.inner - } - - /// Gives readonly access to the underlying HTTP request. - pub fn http(&self) -> &http::Request { - &self.inner - } - - /// Attempts to clone the operation `Request`. This can fail if the - /// request body can't be cloned, such as if it is being streamed and the - /// stream can't be recreated. - pub fn try_clone(&self) -> Option { - let cloned_body = self.inner.body().try_clone()?; - let mut cloned_request = http::Request::builder() - .uri(self.inner.uri().clone()) - .method(self.inner.method()); - *cloned_request - .headers_mut() - .expect("builder has not been modified, headers must be valid") = - self.inner.headers().clone(); - let inner = cloned_request - .body(cloned_body) - .expect("a clone of a valid request should be a valid request"); - Some(Request { - inner, - properties: self.properties.clone(), - }) - } - - /// Consumes the operation `Request` and returns the underlying HTTP request and properties. - pub fn into_parts(self) -> (http::Request, SharedPropertyBag) { - (self.inner, self.properties) - } -} - -// TODO(enableNewSmithyRuntimeCleanup): Delete `operation::Response` when cleaning up middleware -/// Operation response type that associates a property bag with an underlying HTTP response. -/// This type represents the response in the Tower `Service` in middleware so that middleware -/// can share information with each other via the properties. -#[derive(Debug)] -pub struct Response { - /// The underlying HTTP Response - inner: http::Response, - - /// Property bag of configuration options - properties: SharedPropertyBag, -} - -impl Response { - /// Creates a new operation `Response` with the given `inner` HTTP response. - pub fn new(inner: http::Response) -> Self { - Response { - inner, - properties: SharedPropertyBag::new(), - } - } - - /// Gives mutable access to the properties. - pub fn properties_mut(&mut self) -> impl DerefMut + '_ { - self.properties.acquire_mut() - } - - /// Gives readonly access to the properties. - pub fn properties(&self) -> impl Deref + '_ { - self.properties.acquire() - } - - /// Gives mutable access to the underlying HTTP response. - pub fn http_mut(&mut self) -> &mut http::Response { - &mut self.inner - } - - /// Gives readonly access to the underlying HTTP response. - pub fn http(&self) -> &http::Response { - &self.inner - } - - /// Consumes the operation `Request` and returns the underlying HTTP response and properties. - pub fn into_parts(self) -> (http::Response, SharedPropertyBag) { - (self.inner, self.properties) - } - - /// Return mutable references to the response and property bag contained within this `operation::Response` - pub fn parts_mut( - &mut self, - ) -> ( - &mut http::Response, - impl DerefMut + '_, - ) { - (&mut self.inner, self.properties.acquire_mut()) - } - - /// Creates a new operation `Response` from an HTTP response and property bag. - pub fn from_parts(inner: http::Response, properties: SharedPropertyBag) -> Self { - Response { inner, properties } - } -} - -#[cfg(test)] -mod test { - use crate::body::SdkBody; - use crate::operation::Request; - use http::header::{AUTHORIZATION, CONTENT_LENGTH}; - use http::Uri; - - #[test] - fn try_clone_clones_all_data() { - let mut request = Request::new( - http::Request::builder() - .uri(Uri::from_static("http://www.amazon.com")) - .method("POST") - .header(CONTENT_LENGTH, 456) - .header(AUTHORIZATION, "Token: hello") - .body(SdkBody::from("hello world!")) - .expect("valid request"), - ); - request.properties_mut().insert("hello"); - let cloned = request.try_clone().expect("request is cloneable"); - - let (request, config) = cloned.into_parts(); - assert_eq!(request.uri(), &Uri::from_static("http://www.amazon.com")); - assert_eq!(request.method(), "POST"); - assert_eq!(request.headers().len(), 2); - assert_eq!( - request.headers().get(AUTHORIZATION).unwrap(), - "Token: hello" - ); - assert_eq!(request.headers().get(CONTENT_LENGTH).unwrap(), "456"); - assert_eq!(request.body().bytes().unwrap(), "hello world!".as_bytes()); - assert_eq!(config.acquire().get::<&str>(), Some(&"hello")); - } -} diff --git a/rust-runtime/aws-smithy-http/src/property_bag.rs b/rust-runtime/aws-smithy-http/src/property_bag.rs deleted file mode 100644 index 1ac6adc989..0000000000 --- a/rust-runtime/aws-smithy-http/src/property_bag.rs +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! A typemap used to store configuration for smithy operations. -//! -//! This code is functionally equivalent to `Extensions` in the `http` crate. Examples -//! have been updated to be more relevant for smithy use, the interface has been made public, -//! and the doc comments have been updated to reflect how the property bag is used in the SDK. -//! Additionally, optimizations around the HTTP use case have been removed in favor or simpler code. - -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt; -use std::fmt::{Debug, Formatter}; -use std::hash::{BuildHasherDefault, Hasher}; -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, Mutex}; - -type AnyMap = HashMap>; - -struct NamedType { - name: &'static str, - value: Box, -} - -impl NamedType { - fn as_mut(&mut self) -> Option<&mut T> { - self.value.downcast_mut() - } - - fn as_ref(&self) -> Option<&T> { - self.value.downcast_ref() - } - - fn assume(self) -> Option { - self.value.downcast().map(|t| *t).ok() - } - - fn new(value: T) -> Self { - Self { - name: std::any::type_name::(), - value: Box::new(value), - } - } -} - -// With TypeIds as keys, there's no need to hash them. They are already hashes -// themselves, coming from the compiler. The IdHasher just holds the u64 of -// the TypeId, and then returns it, instead of doing any bit fiddling. -#[derive(Default)] -struct IdHasher(u64); - -impl Hasher for IdHasher { - #[inline] - fn finish(&self) -> u64 { - self.0 - } - - fn write(&mut self, _: &[u8]) { - unreachable!("TypeId calls write_u64"); - } - - #[inline] - fn write_u64(&mut self, id: u64) { - self.0 = id; - } -} - -/// A type-map of configuration data. -/// -/// `PropertyBag` can be used by `Request` and `Response` to store -/// data used to configure the SDK request pipeline. -#[derive(Default)] -pub struct PropertyBag { - // In http where this property bag is usually empty, this makes sense. We will almost always put - // something in the bag, so we could consider removing the layer of indirection. - map: AnyMap, -} - -impl PropertyBag { - /// Create an empty `PropertyBag`. - #[inline] - pub fn new() -> PropertyBag { - PropertyBag { - map: AnyMap::default(), - } - } - - /// Insert a type into this `PropertyBag`. - /// - /// If a value of this type already existed, it will be returned. - /// - /// # Examples - /// - /// ``` - /// # use aws_smithy_http::property_bag::PropertyBag; - /// let mut props = PropertyBag::new(); - /// - /// #[derive(Debug, Eq, PartialEq)] - /// struct Endpoint(&'static str); - /// assert!(props.insert(Endpoint("dynamo.amazon.com")).is_none()); - /// assert_eq!( - /// props.insert(Endpoint("kinesis.amazon.com")), - /// Some(Endpoint("dynamo.amazon.com")) - /// ); - /// ``` - pub fn insert(&mut self, val: T) -> Option { - self.map - .insert(TypeId::of::(), NamedType::new(val)) - .and_then(|val| val.assume()) - } - - /// Get a reference to a type previously inserted on this `PropertyBag`. - /// - /// # Examples - /// - /// ``` - /// # use aws_smithy_http::property_bag::PropertyBag; - /// let mut props = PropertyBag::new(); - /// assert!(props.get::().is_none()); - /// props.insert(5i32); - /// - /// assert_eq!(props.get::(), Some(&5i32)); - /// ``` - pub fn get(&self) -> Option<&T> { - self.map - .get(&TypeId::of::()) - .and_then(|val| val.as_ref()) - } - - /// Returns an iterator of the types contained in this PropertyBag - /// - /// # Stability - /// This method is unstable and may be removed or changed in a future release. The exact - /// format of the returned types may also change. - pub fn contents(&self) -> impl Iterator + '_ { - self.map.values().map(|tpe| tpe.name) - } - - /// Get a mutable reference to a type previously inserted on this `PropertyBag`. - /// - /// # Examples - /// - /// ``` - /// # use aws_smithy_http::property_bag::PropertyBag; - /// let mut props = PropertyBag::new(); - /// props.insert(String::from("Hello")); - /// props.get_mut::().unwrap().push_str(" World"); - /// - /// assert_eq!(props.get::().unwrap(), "Hello World"); - /// ``` - pub fn get_mut(&mut self) -> Option<&mut T> { - self.map - .get_mut(&TypeId::of::()) - .map(|val| val.as_mut().expect("type mismatch!")) - } - - /// Remove a type from this `PropertyBag`. - /// - /// If a value of this type existed, it will be returned. - /// - /// # Examples - /// - /// ``` - /// # use aws_smithy_http::property_bag::PropertyBag; - /// let mut props = PropertyBag::new(); - /// props.insert(5i32); - /// assert_eq!(props.remove::(), Some(5i32)); - /// assert!(props.get::().is_none()); - /// ``` - pub fn remove(&mut self) -> Option { - self.map.remove(&TypeId::of::()).and_then(|tpe| { - (tpe.value as Box) - .downcast() - .ok() - .map(|boxed| *boxed) - }) - } - - /// Clear the `PropertyBag` of all inserted extensions. - /// - /// # Examples - /// - /// ``` - /// # use aws_smithy_http::property_bag::PropertyBag; - /// let mut props = PropertyBag::new(); - /// props.insert(5i32); - /// props.clear(); - /// - /// assert!(props.get::().is_none()); - #[inline] - pub fn clear(&mut self) { - self.map.clear(); - } -} - -impl fmt::Debug for PropertyBag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut fmt = f.debug_struct("PropertyBag"); - - struct Contents<'a>(&'a PropertyBag); - impl<'a> Debug for Contents<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.0.contents()).finish() - } - } - fmt.field("contents", &Contents(self)); - fmt.finish() - } -} - -/// A wrapper of [`PropertyBag`] that can be safely shared across threads and cheaply cloned. -/// -/// To access properties, use either `acquire` or `acquire_mut`. This can be one line for -/// single property accesses, for example: -/// ```rust -/// # use aws_smithy_http::property_bag::SharedPropertyBag; -/// # let properties = SharedPropertyBag::new(); -/// let my_string = properties.acquire().get::(); -/// ``` -/// -/// For multiple accesses, the acquire result should be stored as a local since calling -/// acquire repeatedly will be slower than calling it once: -/// ```rust -/// # use aws_smithy_http::property_bag::SharedPropertyBag; -/// # let properties = SharedPropertyBag::new(); -/// let props = properties.acquire(); -/// let my_string = props.get::(); -/// let my_vec = props.get::>(); -/// ``` -/// -/// Use `acquire_mut` to insert properties into the bag: -/// ```rust -/// # use aws_smithy_http::property_bag::SharedPropertyBag; -/// # let properties = SharedPropertyBag::new(); -/// properties.acquire_mut().insert("example".to_string()); -/// ``` -#[derive(Clone, Debug, Default)] -pub struct SharedPropertyBag(Arc>); - -impl SharedPropertyBag { - /// Create an empty `SharedPropertyBag`. - pub fn new() -> Self { - SharedPropertyBag(Arc::new(Mutex::new(PropertyBag::new()))) - } - - /// Acquire an immutable reference to the property bag. - pub fn acquire(&self) -> impl Deref + '_ { - self.0.lock().unwrap() - } - - /// Acquire a mutable reference to the property bag. - pub fn acquire_mut(&self) -> impl DerefMut + '_ { - self.0.lock().unwrap() - } -} - -impl From for SharedPropertyBag { - fn from(bag: PropertyBag) -> Self { - SharedPropertyBag(Arc::new(Mutex::new(bag))) - } -} - -#[cfg(test)] -mod test { - use crate::property_bag::PropertyBag; - - #[test] - fn test_extensions() { - #[derive(Debug, PartialEq)] - struct MyType(i32); - - let mut property_bag = PropertyBag::new(); - - property_bag.insert(5i32); - property_bag.insert(MyType(10)); - - assert_eq!(property_bag.get(), Some(&5i32)); - assert_eq!(property_bag.get_mut(), Some(&mut 5i32)); - - assert_eq!(property_bag.remove::(), Some(5i32)); - assert!(property_bag.get::().is_none()); - - assert_eq!(property_bag.get::(), None); - assert_eq!(property_bag.get(), Some(&MyType(10))); - assert_eq!( - format!("{:?}", property_bag), - r#"PropertyBag { contents: ["aws_smithy_http::property_bag::test::test_extensions::MyType"] }"# - ); - } -} diff --git a/rust-runtime/aws-smithy-http/src/response.rs b/rust-runtime/aws-smithy-http/src/response.rs deleted file mode 100644 index 7ba039f419..0000000000 --- a/rust-runtime/aws-smithy-http/src/response.rs +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Types for response parsing. - -use crate::operation; -use bytes::Bytes; - -/// `ParseHttpResponse` is a generic trait for parsing structured data from HTTP responses. -/// -/// It is designed to be nearly infinitely flexible, because `Output` is unconstrained, it can be used to support -/// event streams, S3 streaming responses, regular request-response style operations, as well -/// as any other HTTP-based protocol that we manage to come up with. -/// -/// The split between `parse_unloaded` and `parse_loaded` enables keeping the parsing code pure and sync -/// whenever possible and delegating the process of actually reading the HTTP response to the caller when -/// the required behavior is simply "read to the end." -/// -/// It also enables this critical and core trait to avoid being async, and it makes code that uses -/// the trait easier to test. -pub trait ParseHttpResponse { - /// Output type of the HttpResponse. - /// - /// For request/response style operations, this is typically something like: - /// `Result` - /// - /// For streaming operations, this is something like: - /// `Result, TranscribeStreamingError>` - type Output; - - /// Parse an HTTP request without reading the body. If the body must be provided to proceed, - /// return `None` - /// - /// This exists to serve APIs like S3::GetObject where the body is passed directly into the - /// response and consumed by the client. However, even in the case of S3::GetObject, errors - /// require reading the entire body. - /// - /// This also facilitates `EventStream` and other streaming HTTP protocols by enabling the - /// handler to take ownership of the HTTP response directly. - /// - /// Currently `parse_unloaded` operates on a borrowed HTTP request to enable - /// the caller to provide a raw HTTP response to the caller for inspection after the response is - /// returned. For EventStream-like use cases, the caller can use `mem::swap` to replace - /// the streaming body with an empty body as long as the body implements default. - /// - /// We should consider if this is too limiting & if this should take an owned response instead. - fn parse_unloaded(&self, response: &mut operation::Response) -> Option; - - /// Parse an HTTP request from a fully loaded body. This is for standard request/response style - /// APIs like AwsJson 1.0/1.1 and the error path of most streaming APIs - /// - /// Using an explicit body type of Bytes here is a conscious decision—If you _really_ need - /// to precisely control how the data is loaded into memory (e.g. by using `bytes::Buf`), implement - /// your handler in `parse_unloaded`. - /// - /// Production code will never call `parse_loaded` without first calling `parse_unloaded`. However, - /// in tests it may be easier to use `parse_loaded` directly. It is OK to panic in `parse_loaded` - /// if `parse_unloaded` will never return `None`, however, it may make your code easier to test if an - /// implementation is provided. - fn parse_loaded(&self, response: &http::Response) -> Self::Output; - - /// Returns whether the contents of this response are sensitive - /// - /// When this is set to true, wire logging will be disabled - fn sensitive(&self) -> bool { - false - } -} - -/// Convenience Trait for non-streaming APIs -/// -/// `ParseStrictResponse` enables operations that _never_ need to stream the body incrementally to -/// have cleaner implementations. There is a blanket implementation -pub trait ParseStrictResponse { - /// The type returned by this parser. - type Output; - - /// Parse an [`http::Response`] into `Self::Output`. - fn parse(&self, response: &http::Response) -> Self::Output; - - /// Returns whether the contents of this response are sensitive - /// - /// When this is set to true, wire logging will be disabled - fn sensitive(&self) -> bool { - false - } -} - -impl ParseHttpResponse for T { - type Output = T::Output; - - fn parse_unloaded(&self, _response: &mut operation::Response) -> Option { - None - } - - fn parse_loaded(&self, response: &http::Response) -> Self::Output { - self.parse(response) - } - - fn sensitive(&self) -> bool { - ParseStrictResponse::sensitive(self) - } -} - -#[cfg(test)] -mod test { - use crate::body::SdkBody; - use crate::operation; - use crate::response::ParseHttpResponse; - use bytes::Bytes; - use std::mem; - - #[test] - fn supports_streaming_body() { - struct S3GetObject { - _body: SdkBody, - } - - struct S3GetObjectParser; - - impl ParseHttpResponse for S3GetObjectParser { - type Output = S3GetObject; - - fn parse_unloaded(&self, response: &mut operation::Response) -> Option { - // For responses that pass on the body, use mem::take to leave behind an empty body - let body = mem::replace(response.http_mut().body_mut(), SdkBody::taken()); - Some(S3GetObject { _body: body }) - } - - fn parse_loaded(&self, _response: &http::Response) -> Self::Output { - unimplemented!() - } - } - } -} diff --git a/rust-runtime/aws-smithy-http/src/result.rs b/rust-runtime/aws-smithy-http/src/result.rs index c86e9c5f88..fbe6494498 100644 --- a/rust-runtime/aws-smithy-http/src/result.rs +++ b/rust-runtime/aws-smithy-http/src/result.rs @@ -5,29 +5,17 @@ //! `Result` wrapper types for [success](SdkSuccess) and [failure](SdkError) responses. -use std::error::Error; -use std::fmt; -use std::fmt::{Debug, Display, Formatter}; - +use crate::body::SdkBody; +use crate::connection::ConnectionMetadata; use aws_smithy_types::error::metadata::{ProvideErrorMetadata, EMPTY_ERROR_METADATA}; use aws_smithy_types::error::ErrorMetadata; use aws_smithy_types::retry::ErrorKind; - -use crate::connection::ConnectionMetadata; -use crate::operation; +use std::error::Error; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; type BoxError = Box; -/// Successful SDK Result -#[derive(Debug)] -pub struct SdkSuccess { - /// Raw Response from the service. (e.g. Http Response) - pub raw: operation::Response, - - /// Parsed response from the service - pub parsed: O, -} - /// Builders for `SdkError` variant context. pub mod builders { use super::*; @@ -329,7 +317,7 @@ pub trait CreateUnhandledError { /// [`Error::source`](std::error::Error::source) for more details about the underlying cause. #[non_exhaustive] #[derive(Debug)] -pub enum SdkError { +pub enum SdkError> { /// The request failed during construction. It was not dispatched over the network. ConstructionFailure(ConstructionFailure), diff --git a/rust-runtime/aws-smithy-http/src/retry.rs b/rust-runtime/aws-smithy-http/src/retry.rs deleted file mode 100644 index eaf7d0e093..0000000000 --- a/rust-runtime/aws-smithy-http/src/retry.rs +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! HTTP specific retry behaviors -//! -//! For protocol agnostic retries, see `aws_smithy_types::Retry`. - -use crate::operation::Response; -use crate::result::SdkError; -use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind, RetryKind}; - -/// Classifies what kind of retry is needed for a given `response`. -pub trait ClassifyRetry: Clone { - /// Run this classifier against a response to determine if it should be retried. - fn classify_retry(&self, response: Result<&T, &E>) -> RetryKind; -} - -const TRANSIENT_ERROR_STATUS_CODES: &[u16] = &[500, 502, 503, 504]; - -/// The default implementation of [`ClassifyRetry`] for generated clients. -#[derive(Clone, Debug, Default)] -pub struct DefaultResponseRetryClassifier; - -impl DefaultResponseRetryClassifier { - /// Creates a new `DefaultResponseRetryClassifier` - pub fn new() -> Self { - Default::default() - } - - /// Matches on the given `result` and, if possible, returns the underlying cause and the operation response - /// that can be used for further classification logic. Otherwise, it returns a `RetryKind` that should be used - /// for the result. - // - // IMPORTANT: This function is used by the AWS SDK in `aws-http` for the SDK's own response classification logic - #[doc(hidden)] - pub fn try_extract_err_response<'a, T, E>( - result: Result<&T, &'a SdkError>, - ) -> Result<(&'a E, &'a Response), RetryKind> { - match result { - Ok(_) => Err(RetryKind::Unnecessary), - Err(SdkError::ServiceError(context)) => Ok((context.err(), context.raw())), - Err(SdkError::TimeoutError(_err)) => Err(RetryKind::Error(ErrorKind::TransientError)), - Err(SdkError::DispatchFailure(err)) => { - if err.is_timeout() || err.is_io() { - Err(RetryKind::Error(ErrorKind::TransientError)) - } else if let Some(ek) = err.as_other() { - Err(RetryKind::Error(ek)) - } else { - Err(RetryKind::UnretryableFailure) - } - } - Err(SdkError::ResponseError { .. }) => Err(RetryKind::Error(ErrorKind::TransientError)), - Err(SdkError::ConstructionFailure(_)) => Err(RetryKind::UnretryableFailure), - } - } -} - -impl ClassifyRetry> for DefaultResponseRetryClassifier -where - E: ProvideErrorKind, -{ - fn classify_retry(&self, result: Result<&T, &SdkError>) -> RetryKind { - let (err, response) = match Self::try_extract_err_response(result) { - Ok(extracted) => extracted, - Err(retry_kind) => return retry_kind, - }; - if let Some(kind) = err.retryable_error_kind() { - return RetryKind::Error(kind); - }; - if TRANSIENT_ERROR_STATUS_CODES.contains(&response.http().status().as_u16()) { - return RetryKind::Error(ErrorKind::TransientError); - }; - RetryKind::UnretryableFailure - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::body::SdkBody; - use crate::operation; - use crate::result::{SdkError, SdkSuccess}; - use crate::retry::ClassifyRetry; - use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind, RetryKind}; - use std::fmt; - - #[derive(Debug)] - struct UnmodeledError; - impl fmt::Display for UnmodeledError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "UnmodeledError") - } - } - impl std::error::Error for UnmodeledError {} - - struct CodedError { - code: &'static str, - } - - impl ProvideErrorKind for UnmodeledError { - fn retryable_error_kind(&self) -> Option { - None - } - - fn code(&self) -> Option<&str> { - None - } - } - - impl ProvideErrorKind for CodedError { - fn retryable_error_kind(&self) -> Option { - None - } - - fn code(&self) -> Option<&str> { - Some(self.code) - } - } - - fn make_err( - err: E, - raw: http::Response<&'static str>, - ) -> Result, SdkError> { - Err(SdkError::service_error( - err, - operation::Response::new(raw.map(SdkBody::from)), - )) - } - - #[test] - fn not_an_error() { - let policy = DefaultResponseRetryClassifier::new(); - let test_response = http::Response::new("OK"); - assert_eq!( - policy.classify_retry(make_err(UnmodeledError, test_response).as_ref()), - RetryKind::UnretryableFailure - ); - } - - #[test] - fn classify_by_response_status() { - let policy = DefaultResponseRetryClassifier::new(); - let test_resp = http::Response::builder() - .status(500) - .body("error!") - .unwrap(); - assert_eq!( - policy.classify_retry(make_err(UnmodeledError, test_resp).as_ref()), - RetryKind::Error(ErrorKind::TransientError) - ); - } - - #[test] - fn classify_by_response_status_not_retryable() { - let policy = DefaultResponseRetryClassifier::new(); - let test_resp = http::Response::builder() - .status(408) - .body("error!") - .unwrap(); - assert_eq!( - policy.classify_retry(make_err(UnmodeledError, test_resp).as_ref()), - RetryKind::UnretryableFailure - ); - } - - #[test] - fn classify_by_error_kind() { - struct ModeledRetries; - let test_response = http::Response::new("OK"); - impl ProvideErrorKind for ModeledRetries { - fn retryable_error_kind(&self) -> Option { - Some(ErrorKind::ClientError) - } - - fn code(&self) -> Option<&str> { - // code should not be called when `error_kind` is provided - unimplemented!() - } - } - - let policy = DefaultResponseRetryClassifier::new(); - - assert_eq!( - policy.classify_retry(make_err(ModeledRetries, test_response).as_ref()), - RetryKind::Error(ErrorKind::ClientError) - ); - } - - #[test] - fn classify_response_error() { - let policy = DefaultResponseRetryClassifier::new(); - assert_eq!( - policy.classify_retry( - Result::, SdkError>::Err(SdkError::response_error( - UnmodeledError, - operation::Response::new(http::Response::new("OK").map(SdkBody::from)), - )) - .as_ref() - ), - RetryKind::Error(ErrorKind::TransientError) - ); - } - - #[test] - fn test_timeout_error() { - let policy = DefaultResponseRetryClassifier::new(); - let err: Result<(), SdkError> = Err(SdkError::timeout_error("blah")); - assert_eq!( - policy.classify_retry(err.as_ref()), - RetryKind::Error(ErrorKind::TransientError) - ); - } -}