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
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
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;
}
/// 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 {
type Output;
fn parse(&self, response: &http::Response<Bytes>) -> Self::Output;
}
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)
}
}
#[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() {
pub struct S3GetObject {
pub 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 })
}
fn parse_loaded(&self, _response: &http::Response<Bytes>) -> Self::Output {
unimplemented!()
}
}
}
}