1use serde::Deserialize;
6use std::borrow::Cow;
7use std::collections::HashMap;
8
9#[derive(Deserialize, Debug)]
10#[serde(untagged)]
11pub(crate) enum LambdaHttpEvent<'a> {
12    ApiGatewayHttpV2(ApiGatewayHttpV2Event<'a>),
13    ApiGatewayRestOrAlb(ApiGatewayRestEvent<'a>),
14}
15
16impl LambdaHttpEvent<'_> {
17    pub fn method<'a>(&'a self) -> &'a str {
19        match self {
20            Self::ApiGatewayHttpV2(event) => &event.request_context.http.method,
21            Self::ApiGatewayRestOrAlb(event) => &event.http_method,
22        }
23    }
24
25    #[allow(dead_code)]
27    pub fn hostname<'a>(&'a self) -> Option<&'a str> {
28        match self {
29            Self::ApiGatewayHttpV2(event) => Some(&event.request_context.domain_name),
30            Self::ApiGatewayRestOrAlb(event) => {
31                if let RestOrAlbRequestContext::Rest(context) = &event.request_context {
32                    Some(&context.domain_name)
33                } else if let Some(host_headers) = event.multi_value_headers.get("host") {
34                    host_headers.first().map(|h| h as &str)
35                } else {
36                    None
37                }
38            }
39        }
40    }
41
42    pub fn path_query(&self) -> String {
44        match self {
45            Self::ApiGatewayHttpV2(event) => {
46                let path = encode_path_query(&event.raw_path);
47                let query = &event.raw_query_string as &str;
48                if query.is_empty() {
49                    path.into_owned()
51                } else {
52                    format!("{}?{}", path, query)
54                }
55            }
56            Self::ApiGatewayRestOrAlb(event) => {
57                let path = if let RestOrAlbRequestContext::Rest(context) = &event.request_context {
58                    &context.path
60                } else {
61                    &event.path
63                };
64                if let Some(query_string_parameters) = &event.multi_value_query_string_parameters {
65                    let querystr = query_string_parameters
67                        .iter()
68                        .flat_map(|(k, vec)| {
69                            let k_enc = encode_path_query(&k);
70                            vec.iter()
71                                .map(move |v| format!("{}={}", k_enc, encode_path_query(&v)))
72                        })
73                        .collect::<Vec<_>>()
74                        .join("&");
75                    format!("{}?{}", path, querystr)
76                } else {
77                    path.clone()
79                }
80            }
81        }
82    }
83
84    pub fn headers<'a>(&'a self) -> Vec<(&'a str, Cow<'a, str>)> {
86        match self {
87            Self::ApiGatewayHttpV2(event) => {
88                let mut headers: Vec<(&'a str, Cow<'a, str>)> = event
89                    .headers
90                    .iter()
91                    .map(|(k, v)| (k as &str, Cow::from(v as &str)))
92                    .collect();
93
94                if let Some(cookies) = &event.cookies {
96                    let cookie_value = cookies.join("; ");
97                    headers.push(("cookie", Cow::from(cookie_value)));
98                }
99
100                headers
101            }
102            Self::ApiGatewayRestOrAlb(event) => event
103                .multi_value_headers
104                .iter()
105                .flat_map(|(k, vec)| vec.iter().map(move |v| (k as &str, Cow::from(v as &str))))
106                .collect(),
107        }
108    }
109
110    #[allow(dead_code)]
113    pub fn cookies<'a>(&'a self) -> Vec<&'a str> {
114        match self {
115            Self::ApiGatewayHttpV2(event) => {
116                if let Some(cookies) = &event.cookies {
117                    cookies.iter().map(|c| c.as_str()).collect()
118                } else {
119                    Vec::new()
120                }
121            }
122            Self::ApiGatewayRestOrAlb(event) => {
123                if let Some(cookie_headers) = event.multi_value_headers.get("cookie") {
124                    cookie_headers
125                        .iter()
126                        .flat_map(|v| v.split(";"))
127                        .map(|c| c.trim())
128                        .collect()
129                } else {
130                    Vec::new()
131                }
132            }
133        }
134    }
135
136    #[cfg(feature = "br")]
139    pub fn client_supports_brotli(&self) -> bool {
140        match self {
141            Self::ApiGatewayHttpV2(event) => {
142                if let Some(header_val) = event.headers.get("accept-encoding") {
143                    for elm in header_val.to_ascii_lowercase().split(',') {
144                        if let Some(algo_name) = elm.split(';').next() {
145                            if algo_name.trim() == "br" {
147                                return true;
149                            }
150                        }
151                    }
152                    false
154                } else {
155                    false
157                }
158            }
159            Self::ApiGatewayRestOrAlb(event) => {
160                if let Some(header_vals) = event.multi_value_headers.get("accept-encoding") {
161                    for header_val in header_vals {
162                        for elm in header_val.to_ascii_lowercase().split(',') {
163                            if let Some(algo_name) = elm.split(';').next() {
164                                if algo_name.trim() == "br" {
166                                    return true;
168                                }
169                            }
170                        }
171                    }
172                    false
174                } else {
175                    false
177                }
178            }
179        }
180    }
181
182    #[cfg(not(feature = "br"))]
184    pub fn client_supports_brotli(&self) -> bool {
185        false
186    }
187
188    pub fn multi_value(&self) -> bool {
190        match self {
191            Self::ApiGatewayHttpV2(_) => false,
192            Self::ApiGatewayRestOrAlb(_) => true,
193        }
194    }
195
196    pub fn body(self) -> Result<Vec<u8>, base64::DecodeError> {
198        let (body, b64_encoded) = match self {
199            Self::ApiGatewayHttpV2(event) => (event.body, event.is_base64_encoded),
200            Self::ApiGatewayRestOrAlb(event) => (event.body, event.is_base64_encoded),
201        };
202
203        if let Some(body) = body {
204            if b64_encoded {
205                base64::decode(&body as &str)
207            } else {
208                Ok(body.into_owned().into_bytes())
210            }
211        } else {
212            Ok(Vec::new())
214        }
215    }
216
217    #[allow(dead_code)]
219    pub fn source_ip(&self) -> Option<std::net::IpAddr> {
220        use std::net::IpAddr;
221        use std::str::FromStr;
222        match self {
223            Self::ApiGatewayHttpV2(event) => {
224                IpAddr::from_str(&event.request_context.http.source_ip).ok()
225            }
226            Self::ApiGatewayRestOrAlb(event) => {
227                if let RestOrAlbRequestContext::Rest(context) = &event.request_context {
228                    IpAddr::from_str(&context.identity.source_ip).ok()
229                } else {
230                    None
231                }
232            }
233        }
234    }
235}
236
237#[derive(Deserialize, Debug)]
240#[serde(rename_all = "camelCase")]
241pub(crate) struct ApiGatewayHttpV2Event<'a> {
242    #[allow(dead_code)]
243    version: String,
244    raw_path: String,
245    raw_query_string: String,
246    cookies: Option<Vec<String>>,
247    headers: HashMap<String, String>,
248    body: Option<Cow<'a, str>>,
250    #[serde(default)]
251    is_base64_encoded: bool,
252    request_context: ApiGatewayV2RequestContext,
253    }
261
262#[derive(Deserialize, Debug, Clone)]
263#[serde(rename_all = "camelCase")]
264struct ApiGatewayV2RequestContext {
265    domain_name: String,
267    http: Http,
269    }
289
290#[derive(Deserialize, Debug, Default, Clone)]
291#[serde(rename_all = "camelCase")]
292struct Http {
293    method: String,
295    source_ip: String,
297    }
306
307#[derive(Deserialize, Debug)]
313#[serde(rename_all = "camelCase")]
314pub(crate) struct ApiGatewayRestEvent<'a> {
315    path: String,
317    http_method: String,
318    body: Option<Cow<'a, str>>,
320    #[serde(default)]
321    is_base64_encoded: bool,
322    multi_value_headers: HashMap<String, Vec<String>>,
323    #[serde(default)]
324    multi_value_query_string_parameters: Option<HashMap<String, Vec<String>>>,
325    request_context: RestOrAlbRequestContext,
327    }
332
333#[derive(Deserialize, Debug)]
334#[serde(untagged)]
335enum RestOrAlbRequestContext {
336    Rest(ApiGatewayRestRequestContext),
337    Alb(AlbRequestContext),
338}
339
340#[derive(Deserialize, Debug)]
342#[serde(rename_all = "camelCase")]
343struct ApiGatewayRestRequestContext {
344    domain_name: String,
345    identity: ApiGatewayRestIdentity,
346    path: String,
348    }
361
362#[derive(Deserialize, Debug)]
364#[serde(rename_all = "camelCase")]
365struct ApiGatewayRestIdentity {
366    #[allow(dead_code)]
367    access_key: Option<String>,
368    source_ip: String,
369}
370
371#[derive(Deserialize, Debug)]
373#[serde(rename_all = "camelCase")]
374struct AlbRequestContext {}
375
376const RFC3986_PATH_ESCAPE_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
381    .add(b' ')
382    .add(b'"')
383    .add(b'#')
384    .add(b'%')
385    .add(b'+')
386    .add(b':')
387    .add(b'<')
388    .add(b'>')
389    .add(b'?')
390    .add(b'@')
391    .add(b'[')
392    .add(b'\\')
393    .add(b']')
394    .add(b'^')
395    .add(b'`')
396    .add(b'{')
397    .add(b'|')
398    .add(b'}');
399
400fn encode_path_query<'a>(pathstr: &'a str) -> Cow<'a, str> {
401    Cow::from(percent_encoding::utf8_percent_encode(
402        pathstr,
403        &RFC3986_PATH_ESCAPE_SET,
404    ))
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410    use crate::test_consts::*;
411
412    #[test]
413    fn test_decode() {
414        let _: ApiGatewayHttpV2Event =
415            serde_json::from_str(API_GATEWAY_V2_GET_ROOT_NOQUERY).unwrap();
416        let _: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_V2_GET_ROOT_NOQUERY).unwrap();
417        let _: ApiGatewayRestEvent =
418            serde_json::from_str(API_GATEWAY_REST_GET_ROOT_NOQUERY).unwrap();
419        let _: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_REST_GET_ROOT_NOQUERY).unwrap();
420    }
421
422    #[test]
423    fn test_cookie() {
424        let event: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_V2_GET_TWO_COOKIES).unwrap();
425        assert_eq!(
426            event.cookies(),
427            vec!["cookie1=value1".to_string(), "cookie2=value2".to_string()]
428        );
429        let event: LambdaHttpEvent =
430            serde_json::from_str(API_GATEWAY_REST_GET_TWO_COOKIES).unwrap();
431        assert_eq!(
432            event.cookies(),
433            vec!["cookie1=value1".to_string(), "cookie2=value2".to_string()]
434        );
435    }
436}