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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
 * 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<ListTablesResponse, ListTablesError>`
    ///
    /// For streaming operations, this is something like:
    /// `Result<EventStream<TranscribeStreamingEvent>, 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<Self::Output>;

    /// 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<Bytes>) -> 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<Bytes>`] into `Self::Output`.
    fn parse(&self, response: &http::Response<Bytes>) -> 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<T: ParseStrictResponse> ParseHttpResponse for T {
    type Output = T::Output;

    fn parse_unloaded(&self, _response: &mut operation::Response) -> Option<Self::Output> {
        None
    }

    fn parse_loaded(&self, response: &http::Response<Bytes>) -> 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<Self::Output> {
                // 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<Bytes>) -> Self::Output {
                unimplemented!()
            }
        }
    }
}