Skip to main content

aws_lambda_events/encodings/
http.rs

1use base64::display::Base64Display;
2use bytes::Bytes;
3use http_body::{Body as HttpBody, SizeHint};
4use serde::{
5    de::{Deserialize, Deserializer, Error as DeError, Visitor},
6    ser::{Error as SerError, Serialize, Serializer},
7};
8use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll};
9
10/// Representation of http request and response bodies as supported
11/// by API Gateway and ALBs.
12///
13/// These come in three flavors
14/// * `Empty` ( no body )
15/// * `Text` ( text data )
16/// * `Binary` ( binary data )
17///
18/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the [hyper crate](https://crates.io/crates/hyper)
19///
20/// # Examples
21///
22/// Body types are inferred with `From` implementations.
23///
24/// ## Text
25///
26/// Types like `String`, `str` whose type reflects
27/// text produce `Body::Text` variants
28///
29/// ```
30/// assert!(match aws_lambda_events::encodings::Body::from("text") {
31///   aws_lambda_events::encodings::Body::Text(_) => true,
32///   _ => false
33/// })
34/// ```
35///
36/// ## Binary
37///
38/// Types like `Vec<u8>` and `&[u8]` whose types reflect raw bytes produce `Body::Binary` variants
39///
40/// ```
41/// assert!(match aws_lambda_events::encodings::Body::from("text".as_bytes()) {
42///   aws_lambda_events::encodings::Body::Binary(_) => true,
43///   _ => false
44/// })
45/// ```
46///
47/// `Binary` responses bodies will automatically get based64 encoded to meet API Gateway's response expectations.
48///
49/// ## Empty
50///
51/// The unit type ( `()` ) whose type represents an empty value produces `Body::Empty` variants
52///
53/// ```
54/// assert!(match aws_lambda_events::encodings::Body::from(()) {
55///   aws_lambda_events::encodings::Body::Empty => true,
56///   _ => false
57/// })
58/// ```
59///
60///
61/// For more information about API Gateway's body types,
62/// refer to [this documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html).
63#[non_exhaustive]
64#[derive(Debug, Default, Eq, PartialEq)]
65pub enum Body {
66    /// An empty body
67    #[default]
68    Empty,
69    /// A body containing string data
70    Text(String),
71    /// A body containing binary data
72    Binary(Vec<u8>),
73}
74
75impl Body {
76    /// Decodes body, if needed.
77    ///
78    /// # Panics
79    ///
80    /// Panics when aws communicates to handler that request is base64 encoded but
81    /// it can not be base64 decoded
82    pub fn from_maybe_encoded(is_base64_encoded: bool, body: &str) -> Body {
83        use base64::Engine;
84
85        if is_base64_encoded {
86            Body::from(
87                ::base64::engine::general_purpose::STANDARD
88                    .decode(body)
89                    .expect("failed to decode aws base64 encoded body"),
90            )
91        } else {
92            Body::from(body)
93        }
94    }
95}
96
97impl From<()> for Body {
98    fn from(_: ()) -> Self {
99        Body::Empty
100    }
101}
102
103impl<'a> From<&'a str> for Body {
104    fn from(s: &'a str) -> Self {
105        Body::Text(s.into())
106    }
107}
108
109impl From<String> for Body {
110    fn from(b: String) -> Self {
111        Body::Text(b)
112    }
113}
114
115impl From<Cow<'static, str>> for Body {
116    #[inline]
117    fn from(cow: Cow<'static, str>) -> Body {
118        match cow {
119            Cow::Borrowed(b) => Body::from(b.to_owned()),
120            Cow::Owned(o) => Body::from(o),
121        }
122    }
123}
124
125impl From<Cow<'static, [u8]>> for Body {
126    #[inline]
127    fn from(cow: Cow<'static, [u8]>) -> Body {
128        match cow {
129            Cow::Borrowed(b) => Body::from(b),
130            Cow::Owned(o) => Body::from(o),
131        }
132    }
133}
134
135impl From<Vec<u8>> for Body {
136    fn from(b: Vec<u8>) -> Self {
137        Body::Binary(b)
138    }
139}
140
141impl<'a> From<&'a [u8]> for Body {
142    fn from(b: &'a [u8]) -> Self {
143        Body::Binary(b.to_vec())
144    }
145}
146
147impl Deref for Body {
148    type Target = [u8];
149
150    #[inline]
151    fn deref(&self) -> &Self::Target {
152        self.as_ref()
153    }
154}
155
156impl AsRef<[u8]> for Body {
157    #[inline]
158    fn as_ref(&self) -> &[u8] {
159        match self {
160            Body::Empty => &[],
161            Body::Text(ref bytes) => bytes.as_ref(),
162            Body::Binary(ref bytes) => bytes.as_ref(),
163        }
164    }
165}
166
167impl Clone for Body {
168    fn clone(&self) -> Self {
169        match self {
170            Body::Empty => Body::Empty,
171            Body::Text(ref bytes) => Body::Text(bytes.clone()),
172            Body::Binary(ref bytes) => Body::Binary(bytes.clone()),
173        }
174    }
175}
176
177impl Serialize for Body {
178    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179    where
180        S: Serializer,
181    {
182        match self {
183            Body::Text(data) => {
184                serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?)
185            }
186            Body::Binary(data) => {
187                serializer.collect_str(&Base64Display::new(data, &base64::engine::general_purpose::STANDARD))
188            }
189            Body::Empty => serializer.serialize_unit(),
190        }
191    }
192}
193
194impl<'de> Deserialize<'de> for Body {
195    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196    where
197        D: Deserializer<'de>,
198    {
199        struct BodyVisitor;
200
201        impl Visitor<'_> for BodyVisitor {
202            type Value = Body;
203
204            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205                formatter.write_str("string")
206            }
207
208            fn visit_str<E>(self, value: &str) -> Result<Body, E>
209            where
210                E: DeError,
211            {
212                Ok(Body::from(value))
213            }
214        }
215
216        deserializer.deserialize_str(BodyVisitor)
217    }
218}
219
220impl HttpBody for Body {
221    type Data = Bytes;
222    type Error = super::Error;
223
224    fn is_end_stream(&self) -> bool {
225        matches!(self, Body::Empty)
226    }
227
228    fn size_hint(&self) -> SizeHint {
229        match self {
230            Body::Empty => SizeHint::default(),
231            Body::Text(ref s) => SizeHint::with_exact(s.len() as u64),
232            Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64),
233        }
234    }
235
236    fn poll_frame(
237        self: Pin<&mut Self>,
238        _cx: &mut std::task::Context<'_>,
239    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
240        let body = take(self.get_mut());
241        Poll::Ready(match body {
242            Body::Empty => None,
243            Body::Text(s) => Some(Ok(http_body::Frame::data(s.into()))),
244            Body::Binary(b) => Some(Ok(http_body::Frame::data(b.into()))),
245        })
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use std::collections::HashMap;
253
254    #[test]
255    fn body_has_default() {
256        assert_eq!(Body::default(), Body::Empty);
257    }
258
259    #[test]
260    fn from_unit() {
261        assert_eq!(Body::from(()), Body::Empty);
262    }
263
264    #[test]
265    fn from_str() {
266        match Body::from(String::from("foo").as_str()) {
267            Body::Text(_) => (),
268            not => panic!("expected Body::Text(...) got {not:?}"),
269        }
270    }
271
272    #[test]
273    fn from_string() {
274        match Body::from(String::from("foo")) {
275            Body::Text(_) => (),
276            not => panic!("expected Body::Text(...) got {not:?}"),
277        }
278    }
279
280    #[test]
281    fn from_cow_str() {
282        match Body::from(Cow::from("foo")) {
283            Body::Text(_) => (),
284            not => panic!("expected Body::Text(...) got {not:?}"),
285        }
286    }
287
288    #[test]
289    fn from_cow_bytes() {
290        match Body::from(Cow::from("foo".as_bytes())) {
291            Body::Binary(_) => (),
292            not => panic!("expected Body::Binary(...) got {not:?}"),
293        }
294    }
295
296    #[test]
297    fn from_bytes() {
298        match Body::from("foo".as_bytes()) {
299            Body::Binary(_) => (),
300            not => panic!("expected Body::Binary(...) got {not:?}"),
301        }
302    }
303
304    #[test]
305    fn serialize_text() {
306        let mut map = HashMap::new();
307        map.insert("foo", Body::from("bar"));
308        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"bar"}"#);
309    }
310
311    #[test]
312    fn serialize_binary() {
313        let mut map = HashMap::new();
314        map.insert("foo", Body::from("bar".as_bytes()));
315        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"YmFy"}"#);
316    }
317
318    #[test]
319    fn serialize_empty() {
320        let mut map = HashMap::new();
321        map.insert("foo", Body::Empty);
322        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":null}"#);
323    }
324
325    #[test]
326    fn serialize_from_maybe_encoded() {
327        match Body::from_maybe_encoded(false, "foo") {
328            Body::Text(_) => (),
329            not => panic!("expected Body::Text(...) got {not:?}"),
330        }
331
332        match Body::from_maybe_encoded(true, "Zm9v") {
333            Body::Binary(b) => assert_eq!(&[102, 111, 111], b.as_slice()),
334            not => panic!("expected Body::Text(...) got {not:?}"),
335        }
336    }
337}