Concrete body in SdkResult/SdkError (#247)

* Refactor out generic `B` parameter from SdkResult / SdkError

* Switch to from_static

* Add docs, fix old broken docs

* Remove unused From<Bytes> bound

* Rustfmt run
This commit is contained in:
Russell Cohen 2021-03-10 18:28:05 -05:00 committed by GitHub
parent d75b7605b1
commit 6b38bf718e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 90 additions and 62 deletions

View File

@ -48,11 +48,11 @@ impl Default for AwsErrorRetryPolicy {
} }
} }
impl<T, E, B> ClassifyResponse<T, SdkError<E, B>> for AwsErrorRetryPolicy impl<T, E> ClassifyResponse<T, SdkError<E>> for AwsErrorRetryPolicy
where where
E: ProvideErrorKind, E: ProvideErrorKind,
{ {
fn classify(&self, err: Result<&T, &SdkError<E, B>>) -> RetryKind { fn classify(&self, err: Result<&T, &SdkError<E>>) -> RetryKind {
let (err, response) = match err { let (err, response) = match err {
Ok(_) => return RetryKind::NotRetryable, Ok(_) => return RetryKind::NotRetryable,
Err(SdkError::ServiceError { err, raw }) => (err, raw), Err(SdkError::ServiceError { err, raw }) => (err, raw),
@ -88,6 +88,7 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::AwsErrorRetryPolicy; use crate::AwsErrorRetryPolicy;
use smithy_http::middleware::ResponseBody;
use smithy_http::result::{SdkError, SdkSuccess}; use smithy_http::result::{SdkError, SdkSuccess};
use smithy_http::retry::ClassifyResponse; use smithy_http::retry::ClassifyResponse;
use smithy_types::retry::{ErrorKind, ProvideErrorKind, RetryKind}; use smithy_types::retry::{ErrorKind, ProvideErrorKind, RetryKind};
@ -119,8 +120,14 @@ mod test {
} }
} }
fn make_err<E, B>(err: E, raw: http::Response<B>) -> Result<SdkSuccess<(), B>, SdkError<E, B>> { fn make_err<E>(
Err(SdkError::ServiceError { err, raw }) err: E,
raw: http::Response<&'static str>,
) -> Result<SdkSuccess<()>, SdkError<E>> {
Err(SdkError::ServiceError {
err,
raw: raw.map(|b| ResponseBody::from_static(b)),
})
} }
#[test] #[test]

View File

@ -21,7 +21,9 @@ impl Standard {
/// An https connection /// An https connection
pub fn https() -> Self { pub fn https() -> Self {
let https = HttpsConnector::new(); let https = HttpsConnector::new();
Self(Connector::Https(hyper::Client::builder().build::<_, SdkBody>(https))) Self(Connector::Https(
hyper::Client::builder().build::<_, SdkBody>(https),
))
} }
/// A connection based on the provided `impl HttpService` /// A connection based on the provided `impl HttpService`

View File

@ -14,6 +14,7 @@ use aws_sig_auth::signer::SigV4Signer;
use smithy_http::body::SdkBody; use smithy_http::body::SdkBody;
use smithy_http::operation::Operation; use smithy_http::operation::Operation;
use smithy_http::response::ParseHttpResponse; use smithy_http::response::ParseHttpResponse;
pub use smithy_http::result::{SdkError, SdkSuccess};
use smithy_http::retry::ClassifyResponse; use smithy_http::retry::ClassifyResponse;
use smithy_http_tower::dispatch::DispatchLayer; use smithy_http_tower::dispatch::DispatchLayer;
use smithy_http_tower::map_request::MapRequestLayer; use smithy_http_tower::map_request::MapRequestLayer;
@ -27,9 +28,6 @@ use tower::{Service, ServiceBuilder, ServiceExt};
type BoxError = Box<dyn Error + Send + Sync>; type BoxError = Box<dyn Error + Send + Sync>;
pub type StandardClient = Client<conn::Standard>; pub type StandardClient = Client<conn::Standard>;
pub type SdkError<E> = smithy_http::result::SdkError<E, hyper::Body>;
pub type SdkSuccess<T> = smithy_http::result::SdkSuccess<T, hyper::Body>;
/// AWS Service Client /// AWS Service Client
/// ///
/// Hyper-based AWS Service Client. Most customers will want to construct a client with /// Hyper-based AWS Service Client. Most customers will want to construct a client with
@ -138,8 +136,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{Client, conn};
use crate::test_connection::TestConnection; use crate::test_connection::TestConnection;
use crate::{conn, Client};
#[test] #[test]
fn construct_default_client() { fn construct_default_client() {

View File

@ -5,13 +5,13 @@
use http::header::{HeaderName, CONTENT_TYPE}; use http::header::{HeaderName, CONTENT_TYPE};
use http::Request; use http::Request;
use protocol_test_helpers::{assert_ok, validate_body, MediaType};
use smithy_http::body::SdkBody; use smithy_http::body::SdkBody;
use std::future::Ready; use std::future::Ready;
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tower::BoxError; use tower::BoxError;
use protocol_test_helpers::{validate_body, MediaType, assert_ok};
type ConnectVec<B> = Vec<(http::Request<SdkBody>, http::Response<B>)>; type ConnectVec<B> = Vec<(http::Request<SdkBody>, http::Response<B>)>;
@ -34,14 +34,18 @@ impl ValidateRequest {
} }
let actual_str = std::str::from_utf8(actual.body().bytes().unwrap_or(&[])); let actual_str = std::str::from_utf8(actual.body().bytes().unwrap_or(&[]));
let expected_str = std::str::from_utf8(expected.body().bytes().unwrap_or(&[])); let expected_str = std::str::from_utf8(expected.body().bytes().unwrap_or(&[]));
let media_type = if actual.headers().get(CONTENT_TYPE).map(|v| v.to_str().unwrap().contains("json")).unwrap_or(false) { let media_type = if actual
.headers()
.get(CONTENT_TYPE)
.map(|v| v.to_str().unwrap().contains("json"))
.unwrap_or(false)
{
MediaType::Json MediaType::Json
} else { } else {
MediaType::Other("unknown".to_string()) MediaType::Other("unknown".to_string())
}; };
match (actual_str, expected_str) { match (actual_str, expected_str) {
(Ok(actual), Ok(expected)) => (Ok(actual), Ok(expected)) => assert_ok(validate_body(actual, expected, media_type)),
assert_ok(validate_body(actual, expected, media_type)),
_ => assert_eq!(actual.body().bytes(), expected.body().bytes()), _ => assert_eq!(actual.body().bytes(), expected.body().bytes()),
}; };
assert_eq!(actual.uri(), expected.uri()); assert_eq!(actual.uri(), expected.uri());

View File

@ -8,7 +8,7 @@ use aws_endpoint::{set_endpoint_resolver, DefaultAwsEndpointResolver};
use aws_http::user_agent::AwsUserAgent; use aws_http::user_agent::AwsUserAgent;
use aws_http::AwsErrorRetryPolicy; use aws_http::AwsErrorRetryPolicy;
use aws_hyper::test_connection::TestConnection; use aws_hyper::test_connection::TestConnection;
use aws_hyper::{Client, RetryConfig, SdkError}; use aws_hyper::{Client, RetryConfig};
use aws_sig_auth::signer::OperationSigningConfig; use aws_sig_auth::signer::OperationSigningConfig;
use aws_types::region::Region; use aws_types::region::Region;
use bytes::Bytes; use bytes::Bytes;
@ -18,6 +18,7 @@ use smithy_http::body::SdkBody;
use smithy_http::operation; use smithy_http::operation;
use smithy_http::operation::Operation; use smithy_http::operation::Operation;
use smithy_http::response::ParseHttpResponse; use smithy_http::response::ParseHttpResponse;
use smithy_http::result::SdkError;
use smithy_types::retry::{ErrorKind, ProvideErrorKind}; use smithy_types::retry::{ErrorKind, ProvideErrorKind};
use std::convert::Infallible; use std::convert::Infallible;
use std::error::Error; use std::error::Error;

View File

@ -9,8 +9,8 @@ use aws_hyper::test_connection::TestConnection;
use aws_hyper::Client; use aws_hyper::Client;
use http::Uri; use http::Uri;
use kms::operation::GenerateRandom; use kms::operation::GenerateRandom;
use kms::{Config, Region};
use smithy_http::body::SdkBody; use smithy_http::body::SdkBody;
use kms::{Config, Region};
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
// TODO: having the full HTTP requests right in the code is a bit gross, consider something // TODO: having the full HTTP requests right in the code is a bit gross, consider something

View File

@ -10,6 +10,7 @@ use kms::Blob;
use smithy_http::result::{SdkError, SdkSuccess}; use smithy_http::result::{SdkError, SdkSuccess};
use smithy_http::retry::ClassifyResponse; use smithy_http::retry::ClassifyResponse;
use smithy_types::retry::{ErrorKind, RetryKind}; use smithy_types::retry::{ErrorKind, RetryKind};
use smithy_http::middleware::ResponseBody;
#[test] #[test]
fn validate_sensitive_trait() { fn validate_sensitive_trait() {
@ -29,9 +30,9 @@ fn errors_are_retryable() {
let conf = kms::Config::builder().build(); let conf = kms::Config::builder().build();
let op = CreateAlias::builder().build(&conf); let op = CreateAlias::builder().build(&conf);
let err = Result::<SdkSuccess<CreateAliasOutput, &str>, SdkError<CreateAliasError, &str>>::Err( let err = Result::<SdkSuccess<CreateAliasOutput>, SdkError<CreateAliasError>>::Err(
SdkError::ServiceError { SdkError::ServiceError {
raw: http::Response::builder().body("resp").unwrap(), raw: http::Response::builder().body(ResponseBody::from_static("resp")).unwrap(),
err, err,
}, },
); );

View File

@ -33,7 +33,7 @@ pub enum SendOperationError {
} }
/// Convert a `SendOperationError` into an `SdkError` /// Convert a `SendOperationError` into an `SdkError`
impl<E, B> From<SendOperationError> for SdkError<E, B> { impl<E> From<SendOperationError> for SdkError<E> {
fn from(err: SendOperationError) -> Self { fn from(err: SendOperationError) -> Self {
match err { match err {
SendOperationError::RequestDispatchError(e) => { SendOperationError::RequestDispatchError(e) => {

View File

@ -4,7 +4,6 @@
*/ */
use crate::SendOperationError; use crate::SendOperationError;
use bytes::Bytes;
use smithy_http::middleware::load_response; use smithy_http::middleware::load_response;
use smithy_http::operation; use smithy_http::operation;
use smithy_http::operation::Operation; use smithy_http::operation::Operation;
@ -73,13 +72,13 @@ impl<S, O, T, E, B, R> tower::Service<operation::Operation<O, R>> for ParseRespo
where where
S: Service<operation::Request, Response = http::Response<B>, Error = SendOperationError>, S: Service<operation::Request, Response = http::Response<B>, Error = SendOperationError>,
S::Future: 'static, S::Future: 'static,
B: http_body::Body + Unpin + From<Bytes> + 'static, B: http_body::Body + Unpin + 'static,
B::Error: Into<BoxError>, B::Error: Into<BoxError>,
O: ParseHttpResponse<B, Output = Result<T, E>> + 'static, O: ParseHttpResponse<B, Output = Result<T, E>> + 'static,
E: Error, E: Error,
{ {
type Response = smithy_http::result::SdkSuccess<T, B>; type Response = smithy_http::result::SdkSuccess<T>;
type Error = smithy_http::result::SdkError<E, B>; type Error = smithy_http::result::SdkError<E>;
type Future = BoxedResultFuture<Self::Response, Self::Error>; type Future = BoxedResultFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {

View File

@ -16,6 +16,43 @@ use bytes::{Buf, Bytes};
use http_body::Body; use http_body::Body;
use std::error::Error; use std::error::Error;
/// Body for debugging purposes
///
/// When receiving data from the AWS services, it is often helpful to be able to see the response
/// body that a service generated. When the SDK has fully buffered the body into memory, this
/// facilitates straightforward debugging of the response.
///
/// Take care when calling the debug implementation to avoid printing responses from sensitive operations.
#[derive(Debug)]
pub struct ResponseBody(Inner);
impl ResponseBody {
/// Load a response body from a static string
pub fn from_static(s: &'static str) -> Self {
ResponseBody(Inner::Bytes(Bytes::from_static(s.as_bytes())))
}
/// Returns the raw bytes of this response
///
/// When the response has been buffered into memory, the bytes are returned
/// If the response is streaming or errored during the read process, `None` is returned.
pub fn bytes(&self) -> Option<&[u8]> {
match &self.0 {
Inner::Bytes(bytes) => Some(&bytes),
_ => None
}
}
}
/// Private ResponseBody internals
#[derive(Debug)]
enum Inner {
Bytes(bytes::Bytes),
Streaming,
Err,
}
type BoxError = Box<dyn Error + Send + Sync>; type BoxError = Box<dyn Error + Send + Sync>;
/// [`MapRequest`] defines a synchronous middleware that transforms an [`operation::Request`]. /// [`MapRequest`] defines a synchronous middleware that transforms an [`operation::Request`].
@ -72,22 +109,21 @@ pub trait MapRequest {
pub async fn load_response<B, T, E, O>( pub async fn load_response<B, T, E, O>(
mut response: http::Response<B>, mut response: http::Response<B>,
handler: &O, handler: &O,
) -> Result<SdkSuccess<T, B>, SdkError<E, B>> ) -> Result<SdkSuccess<T>, SdkError<E>>
where where
B: http_body::Body + Unpin, B: http_body::Body + Unpin,
B: From<Bytes> + 'static,
B::Error: Into<BoxError>, B::Error: Into<BoxError>,
O: ParseHttpResponse<B, Output = Result<T, E>>, O: ParseHttpResponse<B, Output = Result<T, E>>,
{ {
if let Some(parsed_response) = handler.parse_unloaded(&mut response) { if let Some(parsed_response) = handler.parse_unloaded(&mut response) {
return sdk_result(parsed_response, response); return sdk_result(parsed_response, response.map(|_|ResponseBody(Inner::Streaming)));
} }
let body = match read_body(response.body_mut()).await { let body = match read_body(response.body_mut()).await {
Ok(body) => body, Ok(body) => body,
Err(e) => { Err(e) => {
return Err(SdkError::ResponseError { return Err(SdkError::ResponseError {
raw: response, raw: response.map(|_|ResponseBody(Inner::Err)),
err: e.into(), err: e.into(),
}); });
} }
@ -95,7 +131,7 @@ where
let response = response.map(|_| Bytes::from(body)); let response = response.map(|_| Bytes::from(body));
let parsed = handler.parse_loaded(&response); let parsed = handler.parse_loaded(&response);
sdk_result(parsed, response.map(B::from)) sdk_result(parsed, response.map(|body|ResponseBody(Inner::Bytes(body))))
} }
async fn read_body<B: http_body::Body>(body: B) -> Result<Vec<u8>, B::Error> { async fn read_body<B: http_body::Body>(body: B) -> Result<Vec<u8>, B::Error> {
@ -112,10 +148,10 @@ async fn read_body<B: http_body::Body>(body: B) -> Result<Vec<u8>, B::Error> {
} }
/// Convert a `Result<T, E>` into an `SdkResult` that includes the raw HTTP response /// Convert a `Result<T, E>` into an `SdkResult` that includes the raw HTTP response
fn sdk_result<T, E, B>( fn sdk_result<T, E>(
parsed: Result<T, E>, parsed: Result<T, E>,
raw: http::Response<B>, raw: http::Response<ResponseBody>,
) -> Result<SdkSuccess<T, B>, SdkError<E, B>> { ) -> Result<SdkSuccess<T>, SdkError<E>> {
match parsed { match parsed {
Ok(parsed) => Ok(SdkSuccess { raw, parsed }), Ok(parsed) => Ok(SdkSuccess { raw, parsed }),
Err(err) => Err(SdkError::ServiceError { raw, err }), Err(err) => Err(SdkError::ServiceError { raw, err }),

View File

@ -6,37 +6,19 @@
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use crate::middleware::ResponseBody;
type BoxError = Box<dyn Error + Send + Sync>; type BoxError = Box<dyn Error + Send + Sync>;
/// Successful Sdk Result /// Successful Sdk Result
///
/// Typically, transport implementations will type alias (or entirely wrap / transform) this type
/// plugging in a concrete body implementation, eg:
/// ```rust
/// # mod hyper {
/// # pub struct Body;
/// # }
/// type SdkSuccess<O> = smithy_http::result::SdkSuccess<O, hyper::Body>;
/// ```
#[derive(Debug)] #[derive(Debug)]
pub struct SdkSuccess<O, B> { pub struct SdkSuccess<O> {
pub raw: http::Response<B>, pub raw: http::Response<ResponseBody>,
pub parsed: O, pub parsed: O,
} }
/// Failing Sdk Result /// Failing Sdk Result
///
/// Typically, transport implementations will type alias (or entirely wrap / transform) this type
/// by specifying a concrete body implementation:
/// ```rust
/// # mod hyper {
/// # pub struct Body;
/// # }
/// type SdkError<E> = smithy_http::result::SdkError<E, hyper::Body>;
/// ```
#[derive(Debug)] #[derive(Debug)]
pub enum SdkError<E, B> { pub enum SdkError<E> {
/// The request failed during construction. It was not dispatched over the network. /// The request failed during construction. It was not dispatched over the network.
ConstructionFailure(BoxError), ConstructionFailure(BoxError),
@ -47,28 +29,26 @@ pub enum SdkError<E, B> {
/// A response was received but it was not parseable according the the protocol (for example /// A response was received but it was not parseable according the the protocol (for example
/// the server hung up while the body was being read) /// the server hung up while the body was being read)
ResponseError { ResponseError {
raw: http::Response<B>, raw: http::Response<ResponseBody>,
err: BoxError, err: BoxError,
}, },
/// An error response was received from the service /// An error response was received from the service
ServiceError { err: E, raw: http::Response<B> }, ServiceError { err: E, raw: http::Response<ResponseBody> },
} }
impl<E, B> Display for SdkError<E, B> impl<E> Display for SdkError<E>
where where
E: Error, E: Error,
B: Debug,
{ {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self) write!(f, "{:?}", self)
} }
} }
impl<E, B> Error for SdkError<E, B> impl<E> Error for SdkError<E>
where where
E: Error + 'static, E: Error + 'static,
B: Debug,
{ {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
match self { match self {