1#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))]
10use crate::ext::extensions::{PathParameters, StageVariables};
11#[cfg(any(
12 feature = "apigw_rest",
13 feature = "apigw_http",
14 feature = "alb",
15 feature = "apigw_websockets",
16 feature = "vpc_lattice"
17))]
18use crate::ext::extensions::{QueryStringParameters, RawHttpPath};
19#[cfg(feature = "alb")]
20use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext};
21#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))]
22use aws_lambda_events::apigw::ApiGatewayRequestAuthorizer;
23#[cfg(feature = "apigw_rest")]
24use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext};
25#[cfg(feature = "apigw_http")]
26use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext};
27#[cfg(feature = "apigw_websockets")]
28use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext};
29#[cfg(feature = "vpc_lattice")]
30use aws_lambda_events::vpc_lattice::{VpcLatticeRequestV2, VpcLatticeRequestV2Context};
31
32use aws_lambda_events::{encodings::Body, query_map::QueryMap};
33use http::{header::HeaderName, HeaderMap, HeaderValue};
34
35use serde::{Deserialize, Serialize};
36use serde_json::error::Error as JsonError;
37
38use std::{env, future::Future, io::Read, pin::Pin};
39use url::Url;
40
41#[non_exhaustive]
47#[doc(hidden)]
48#[derive(Debug)]
49pub enum LambdaRequest {
50 #[cfg(feature = "apigw_rest")]
51 ApiGatewayV1(ApiGatewayProxyRequest),
52 #[cfg(feature = "apigw_http")]
53 ApiGatewayV2(ApiGatewayV2httpRequest),
54 #[cfg(feature = "alb")]
55 Alb(AlbTargetGroupRequest),
56 #[cfg(feature = "apigw_websockets")]
57 WebSocket(ApiGatewayWebsocketProxyRequest),
58 #[cfg(feature = "vpc_lattice")]
59 VpcLatticeV2(VpcLatticeRequestV2),
60 #[cfg(feature = "pass_through")]
61 PassThrough(String),
62}
63
64impl LambdaRequest {
65 pub fn request_origin(&self) -> RequestOrigin {
69 match self {
70 #[cfg(feature = "apigw_rest")]
71 LambdaRequest::ApiGatewayV1 { .. } => RequestOrigin::ApiGatewayV1,
72 #[cfg(feature = "apigw_http")]
73 LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2,
74 #[cfg(feature = "alb")]
75 LambdaRequest::Alb { .. } => RequestOrigin::Alb,
76 #[cfg(feature = "apigw_websockets")]
77 LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket,
78 #[cfg(feature = "vpc_lattice")]
79 LambdaRequest::VpcLatticeV2 { .. } => RequestOrigin::VpcLatticeV2,
80 #[cfg(feature = "pass_through")]
81 LambdaRequest::PassThrough { .. } => RequestOrigin::PassThrough,
82 #[cfg(not(any(
83 feature = "apigw_rest",
84 feature = "apigw_http",
85 feature = "alb",
86 feature = "apigw_websockets",
87 feature = "vpc_lattice",
88 )))]
89 _ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, `apigw_websockets` or `vpc_lattice` must be enabled for the `lambda-http` crate."),
90 }
91 }
92}
93
94pub type RequestFuture<'a, R, E> = Pin<Box<dyn Future<Output = Result<R, E>> + Send + 'a>>;
96
97#[non_exhaustive]
99#[doc(hidden)]
100#[derive(Debug, Clone)]
101pub enum RequestOrigin {
102 #[cfg(feature = "apigw_rest")]
104 ApiGatewayV1,
105 #[cfg(feature = "apigw_http")]
107 ApiGatewayV2,
108 #[cfg(feature = "alb")]
110 Alb,
111 #[cfg(feature = "apigw_websockets")]
113 WebSocket,
114 #[cfg(feature = "vpc_lattice")]
116 VpcLatticeV2,
117 #[cfg(feature = "pass_through")]
119 PassThrough,
120}
121
122#[cfg(feature = "apigw_http")]
123fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Body> {
124 let http_method = ag.request_context.http.method.clone();
125 let host = ag
126 .headers
127 .get(http::header::HOST)
128 .and_then(|s| s.to_str().ok())
129 .or(ag.request_context.domain_name.as_deref());
130 let raw_path = ag.raw_path.unwrap_or_default();
131 let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path);
132
133 let query_string_parameters = if let Some(query) = &ag.raw_query_string {
139 query.parse().unwrap() } else {
141 ag.query_string_parameters
142 };
143
144 let mut uri = build_request_uri(&path, &ag.headers, host, None);
145 if let Some(query) = ag.raw_query_string {
146 if !query.is_empty() {
147 uri.push('?');
148 uri.push_str(&query);
149 }
150 }
151
152 let builder = http::Request::builder()
153 .uri(uri)
154 .extension(RawHttpPath(raw_path))
155 .extension(QueryStringParameters(query_string_parameters))
156 .extension(PathParameters(QueryMap::from(ag.path_parameters)))
157 .extension(StageVariables(QueryMap::from(ag.stage_variables)))
158 .extension(RequestContext::ApiGatewayV2(ag.request_context));
159
160 let mut headers = ag.headers;
161 if let Some(cookies) = ag.cookies {
162 if let Ok(header_value) = HeaderValue::from_str(&cookies.join("; ")) {
163 headers.insert(http::header::COOKIE, header_value);
164 }
165 }
166
167 let base64 = ag.is_base64_encoded;
168
169 let mut req = builder
170 .body(
171 ag.body
172 .as_deref()
173 .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
174 )
175 .expect("failed to build request");
176
177 let _ = std::mem::replace(req.headers_mut(), headers);
179 let _ = std::mem::replace(req.method_mut(), http_method);
180
181 req
182}
183
184#[cfg(feature = "apigw_rest")]
185fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
186 let http_method = ag.http_method;
187 let host = ag
188 .headers
189 .get(http::header::HOST)
190 .and_then(|s| s.to_str().ok())
191 .or(ag.request_context.domain_name.as_deref());
192 let raw_path = ag.path.unwrap_or_default();
193 let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path);
194
195 let builder = http::Request::builder()
196 .uri(build_request_uri(
197 &path,
198 &ag.headers,
199 host,
200 Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)),
201 ))
202 .extension(RawHttpPath(raw_path))
203 .extension(QueryStringParameters(
207 if ag.multi_value_query_string_parameters.is_empty() {
208 ag.query_string_parameters
209 } else {
210 ag.multi_value_query_string_parameters
211 },
212 ))
213 .extension(PathParameters(QueryMap::from(ag.path_parameters)))
214 .extension(StageVariables(QueryMap::from(ag.stage_variables)))
215 .extension(RequestContext::ApiGatewayV1(ag.request_context));
216
217 let mut headers = ag.multi_value_headers;
220 headers.extend(ag.headers);
221
222 let base64 = ag.is_base64_encoded;
223 let mut req = builder
224 .body(
225 ag.body
226 .as_deref()
227 .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
228 )
229 .expect("failed to build request");
230
231 let _ = std::mem::replace(req.headers_mut(), headers);
233 let _ = std::mem::replace(req.method_mut(), http_method);
234
235 req
236}
237
238#[cfg(feature = "alb")]
239fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
240 let http_method = alb.http_method;
241 let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok());
242 let raw_path = alb.path.unwrap_or_default();
243
244 let query_string_parameters = decode_query_map(alb.query_string_parameters);
245 let multi_value_query_string_parameters = decode_query_map(alb.multi_value_query_string_parameters);
246
247 let builder = http::Request::builder()
248 .uri(build_request_uri(
249 &raw_path,
250 &alb.headers,
251 host,
252 Some((&multi_value_query_string_parameters, &query_string_parameters)),
253 ))
254 .extension(RawHttpPath(raw_path))
255 .extension(QueryStringParameters(
259 if multi_value_query_string_parameters.is_empty() {
260 query_string_parameters
261 } else {
262 multi_value_query_string_parameters
263 },
264 ))
265 .extension(RequestContext::Alb(alb.request_context));
266
267 let mut headers = alb.multi_value_headers;
270 headers.extend(alb.headers);
271
272 let base64 = alb.is_base64_encoded;
273
274 let mut req = builder
275 .body(
276 alb.body
277 .as_deref()
278 .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
279 )
280 .expect("failed to build request");
281
282 let _ = std::mem::replace(req.headers_mut(), headers);
284 let _ = std::mem::replace(req.method_mut(), http_method);
285
286 req
287}
288
289#[cfg(any(feature = "alb", feature = "vpc_lattice"))]
290fn decode_query_map(query_map: QueryMap) -> QueryMap {
291 use std::str::FromStr;
292
293 let query_string = query_map.to_query_string();
294 let decoded = percent_encoding::percent_decode(query_string.as_bytes()).decode_utf8_lossy();
295 QueryMap::from_str(&decoded).unwrap_or_default()
296}
297
298#[cfg(feature = "apigw_websockets")]
299fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request<Body> {
300 let http_method = ag.http_method;
301 let host = ag
302 .headers
303 .get(http::header::HOST)
304 .and_then(|s| s.to_str().ok())
305 .or(ag.request_context.domain_name.as_deref());
306 let raw_path = ag.path.unwrap_or_default();
307 let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path);
308
309 let builder = http::Request::builder()
310 .uri(build_request_uri(
311 &path,
312 &ag.headers,
313 host,
314 Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)),
315 ))
316 .extension(RawHttpPath(raw_path))
317 .extension(QueryStringParameters(
321 if ag.multi_value_query_string_parameters.is_empty() {
322 ag.query_string_parameters
323 } else {
324 ag.multi_value_query_string_parameters
325 },
326 ))
327 .extension(PathParameters(QueryMap::from(ag.path_parameters)))
328 .extension(StageVariables(QueryMap::from(ag.stage_variables)))
329 .extension(RequestContext::WebSocket(ag.request_context));
330
331 let mut headers = ag.multi_value_headers;
334 headers.extend(ag.headers);
335
336 let base64 = ag.is_base64_encoded;
337 let mut req = builder
338 .body(
339 ag.body
340 .as_deref()
341 .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
342 )
343 .expect("failed to build request");
344
345 let _ = std::mem::replace(req.headers_mut(), headers);
347 let _ = std::mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET));
348
349 req
350}
351
352#[cfg(feature = "vpc_lattice")]
353fn into_vpc_lattice_request(vlr: VpcLatticeRequestV2) -> http::Request<Body> {
354 let http_method = vlr.method;
355 let host = vlr.headers.get(http::header::HOST).and_then(|s| s.to_str().ok());
356 let raw_path = vlr.path.unwrap_or_default();
357
358 let query_string_parameters = decode_query_map(vlr.query_string_parameters);
359
360 let builder = http::Request::builder()
361 .uri(build_request_uri(
362 &raw_path,
363 &vlr.headers,
364 host,
365 Some((&query_string_parameters, &query_string_parameters)),
366 ))
367 .extension(RawHttpPath(raw_path))
368 .extension(QueryStringParameters(query_string_parameters))
369 .extension(RequestContext::VpcLattice(vlr.request_context));
370
371 let base64 = vlr.is_base64_encoded;
372
373 let mut req = builder
374 .body(
375 vlr.body
376 .as_deref()
377 .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
378 )
379 .expect("failed to build request");
380
381 let _ = std::mem::replace(req.headers_mut(), vlr.headers);
383 let _ = std::mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET));
384
385 req
386}
387
388#[cfg(feature = "pass_through")]
389fn into_pass_through_request(data: String) -> http::Request<Body> {
390 let mut builder = http::Request::builder();
391
392 let headers = builder.headers_mut().unwrap();
393 headers.insert("Content-Type", "application/json".parse().unwrap());
394
395 let raw_path = "/events";
396
397 builder
398 .method(http::Method::POST)
399 .uri(raw_path)
400 .extension(RawHttpPath(raw_path.to_string()))
401 .extension(RequestContext::PassThrough)
402 .body(Body::from(data))
403 .expect("failed to build request")
404}
405
406#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))]
407fn apigw_path_with_stage(stage: &Option<String>, path: &str) -> String {
408 if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() {
409 return path.into();
410 }
411
412 let stage = match stage {
413 None => return path.into(),
414 Some(stage) if stage == "$default" => return path.into(),
415 Some(stage) => stage,
416 };
417
418 let prefix = format!("/{stage}/");
419 if path.starts_with(&prefix) {
420 path.into()
421 } else {
422 format!("/{stage}{path}")
423 }
424}
425
426#[non_exhaustive]
429#[derive(Deserialize, Debug, Clone, Serialize)]
430#[serde(untagged)]
431pub enum RequestContext {
432 #[cfg(feature = "apigw_rest")]
434 ApiGatewayV1(ApiGatewayProxyRequestContext),
435 #[cfg(feature = "apigw_http")]
437 ApiGatewayV2(ApiGatewayV2httpRequestContext),
438 #[cfg(feature = "alb")]
440 Alb(AlbTargetGroupRequestContext),
441 #[cfg(feature = "apigw_websockets")]
443 WebSocket(ApiGatewayWebsocketProxyRequestContext),
444 #[cfg(feature = "vpc_lattice")]
446 VpcLattice(VpcLatticeRequestV2Context),
447 #[cfg(feature = "pass_through")]
449 PassThrough,
450}
451
452impl From<LambdaRequest> for http::Request<Body> {
454 fn from(value: LambdaRequest) -> Self {
455 match value {
456 #[cfg(feature = "apigw_rest")]
457 LambdaRequest::ApiGatewayV1(ag) => into_proxy_request(ag),
458 #[cfg(feature = "apigw_http")]
459 LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag),
460 #[cfg(feature = "alb")]
461 LambdaRequest::Alb(alb) => into_alb_request(alb),
462 #[cfg(feature = "apigw_websockets")]
463 LambdaRequest::WebSocket(ag) => into_websocket_request(ag),
464 #[cfg(feature = "vpc_lattice")]
465 LambdaRequest::VpcLatticeV2(vpclat) => into_vpc_lattice_request(vpclat),
466 #[cfg(feature = "pass_through")]
467 LambdaRequest::PassThrough(data) => into_pass_through_request(data),
468 }
469 }
470}
471
472impl RequestContext {
473 #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))]
475 pub fn authorizer(&self) -> Option<&ApiGatewayRequestAuthorizer> {
476 match self {
477 #[cfg(feature = "apigw_rest")]
478 Self::ApiGatewayV1(ag) => Some(&ag.authorizer),
479 #[cfg(feature = "apigw_http")]
480 Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(),
481 #[cfg(feature = "apigw_websockets")]
482 Self::WebSocket(ag) => Some(&ag.authorizer),
483 #[cfg(any(feature = "alb", feature = "pass_through", feature = "vpc_lattice"))]
484 _ => None,
485 }
486 }
487}
488
489pub fn from_reader<R>(rdr: R) -> Result<crate::Request, JsonError>
506where
507 R: Read,
508{
509 serde_json::from_reader(rdr).map(LambdaRequest::into)
510}
511
512pub fn from_str(s: &str) -> Result<crate::Request, JsonError> {
529 serde_json::from_str(s).map(LambdaRequest::into)
530}
531
532fn x_forwarded_proto() -> HeaderName {
533 HeaderName::from_static("x-forwarded-proto")
534}
535
536fn build_request_uri(
537 path: &str,
538 headers: &HeaderMap,
539 host: Option<&str>,
540 queries: Option<(&QueryMap, &QueryMap)>,
541) -> String {
542 let mut url = match host {
543 None => {
544 let rel_url = Url::parse(&format!("http://localhost{path}")).unwrap();
545 rel_url.path().to_string()
546 }
547 Some(host) => {
548 let scheme = headers
549 .get(x_forwarded_proto())
550 .and_then(|s| s.to_str().ok())
551 .unwrap_or("https");
552 let url = format!("{scheme}://{host}{path}");
553 Url::parse(&url).unwrap().to_string()
554 }
555 };
556
557 if let Some((mv, sv)) = queries {
558 if !mv.is_empty() {
559 url.push('?');
560 url.push_str(&mv.to_query_string());
561 } else if !sv.is_empty() {
562 url.push('?');
563 url.push_str(&sv.to_query_string());
564 }
565 }
566
567 url
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573 use crate::ext::RequestExt;
574 use std::fs::File;
575
576 #[test]
577 fn deserializes_apigw_request_events_from_readables() {
578 let result = from_reader(File::open("tests/data/apigw_proxy_request.json").expect("expected file"));
582 assert!(result.is_ok(), "event was not parsed as expected {result:?}");
583 }
584
585 #[test]
586 fn deserializes_minimal_apigw_http_request_events() {
587 let input = include_str!("../tests/data/apigw_v2_proxy_request_minimal.json");
590 let result = from_str(input);
591 assert!(
592 result.is_ok(),
593 "event was not parsed as expected {result:?} given {input}"
594 );
595 let req = result.expect("failed to parse request");
596 assert_eq!(req.method(), "GET");
597 assert_eq!(req.uri(), "https://xxx.execute-api.us-east-1.amazonaws.com/");
598
599 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
601 assert!(
602 matches!(req_context, &RequestContext::ApiGatewayV2(_)),
603 "expected ApiGatewayV2 context, got {req_context:?}"
604 );
605 }
606
607 #[test]
608 fn deserializes_apigw_http_request_events() {
609 let input = include_str!("../tests/data/apigw_v2_proxy_request.json");
612 let result = from_str(input);
613 assert!(
614 result.is_ok(),
615 "event was not parsed as expected {result:?} given {input}"
616 );
617 let req = result.expect("failed to parse request");
618 let cookie_header = req
619 .headers()
620 .get(http::header::COOKIE)
621 .ok_or_else(|| "Cookie header not found".to_string())
622 .and_then(|v| v.to_str().map_err(|e| e.to_string()));
623
624 assert_eq!(req.method(), "POST");
625 assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value");
626 assert_eq!(cookie_header, Ok("cookie1=value1; cookie2=value2"));
627
628 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
630 assert!(
631 matches!(req_context, &RequestContext::ApiGatewayV2(_)),
632 "expected ApiGatewayV2 context, got {req_context:?}"
633 );
634
635 let (parts, _) = req.into_parts();
636 assert_eq!("https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value", parts.uri.to_string());
637 }
638
639 #[test]
640 fn deserializes_apigw_request_events() {
641 let input = include_str!("../tests/data/apigw_proxy_request.json");
645 let result = from_str(input);
646 assert!(
647 result.is_ok(),
648 "event was not parsed as expected {result:?} given {input}"
649 );
650 let req = result.expect("failed to parse request");
651 assert_eq!(req.method(), "GET");
652 assert_eq!(
653 req.uri(),
654 "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me"
655 );
656
657 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
659 assert!(
660 matches!(req_context, &RequestContext::ApiGatewayV1(_)),
661 "expected ApiGateway context, got {req_context:?}"
662 );
663 }
664
665 #[test]
666 fn deserializes_lambda_function_url_request_events() {
667 let input = include_str!("../tests/data/lambda_function_url_request.json");
670 let result = from_str(input);
671 assert!(
672 result.is_ok(),
673 "event was not parsed as expected {result:?} given {input}"
674 );
675 let req = result.expect("failed to parse request");
676 let cookie_header = req
677 .headers()
678 .get_all(http::header::COOKIE)
679 .iter()
680 .map(|v| v.to_str().unwrap().to_string())
681 .reduce(|acc, nxt| [acc, nxt].join(";"));
682
683 assert_eq!(req.method(), "GET");
684 assert_eq!(
685 req.uri(),
686 "https://id.lambda-url.eu-west-2.on.aws/my/path?parameter1=value1¶meter1=value2¶meter2=value"
687 );
688 assert_eq!(cookie_header, Some("test=hi".to_string()));
689
690 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
692 assert!(
693 matches!(req_context, &RequestContext::ApiGatewayV2(_)),
694 "expected ApiGatewayV2 context, got {req_context:?}"
695 );
696 }
697
698 #[test]
699 fn deserializes_alb_request_events() {
700 let input = include_str!("../tests/data/alb_request.json");
703 let result = from_str(input);
704 assert!(
705 result.is_ok(),
706 "event was not parsed as expected {result:?} given {input}"
707 );
708 let req = result.expect("failed to parse request");
709 assert_eq!(req.method(), "GET");
710 assert_eq!(
711 req.uri(),
712 "https://lambda-846800462-us-east-2.elb.amazonaws.com/?myKey=val2"
713 );
714
715 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
717 assert!(
718 matches!(req_context, &RequestContext::Alb(_)),
719 "expected Alb context, got {req_context:?}"
720 );
721 }
722
723 #[test]
724 fn deserializes_alb_request_encoded_query_parameters_events() {
725 let input = include_str!("../tests/data/alb_request_encoded_query_parameters.json");
728 let result = from_str(input);
729 assert!(
730 result.is_ok(),
731 "event was not parsed as expected {result:?} given {input}"
732 );
733 let req = result.expect("failed to parse request");
734 assert_eq!(req.method(), "GET");
735 assert_eq!(
736 req.uri(),
737 "https://lambda-846800462-us-east-2.elb.amazonaws.com/?myKey=%3FshowAll%3Dtrue"
738 );
739
740 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
742 assert!(
743 matches!(req_context, &RequestContext::Alb(_)),
744 "expected Alb context, got {req_context:?}"
745 );
746 }
747
748 #[test]
749 fn deserializes_apigw_multi_value_request_events() {
750 let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json");
753 let result = from_str(input);
754 assert!(
755 result.is_ok(),
756 "event is was not parsed as expected {result:?} given {input}"
757 );
758 let request = result.expect("failed to parse request");
759
760 assert!(!request
761 .query_string_parameters_ref()
762 .expect("Request is missing query parameters")
763 .is_empty());
764
765 let params = request.query_string_parameters();
767 assert_eq!(Some(vec!["you", "me"]), params.all("multiValueName"));
768 assert_eq!(Some(vec!["me"]), params.all("name"));
769
770 let query = request.uri().query().unwrap();
771 assert!(query.contains("name=me"));
772 assert!(query.contains("multiValueName=you&multiValueName=me"));
773 let (parts, _) = request.into_parts();
774 assert!(parts.uri.to_string().contains("name=me"));
775 assert!(parts.uri.to_string().contains("multiValueName=you&multiValueName=me"));
776 }
777
778 #[test]
779 fn deserializes_alb_multi_value_request_events() {
780 let input = include_str!("../tests/data/alb_multi_value_request.json");
783 let result = from_str(input);
784 assert!(
785 result.is_ok(),
786 "event is was not parsed as expected {result:?} given {input}"
787 );
788 let request = result.expect("failed to parse request");
789 assert!(!request
790 .query_string_parameters_ref()
791 .expect("Request is missing query parameters")
792 .is_empty());
793
794 let params = request.query_string_parameters();
796 assert_eq!(Some(vec!["val1", "val2"]), params.all("myKey"));
797 assert_eq!(Some(vec!["val3", "val4"]), params.all("myOtherKey"));
798
799 let query = request.uri().query().unwrap();
800 assert!(query.contains("myKey=val1&myKey=val2"));
801 assert!(query.contains("myOtherKey=val3&myOtherKey=val4"));
802 }
803
804 #[test]
805 fn deserializes_alb_multi_value_request_encoded_query_parameters_events() {
806 let input = include_str!("../tests/data/alb_multi_value_request_encoded_query_parameters.json");
809 let result = from_str(input);
810 assert!(
811 result.is_ok(),
812 "event is was not parsed as expected {result:?} given {input}"
813 );
814 let request = result.expect("failed to parse request");
815 assert!(!request
816 .query_string_parameters_ref()
817 .expect("Request is missing query parameters")
818 .is_empty());
819
820 assert_eq!(
822 request
823 .query_string_parameters_ref()
824 .and_then(|params| params.all("myKey")),
825 Some(vec!["?showAll=true", "?showAll=false"])
826 );
827 }
828
829 #[test]
830 #[cfg(feature = "vpc_lattice")]
831 fn deserializes_vpc_lattice_basic() {
832 let input = include_str!("../tests/data/vpc_lattice_v2_request.json");
833 let result = from_str(input);
834 assert!(
835 result.is_ok(),
836 "event is was not parsed as expected {result:?} given {input}"
837 );
838 let request = result.expect("failed to parse request");
839 assert_eq!(request.method(), "GET");
840
841 let body_str = match request.body() {
842 Body::Text(s) => s.as_str(),
843 _ => "",
844 };
845
846 assert_eq!(body_str, "All is good");
847
848 let uri = request.uri().to_string();
849 assert!(uri.starts_with("/health?"), "unexpected uri: {uri}");
850 assert!(uri.contains("multi=a"), "unexpected uri: {uri}");
851 assert!(uri.contains("multi=DEF"), "unexpected uri: {uri}");
852 assert!(uri.contains("multi=g"), "unexpected uri: {uri}");
853 assert!(uri.contains("state=prod"), "unexpected uri: {uri}");
854
855 let req_context = request
857 .request_context_ref()
858 .expect("Request is missing RequestContext");
859 assert!(
860 matches!(req_context, &RequestContext::VpcLattice(_)),
861 "expected Vpc lattice context, got {req_context:?}"
862 );
863 }
864
865 #[test]
866 #[cfg(feature = "vpc_lattice")]
867 fn deserializes_vpc_lattice_basic_base64() {
868 let input = include_str!("../tests/data/vpc_lattice_v2_request_base64.json");
869 let result = from_str(input);
870 assert!(
871 result.is_ok(),
872 "event is was not parsed as expected {result:?} given {input}"
873 );
874 let request = result.expect("failed to parse request");
875 assert_eq!(request.method(), "GET");
876
877 let body_array = match request.body() {
878 Body::Binary(s) => s.as_slice(),
879 _ => &[],
880 };
881
882 assert_eq!(body_array, *b"All is good");
883
884 let uri = request.uri();
886
887 assert!(uri.to_string().starts_with("https://www.site.com/health?"));
888 assert!(uri.to_string().contains("multi=a&multi=DEF&multi=g"));
889 assert!(uri.to_string().contains("state=prod"));
890
891 let req_context = request
893 .request_context_ref()
894 .expect("Request is missing RequestContext");
895 assert!(
896 matches!(req_context, &RequestContext::VpcLattice(_)),
897 "expected Vpc lattice context, got {req_context:?}"
898 );
899 }
900
901 #[test]
902 #[cfg(feature = "vpc_lattice")]
903 fn deserializes_vpc_lattice_headers() {
904 let input = include_str!("../tests/data/vpc_lattice_v2_request.json");
905 let result = from_str(input);
906 assert!(
907 result.is_ok(),
908 "event is was not parsed as expected {result:?} given {input}"
909 );
910 let request = result.expect("failed to parse request");
911
912 let multi_headers_as_big_string = request
914 .headers()
915 .get_all("multi")
916 .iter()
917 .map(|v| v.to_str().unwrap().to_string())
918 .reduce(|acc, nxt| [acc, nxt].join(";"));
919
920 assert_eq!(multi_headers_as_big_string, Some("x;y".to_string()));
921
922 let basic_headers_as_big_string = request
924 .headers()
925 .get_all("user-agent")
926 .iter()
927 .map(|v| v.to_str().unwrap().to_string())
928 .reduce(|acc, nxt| [acc, nxt].join(";"));
929
930 assert_eq!(basic_headers_as_big_string, Some("curl/7.68.0".to_string()));
931
932 let req_context = request
934 .request_context_ref()
935 .expect("Request is missing RequestContext");
936 assert!(
937 matches!(req_context, &RequestContext::VpcLattice(_)),
938 "expected Vpc lattice context, got {req_context:?}"
939 );
940 }
941
942 #[test]
943 #[cfg(feature = "vpc_lattice")]
944 fn deserializes_vpc_lattice_multi_value_querys() {
945 let input = include_str!("../tests/data/vpc_lattice_v2_request.json");
946 let result = from_str(input);
947 assert!(
948 result.is_ok(),
949 "event is was not parsed as expected {result:?} given {input}"
950 );
951 let request = result.expect("failed to parse request");
952 assert!(!request
953 .query_string_parameters_ref()
954 .expect("Request is missing query parameters")
955 .is_empty());
956
957 let params = request.query_string_parameters();
958 assert_eq!(Some(vec!["prod"]), params.all("state"));
959 assert_eq!(Some(vec!["a", "DEF", "g"]), params.all("multi"));
960
961 let query = request.uri().query().unwrap();
962 assert!(query.contains("multi=a&multi=DEF&multi=g"));
963 assert!(query.contains("state=prod"));
964 }
965
966 #[test]
967 #[cfg(feature = "vpc_lattice")]
968 fn deserializes_vpc_lattice_encoded_query_parameters() {
969 let input = include_str!("../tests/data/vpc_lattice_v2_request_encoded_query.json");
970 let result = from_str(input);
971 assert!(
972 result.is_ok(),
973 "event is was not parsed as expected {result:?} given {input}"
974 );
975 let request = result.expect("failed to parse request");
976
977 let params = request.query_string_parameters();
978 assert_eq!(Some(vec!["?showAll=true"]), params.all("filter"));
980 assert_eq!(Some(vec!["hello world"]), params.all("q"));
981
982 let query = request.uri().query().unwrap();
983 assert!(query.contains("filter="), "unexpected uri query: {query}");
984 assert!(query.contains("q="), "unexpected uri query: {query}");
985 }
986
987 #[test]
988 #[cfg(feature = "vpc_lattice")]
989 fn deserializes_vpc_lattice_no_body() {
990 let input = r#"{
991 "version": "2.0",
992 "path": "/ping",
993 "method": "GET",
994 "headers": {"accept": ["*/*"]},
995 "queryStringParameters": {},
996 "isBase64Encoded": false,
997 "requestContext": {
998 "serviceNetworkArn": "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-abc",
999 "serviceArn": "arn:aws:vpc-lattice:us-east-1:123456789012:service/svc-abc",
1000 "targetGroupArn": "arn:aws:vpc-lattice:us-east-1:123456789012:targetgroup/tg-abc",
1001 "region": "us-east-1",
1002 "timeEpoch": "1724875399456789"
1003 }
1004 }"#;
1005 let result = from_str(input);
1006 assert!(result.is_ok(), "event was not parsed as expected {result:?}");
1007 let request = result.expect("failed to parse request");
1008 assert_eq!(request.method(), "GET");
1009 assert!(
1010 matches!(request.body(), Body::Empty),
1011 "expected empty body, got {:?}",
1012 request.body()
1013 );
1014 }
1015
1016 #[test]
1017 fn deserialize_apigw_http_sam_local() {
1018 let input = include_str!("../tests/data/apigw_v2_sam_local.json");
1027 let result = from_str(input);
1028 assert!(
1029 result.is_ok(),
1030 "event was not parsed as expected {result:?} given {input}"
1031 );
1032 let req = result.expect("failed to parse request");
1033 assert_eq!(req.method(), "GET");
1034 assert_eq!(req.uri(), "http://127.0.0.1:3000/hello");
1035 }
1036
1037 #[test]
1038 fn deserialize_apigw_no_host() {
1039 let input = include_str!("../tests/data/apigw_no_host.json");
1041 let result = from_str(input);
1042 assert!(
1043 result.is_ok(),
1044 "event was not parsed as expected {result:?} given {input}"
1045 );
1046 let req = result.expect("failed to parse request");
1047 assert_eq!(req.method(), "GET");
1048 assert_eq!(req.uri(), "/test/hello?name=me");
1049 }
1050
1051 #[test]
1052 fn deserialize_alb_no_host() {
1053 let input = include_str!("../tests/data/alb_no_host.json");
1055 let result = from_str(input);
1056 assert!(
1057 result.is_ok(),
1058 "event was not parsed as expected {result:?} given {input}"
1059 );
1060 let req = result.expect("failed to parse request");
1061 assert_eq!(req.method(), "GET");
1062 assert_eq!(req.uri(), "/v1/health/");
1063 }
1064
1065 #[test]
1066 fn deserialize_apigw_path_with_space() {
1067 let input = include_str!("../tests/data/apigw_request_path_with_space.json");
1069 let result = from_str(input);
1070 assert!(
1071 result.is_ok(),
1072 "event was not parsed as expected {result:?} given {input}"
1073 );
1074 let req = result.expect("failed to parse request");
1075 assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path-with%20space?parameter1=value1¶meter1=value2¶meter2=value");
1076 }
1077
1078 #[test]
1079 fn parse_paths_with_spaces() {
1080 let url = build_request_uri("/path with spaces/and multiple segments", &HeaderMap::new(), None, None);
1081 assert_eq!("/path%20with%20spaces/and%20multiple%20segments", url);
1082 }
1083
1084 #[test]
1085 fn deserializes_apigw_http_request_with_stage_in_path() {
1086 let input = include_str!("../tests/data/apigw_v2_proxy_request_with_stage_in_path.json");
1087 let result = from_str(input);
1088 assert!(
1089 result.is_ok(),
1090 "event was not parsed as expected {result:?} given {input}"
1091 );
1092 let req = result.expect("failed to parse request");
1093 assert_eq!("/Prod/my/path", req.uri().path());
1094 assert_eq!("/Prod/my/path", req.raw_http_path());
1095 }
1096
1097 #[test]
1098 fn test_apigw_path_with_stage() {
1099 assert_eq!("/path", apigw_path_with_stage(&None, "/path"));
1100 assert_eq!("/path", apigw_path_with_stage(&Some("$default".into()), "/path"));
1101 assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path"));
1102 assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path"));
1103 }
1104
1105 #[tokio::test]
1106 #[cfg(feature = "apigw_rest")]
1107 async fn test_axum_query_extractor_apigw_rest() {
1108 use axum_core::extract::FromRequestParts;
1109 use axum_extra::extract::Query;
1110 let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json");
1113 let request = from_str(input).expect("failed to parse request");
1114 let (mut parts, _) = request.into_parts();
1115
1116 #[derive(Deserialize)]
1117 #[serde(rename_all = "camelCase")]
1118 struct Params {
1119 name: Vec<String>,
1120 multi_value_name: Vec<String>,
1121 }
1122 struct State;
1123
1124 let query = Query::<Params>::from_request_parts(&mut parts, &State).await.unwrap();
1125 assert_eq!(vec!["me"], query.0.name);
1126 assert_eq!(vec!["you", "me"], query.0.multi_value_name);
1127 }
1128
1129 #[tokio::test]
1130 #[cfg(feature = "apigw_http")]
1131 async fn test_axum_query_extractor_apigw_http() {
1132 use axum_core::extract::FromRequestParts;
1133 use axum_extra::extract::Query;
1134 let input = include_str!("../tests/data/apigw_v2_proxy_request.json");
1135 let request = from_str(input).expect("failed to parse request");
1136 let (mut parts, _) = request.into_parts();
1137
1138 #[derive(Deserialize)]
1139 struct Params {
1140 parameter1: Vec<String>,
1141 parameter2: Vec<String>,
1142 }
1143 struct State;
1144
1145 let query = Query::<Params>::from_request_parts(&mut parts, &State).await.unwrap();
1146 assert_eq!(vec!["value1", "value2"], query.0.parameter1);
1147 assert_eq!(vec!["value"], query.0.parameter2);
1148 }
1149
1150 #[tokio::test]
1151 #[cfg(feature = "alb")]
1152 async fn test_axum_query_extractor_alb() {
1153 use axum_core::extract::FromRequestParts;
1154 use axum_extra::extract::Query;
1155 let input = include_str!("../tests/data/alb_multi_value_request.json");
1156 let request = from_str(input).expect("failed to parse request");
1157 let (mut parts, _) = request.into_parts();
1158
1159 #[derive(Deserialize)]
1160 #[serde(rename_all = "camelCase")]
1161 struct Params {
1162 my_key: Vec<String>,
1163 my_other_key: Vec<String>,
1164 }
1165 struct State;
1166
1167 let query = Query::<Params>::from_request_parts(&mut parts, &State).await.unwrap();
1168 assert_eq!(vec!["val1", "val2"], query.0.my_key);
1169 assert_eq!(vec!["val3", "val4"], query.0.my_other_key);
1170 }
1171
1172 #[test]
1173 #[cfg(feature = "apigw_rest")]
1174 fn deserializes_request_authorizer() {
1175 let input = include_str!("../../lambda-events/src/fixtures/example-apigw-request.json");
1176 let result = from_str(input);
1177 assert!(
1178 result.is_ok(),
1179 "event was not parsed as expected {result:?} given {input}"
1180 );
1181 let req = result.expect("failed to parse request");
1182
1183 let req_context = req.request_context_ref().expect("Request is missing RequestContext");
1184 let authorizer = req_context.authorizer().expect("authorizer is missing");
1185 assert_eq!(Some("admin"), authorizer.fields.get("principalId").unwrap().as_str());
1186 }
1187
1188 #[test]
1189 #[cfg(all(feature = "apigw_http", feature = "vpc_lattice"))]
1190 fn vpc_lattice_event_does_not_match_apigw_v2() {
1191 let data = include_bytes!("../../lambda-events/src/fixtures/example-vpc-lattice-v2-request.json");
1192 let result = serde_json::from_slice::<ApiGatewayV2httpRequest>(data);
1193 assert!(result.is_err(), "VPC Lattice event should not deserialize as APIGW V2");
1194 }
1195
1196 #[test]
1197 #[cfg(feature = "vpc_lattice")]
1198 fn vpc_lattice_method_none_defaults_to_get() {
1199 let input = r#"{
1200 "version": "2.0",
1201 "path": "/ping",
1202 "headers": {"accept": ["*/*"]},
1203 "queryStringParameters": {},
1204 "isBase64Encoded": false,
1205 "requestContext": {
1206 "serviceNetworkArn": "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-abc",
1207 "serviceArn": "arn:aws:vpc-lattice:us-east-1:123456789012:service/svc-abc",
1208 "targetGroupArn": "arn:aws:vpc-lattice:us-east-1:123456789012:targetgroup/tg-abc",
1209 "region": "us-east-1",
1210 "timeEpoch": "1724875399456789"
1211 }
1212 }"#;
1213 let result = from_str(input);
1214 assert!(result.is_ok(), "event was not parsed as expected {result:?}");
1215 let request = result.expect("failed to parse request");
1216 assert_eq!(request.method(), "GET");
1217 }
1218
1219 #[test]
1220 #[cfg(feature = "vpc_lattice")]
1221 fn vpc_lattice_post_with_body() {
1222 let input = r#"{
1223 "version": "2.0",
1224 "path": "/submit",
1225 "method": "POST",
1226 "headers": {"content-type": ["application/json"]},
1227 "queryStringParameters": {},
1228 "body": "{\"key\":\"value\"}",
1229 "isBase64Encoded": false,
1230 "requestContext": {
1231 "serviceNetworkArn": "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-abc",
1232 "serviceArn": "arn:aws:vpc-lattice:us-east-1:123456789012:service/svc-abc",
1233 "targetGroupArn": "arn:aws:vpc-lattice:us-east-1:123456789012:targetgroup/tg-abc",
1234 "region": "us-east-1",
1235 "timeEpoch": "1724875399456789"
1236 }
1237 }"#;
1238 let result = from_str(input);
1239 assert!(result.is_ok(), "event was not parsed as expected {result:?}");
1240 let request = result.expect("failed to parse request");
1241 assert_eq!(request.method(), "POST");
1242 let body_str = match request.body() {
1243 Body::Text(s) => s.as_str(),
1244 other => panic!("expected text body, got {other:?}"),
1245 };
1246 assert_eq!(body_str, r#"{"key":"value"}"#);
1247 }
1248}