1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

pub mod dispatch;
pub mod map_request;
pub mod parse_response;

use aws_smithy_http::result::{ConnectorError, SdkError};
use tower::BoxError;

/// An Error Occurred During the process of sending an Operation
///
/// The variants are split to enable the final [SdkError](`aws_smithy_http::result::SdkError`) to differentiate
/// between two types of errors:
/// 1. [`RequestConstructionError`](SendOperationError::RequestConstructionError): Errors where the
///    SDK never attempted to dispatch the underlying `http::Request`. These represent errors that
///    occurred during the request construction pipeline. These generally stem from configuration issues.
/// 2. [`RequestDispatchError`](SendOperationError::RequestDispatchError): Errors where the inner
///    tower service failed (e.g. because the hostname couldn't be resolved, connection errors,
///    socket hangup etc.). In this case, we don't know how much of the request was _actually_ sent
///    to the client. We only know that we never got back an `http::Response` (and instead got an error).
///
/// `SendOperationError` is currently defined only in `aws-smithy-http-tower` because it may be removed
/// or replaced with `SdkError` in the future.
///
/// `SendOperationError` MAY be moved to a private module in the future.
#[derive(Debug)]
pub enum SendOperationError {
    /// The request could not be constructed
    ///
    /// These errors usually stem from configuration issues (e.g. no region, bad credential provider, etc.)
    RequestConstructionError(BoxError),

    /// The request could not be dispatched
    RequestDispatchError(ConnectorError),
}

/// Convert a `SendOperationError` into an `SdkError`
impl<E> From<SendOperationError> for SdkError<E> {
    fn from(err: SendOperationError) -> Self {
        match err {
            SendOperationError::RequestDispatchError(e) => {
                aws_smithy_http::result::SdkError::DispatchFailure(e)
            }
            SendOperationError::RequestConstructionError(e) => {
                aws_smithy_http::result::SdkError::ConstructionFailure(e)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::dispatch::DispatchLayer;
    use crate::map_request::MapRequestLayer;
    use crate::parse_response::ParseResponseLayer;
    use aws_smithy_http::body::SdkBody;
    use aws_smithy_http::middleware::MapRequest;
    use aws_smithy_http::operation;
    use aws_smithy_http::operation::{Operation, Request};
    use aws_smithy_http::response::ParseStrictResponse;
    use aws_smithy_http::result::ConnectorError;
    use bytes::Bytes;
    use http::Response;
    use std::convert::{Infallible, TryInto};
    use tower::{service_fn, Service, ServiceBuilder};

    /// Creates a stubbed service stack and runs it to validate that all the types line up &
    /// everything is properly wired
    #[tokio::test]
    async fn service_stack() {
        #[derive(Clone)]
        struct AddHeader;
        impl MapRequest for AddHeader {
            type Error = Infallible;
            fn apply(&self, request: Request) -> Result<Request, Self::Error> {
                request.augment(|mut req, _| {
                    req.headers_mut()
                        .insert("X-Test", "Value".try_into().unwrap());
                    Ok(req)
                })
            }
        }

        struct TestParseResponse;
        impl ParseStrictResponse for TestParseResponse {
            type Output = Result<String, Infallible>;

            fn parse(&self, _response: &Response<Bytes>) -> Self::Output {
                Ok("OK".to_string())
            }
        }

        let http_layer = service_fn(|_request: http::Request<SdkBody>| async move {
            if _request.headers().contains_key("X-Test") {
                Ok(http::Response::new(SdkBody::from("ok")))
            } else {
                Err(ConnectorError::user("header not set".into()))
            }
        });

        let mut svc = ServiceBuilder::new()
            .layer(ParseResponseLayer::<TestParseResponse, ()>::new())
            .layer(MapRequestLayer::for_mapper(AddHeader))
            .layer(DispatchLayer)
            .service(http_layer);
        let req = http::Request::new(SdkBody::from("hello"));
        let req = operation::Request::new(req);
        let req = Operation::new(req, TestParseResponse);
        let resp = svc.call(req).await.expect("Response should succeed");
        assert_eq!(resp.parsed, "OK".to_string())
    }
}