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#[derive(Debug, Default, Eq, PartialEq)]
64pub enum Body {
65    /// An empty body
66    #[default]
67    Empty,
68    /// A body containing string data
69    Text(String),
70    /// A body containing binary data
71    Binary(Vec<u8>),
72}
73
74impl Body {
75    /// Decodes body, if needed.
76    ///
77    /// # Panics
78    ///
79    /// Panics when aws communicates to handler that request is base64 encoded but
80    /// it can not be base64 decoded
81    pub fn from_maybe_encoded(is_base64_encoded: bool, body: &str) -> Body {
82        use base64::Engine;
83
84        if is_base64_encoded {
85            Body::from(
86                ::base64::engine::general_purpose::STANDARD
87                    .decode(body)
88                    .expect("failed to decode aws base64 encoded body"),
89            )
90        } else {
91            Body::from(body)
92        }
93    }
94}
95
96impl From<()> for Body {
97    fn from(_: ()) -> Self {
98        Body::Empty
99    }
100}
101
102impl<'a> From<&'a str> for Body {
103    fn from(s: &'a str) -> Self {
104        Body::Text(s.into())
105    }
106}
107
108impl From<String> for Body {
109    fn from(b: String) -> Self {
110        Body::Text(b)
111    }
112}
113
114impl From<Cow<'static, str>> for Body {
115    #[inline]
116    fn from(cow: Cow<'static, str>) -> Body {
117        match cow {
118            Cow::Borrowed(b) => Body::from(b.to_owned()),
119            Cow::Owned(o) => Body::from(o),
120        }
121    }
122}
123
124impl From<Cow<'static, [u8]>> for Body {
125    #[inline]
126    fn from(cow: Cow<'static, [u8]>) -> Body {
127        match cow {
128            Cow::Borrowed(b) => Body::from(b),
129            Cow::Owned(o) => Body::from(o),
130        }
131    }
132}
133
134impl From<Vec<u8>> for Body {
135    fn from(b: Vec<u8>) -> Self {
136        Body::Binary(b)
137    }
138}
139
140impl<'a> From<&'a [u8]> for Body {
141    fn from(b: &'a [u8]) -> Self {
142        Body::Binary(b.to_vec())
143    }
144}
145
146impl Deref for Body {
147    type Target = [u8];
148
149    #[inline]
150    fn deref(&self) -> &Self::Target {
151        self.as_ref()
152    }
153}
154
155impl AsRef<[u8]> for Body {
156    #[inline]
157    fn as_ref(&self) -> &[u8] {
158        match self {
159            Body::Empty => &[],
160            Body::Text(ref bytes) => bytes.as_ref(),
161            Body::Binary(ref bytes) => bytes.as_ref(),
162        }
163    }
164}
165
166impl Clone for Body {
167    fn clone(&self) -> Self {
168        match self {
169            Body::Empty => Body::Empty,
170            Body::Text(ref bytes) => Body::Text(bytes.clone()),
171            Body::Binary(ref bytes) => Body::Binary(bytes.clone()),
172        }
173    }
174}
175
176impl Serialize for Body {
177    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178    where
179        S: Serializer,
180    {
181        match self {
182            Body::Text(data) => {
183                serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?)
184            }
185            Body::Binary(data) => {
186                serializer.collect_str(&Base64Display::new(data, &base64::engine::general_purpose::STANDARD))
187            }
188            Body::Empty => serializer.serialize_unit(),
189        }
190    }
191}
192
193impl<'de> Deserialize<'de> for Body {
194    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195    where
196        D: Deserializer<'de>,
197    {
198        struct BodyVisitor;
199
200        impl Visitor<'_> for BodyVisitor {
201            type Value = Body;
202
203            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204                formatter.write_str("string")
205            }
206
207            fn visit_str<E>(self, value: &str) -> Result<Body, E>
208            where
209                E: DeError,
210            {
211                Ok(Body::from(value))
212            }
213        }
214
215        deserializer.deserialize_str(BodyVisitor)
216    }
217}
218
219impl HttpBody for Body {
220    type Data = Bytes;
221    type Error = super::Error;
222
223    fn is_end_stream(&self) -> bool {
224        matches!(self, Body::Empty)
225    }
226
227    fn size_hint(&self) -> SizeHint {
228        match self {
229            Body::Empty => SizeHint::default(),
230            Body::Text(ref s) => SizeHint::with_exact(s.len() as u64),
231            Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64),
232        }
233    }
234
235    fn poll_frame(
236        self: Pin<&mut Self>,
237        _cx: &mut std::task::Context<'_>,
238    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
239        let body = take(self.get_mut());
240        Poll::Ready(match body {
241            Body::Empty => None,
242            Body::Text(s) => Some(Ok(http_body::Frame::data(s.into()))),
243            Body::Binary(b) => Some(Ok(http_body::Frame::data(b.into()))),
244        })
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use std::collections::HashMap;
252
253    #[test]
254    fn body_has_default() {
255        assert_eq!(Body::default(), Body::Empty);
256    }
257
258    #[test]
259    fn from_unit() {
260        assert_eq!(Body::from(()), Body::Empty);
261    }
262
263    #[test]
264    fn from_str() {
265        match Body::from(String::from("foo").as_str()) {
266            Body::Text(_) => (),
267            not => panic!("expected Body::Text(...) got {:?}", not),
268        }
269    }
270
271    #[test]
272    fn from_string() {
273        match Body::from(String::from("foo")) {
274            Body::Text(_) => (),
275            not => panic!("expected Body::Text(...) got {:?}", not),
276        }
277    }
278
279    #[test]
280    fn from_cow_str() {
281        match Body::from(Cow::from("foo")) {
282            Body::Text(_) => (),
283            not => panic!("expected Body::Text(...) got {:?}", not),
284        }
285    }
286
287    #[test]
288    fn from_cow_bytes() {
289        match Body::from(Cow::from("foo".as_bytes())) {
290            Body::Binary(_) => (),
291            not => panic!("expected Body::Binary(...) got {:?}", not),
292        }
293    }
294
295    #[test]
296    fn from_bytes() {
297        match Body::from("foo".as_bytes()) {
298            Body::Binary(_) => (),
299            not => panic!("expected Body::Binary(...) got {:?}", not),
300        }
301    }
302
303    #[test]
304    fn serialize_text() {
305        let mut map = HashMap::new();
306        map.insert("foo", Body::from("bar"));
307        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"bar"}"#);
308    }
309
310    #[test]
311    fn serialize_binary() {
312        let mut map = HashMap::new();
313        map.insert("foo", Body::from("bar".as_bytes()));
314        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"YmFy"}"#);
315    }
316
317    #[test]
318    fn serialize_empty() {
319        let mut map = HashMap::new();
320        map.insert("foo", Body::Empty);
321        assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":null}"#);
322    }
323
324    #[test]
325    fn serialize_from_maybe_encoded() {
326        match Body::from_maybe_encoded(false, "foo") {
327            Body::Text(_) => (),
328            not => panic!("expected Body::Text(...) got {:?}", not),
329        }
330
331        match Body::from_maybe_encoded(true, "Zm9v") {
332            Body::Binary(b) => assert_eq!(&[102, 111, 111], b.as_slice()),
333            not => panic!("expected Body::Text(...) got {:?}", not),
334        }
335    }
336}