1use std::collections::HashMap;
7use std::str::FromStr;
8
9use bytes::Bytes;
10use http_body_util::{BodyExt, Full};
11use hyper::Response as HyperResponse;
12use lambda_http::{Body as LambdaBody, Request as LambdaRequest, Response as LambdaResponse};
13use tracing::{debug, trace};
14
15use crate::error::{LambdaError, Result};
16
17type UnifiedMcpBody = http_body_util::combinators::UnsyncBoxBody<Bytes, hyper::Error>;
19
20fn infallible_to_hyper_error(never: std::convert::Infallible) -> hyper::Error {
22 match never {}
23}
24
25type MappedFullBody =
27 http_body_util::combinators::MapErr<Full<Bytes>, fn(std::convert::Infallible) -> hyper::Error>;
28
29pub fn lambda_to_hyper_request(
44 lambda_req: LambdaRequest,
45) -> Result<hyper::Request<MappedFullBody>> {
46 let authorizer_fields = extract_authorizer_context(&lambda_req);
48
49 let (mut parts, lambda_body) = lambda_req.into_parts();
51
52 for (field_name, field_value) in authorizer_fields {
54 let header_name = format!("x-authorizer-{}", field_name);
55
56 let Ok(name) = http::HeaderName::from_str(&header_name) else {
59 debug!(
60 "Skipping authorizer field '{}' - invalid header name",
61 field_name
62 );
63 continue;
64 };
65
66 let Ok(value) = http::HeaderValue::from_str(&field_value) else {
67 debug!(
68 "Skipping authorizer field '{}' - invalid header value",
69 field_name
70 );
71 continue;
72 };
73
74 parts.headers.insert(name, value);
75 trace!(
76 "Injected authorizer header: {} = {}",
77 header_name, field_value
78 );
79 }
80
81 let body_bytes = match lambda_body {
83 LambdaBody::Empty => Bytes::new(),
84 LambdaBody::Text(s) => Bytes::from(s),
85 LambdaBody::Binary(b) => Bytes::from(b),
86 _ => Bytes::new(),
87 };
88
89 let full_body = Full::new(body_bytes)
91 .map_err(infallible_to_hyper_error as fn(std::convert::Infallible) -> hyper::Error);
92
93 let hyper_req = hyper::Request::from_parts(parts, full_body);
95
96 debug!(
97 "Converted Lambda request: {} {} -> hyper::Request<Full<Bytes>>",
98 hyper_req.method(),
99 hyper_req.uri()
100 );
101
102 Ok(hyper_req)
103}
104
105pub async fn hyper_to_lambda_response(
110 hyper_resp: HyperResponse<UnifiedMcpBody>,
111) -> Result<LambdaResponse<LambdaBody>> {
112 let (parts, body) = hyper_resp.into_parts();
113
114 let body_bytes = match body.collect().await {
116 Ok(collected) => collected.to_bytes(),
117 Err(err) => {
118 return Err(LambdaError::Body(format!(
119 "Failed to collect response body: {}",
120 err
121 )));
122 }
123 };
124
125 let lambda_body = if body_bytes.is_empty() {
127 LambdaBody::Empty
128 } else {
129 match String::from_utf8(body_bytes.to_vec()) {
131 Ok(text) => LambdaBody::Text(text),
132 Err(_) => LambdaBody::Binary(body_bytes.to_vec()),
133 }
134 };
135
136 let lambda_resp = LambdaResponse::from_parts(parts, lambda_body);
138
139 debug!(
140 "Converted hyper response -> Lambda response (status: {})",
141 lambda_resp.status()
142 );
143
144 Ok(lambda_resp)
145}
146
147pub fn hyper_to_lambda_streaming(
152 hyper_resp: HyperResponse<UnifiedMcpBody>,
153) -> lambda_http::Response<UnifiedMcpBody> {
154 let (parts, body) = hyper_resp.into_parts();
155
156 let lambda_resp = lambda_http::Response::from_parts(parts, body);
158
159 debug!(
160 "Converted hyper response -> Lambda streaming response (status: {})",
161 lambda_resp.status()
162 );
163
164 lambda_resp
165}
166
167pub fn camel_to_snake(s: &str) -> String {
180 let mut result = String::new();
181 let chars: Vec<char> = s.chars().collect();
182
183 for i in 0..chars.len() {
184 let ch = chars[i];
185
186 if ch.is_uppercase() {
187 let is_first = i == 0;
188 let prev_is_lower = i > 0 && chars[i - 1].is_lowercase();
189 let next_is_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
190
191 if !is_first && (prev_is_lower || next_is_lower) {
195 result.push('_');
196 }
197
198 result.push(ch.to_ascii_lowercase());
199 } else {
200 result.push(ch);
201 }
202 }
203
204 result
205}
206
207pub fn sanitize_authorizer_field_name(field: &str) -> String {
224 let snake_case = camel_to_snake(field);
226
227 snake_case
229 .to_ascii_lowercase()
230 .chars()
231 .map(|c| {
232 if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
233 c
234 } else {
235 '-'
236 }
237 })
238 .collect()
239}
240
241pub fn extract_authorizer_context(req: &LambdaRequest) -> HashMap<String, String> {
265 use lambda_http::request::RequestContext;
266
267 let mut fields = HashMap::new();
268
269 let Some(request_context) = req.extensions().get::<RequestContext>() else {
271 return fields; };
273
274 match request_context {
276 RequestContext::ApiGatewayV1(ctx) => {
277 debug!(
278 authorizer_field_count = ctx.authorizer.fields.len(),
279 authorizer_keys = ?ctx.authorizer.fields.keys().collect::<Vec<_>>(),
280 "V1 REST API authorizer context"
281 );
282 }
283 RequestContext::ApiGatewayV2(ctx) => {
284 if let Some(ref authorizer) = ctx.authorizer {
285 debug!(
286 authorizer_field_count = authorizer.fields.len(),
287 authorizer_keys = ?authorizer.fields.keys().collect::<Vec<_>>(),
288 "V2 HTTP API authorizer context"
289 );
290 } else {
291 debug!("V2 HTTP API: no authorizer present");
292 }
293 }
294 _ => {
295 debug!("Non-API Gateway request context (ALB or other)");
296 }
297 }
298
299 let mut authorizer_fields_map = HashMap::new();
303
304 match request_context {
305 RequestContext::ApiGatewayV2(ctx) => {
306 if let Some(ref authorizer) = ctx.authorizer {
308 for (key, value) in &authorizer.fields {
309 authorizer_fields_map.insert(key.clone(), value.clone());
310 }
311 }
312 }
313 RequestContext::ApiGatewayV1(ctx) => {
314 if let Some(serde_json::Value::Object(auth_map)) = ctx.authorizer.fields.get("lambda") {
320 for (key, value) in auth_map {
321 authorizer_fields_map.insert(key.clone(), value.clone());
322 }
323 } else {
324 for (key, value) in &ctx.authorizer.fields {
329 if key == "principalId"
330 || key == "integrationLatency"
331 || key == "usageIdentifierKey"
332 {
333 continue;
334 }
335 authorizer_fields_map.insert(key.clone(), value.clone());
336 }
337 }
338 }
339 _ => {} }
341
342 for (key, value) in authorizer_fields_map {
344 let sanitized_key = sanitize_authorizer_field_name(&key);
346
347 let value_str = match value {
349 serde_json::Value::String(s) => s,
350 other => other.to_string(), };
352
353 fields.insert(sanitized_key, value_str);
354 }
355
356 if !fields.is_empty() {
357 debug!(
358 "Extracted {} authorizer fields from Lambda context",
359 fields.len()
360 );
361 }
362
363 fields
364}
365
366pub fn extract_mcp_headers(req: &LambdaRequest) -> HashMap<String, String> {
371 let mut mcp_headers = HashMap::new();
372
373 if let Some(session_id) = req.headers().get("mcp-session-id")
375 && let Ok(session_id_str) = session_id.to_str()
376 {
377 mcp_headers.insert("mcp-session-id".to_string(), session_id_str.to_string());
378 }
379
380 if let Some(protocol_version) = req.headers().get("mcp-protocol-version")
382 && let Ok(version_str) = protocol_version.to_str()
383 {
384 mcp_headers.insert("mcp-protocol-version".to_string(), version_str.to_string());
385 }
386
387 if let Some(last_event_id) = req.headers().get("last-event-id")
389 && let Ok(event_id_str) = last_event_id.to_str()
390 {
391 mcp_headers.insert("last-event-id".to_string(), event_id_str.to_string());
392 }
393
394 trace!("Extracted MCP headers: {:?}", mcp_headers);
395 mcp_headers
396}
397
398pub fn inject_mcp_headers(resp: &mut LambdaResponse<LambdaBody>, headers: HashMap<String, String>) {
402 for (name, value) in headers {
403 if let (Ok(header_name), Ok(header_value)) = (
404 http::HeaderName::from_bytes(name.as_bytes()),
405 http::HeaderValue::from_str(&value),
406 ) {
407 resp.headers_mut().insert(header_name, header_value);
408 debug!("Injected MCP header: {} = {}", name, value);
409 }
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use http::{HeaderValue, Method, Request, StatusCode};
417 use http_body_util::Full;
418
419 #[test]
420 fn test_lambda_to_hyper_request_conversion() {
421 let mut lambda_req = Request::builder()
423 .method(Method::POST)
424 .uri("/mcp")
425 .body(LambdaBody::Text(
426 r#"{"jsonrpc":"2.0","method":"initialize","id":1}"#.to_string(),
427 ))
428 .unwrap();
429
430 let headers = lambda_req.headers_mut();
432 headers.insert("content-type", HeaderValue::from_static("application/json"));
433 headers.insert(
434 "mcp-session-id",
435 HeaderValue::from_static("test-session-123"),
436 );
437 headers.insert(
438 "mcp-protocol-version",
439 HeaderValue::from_static("2025-11-25"),
440 );
441
442 let hyper_req = lambda_to_hyper_request(lambda_req).unwrap();
444
445 assert_eq!(hyper_req.method(), &Method::POST);
447 assert_eq!(hyper_req.uri().path(), "/mcp");
448
449 assert_eq!(
451 hyper_req.headers().get("content-type").unwrap(),
452 "application/json"
453 );
454 assert_eq!(
455 hyper_req.headers().get("mcp-session-id").unwrap(),
456 "test-session-123"
457 );
458 assert_eq!(
459 hyper_req.headers().get("mcp-protocol-version").unwrap(),
460 "2025-11-25"
461 );
462 }
463
464 #[test]
465 fn test_lambda_to_hyper_empty_body() {
466 let lambda_req = Request::builder()
467 .method(Method::GET)
468 .uri("/sse")
469 .body(LambdaBody::Empty)
470 .unwrap();
471
472 let hyper_req = lambda_to_hyper_request(lambda_req).unwrap();
473 assert_eq!(hyper_req.method(), &Method::GET);
474 assert_eq!(hyper_req.uri().path(), "/sse");
475 }
476
477 #[test]
478 fn test_lambda_to_hyper_binary_body() {
479 let test_data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; let lambda_req = Request::builder()
481 .method(Method::POST)
482 .uri("/binary")
483 .body(LambdaBody::Binary(test_data.clone()))
484 .unwrap();
485
486 let hyper_req = lambda_to_hyper_request(lambda_req).unwrap();
487 assert_eq!(hyper_req.method(), &Method::POST);
488 assert_eq!(hyper_req.uri().path(), "/binary");
489 }
490
491 #[tokio::test]
492 async fn test_hyper_to_lambda_response_conversion() {
493 let json_body = r#"{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}"#;
495 let full_body = Full::new(Bytes::from(json_body));
496 let boxed_body = full_body.map_err(|never| match never {}).boxed_unsync();
497
498 let hyper_resp = hyper::Response::builder()
499 .status(StatusCode::OK)
500 .header("content-type", "application/json")
501 .header("mcp-session-id", "resp-session-456")
502 .body(boxed_body)
503 .unwrap();
504
505 let lambda_resp = hyper_to_lambda_response(hyper_resp).await.unwrap();
507
508 assert_eq!(lambda_resp.status(), StatusCode::OK);
510 assert_eq!(
511 lambda_resp.headers().get("content-type").unwrap(),
512 "application/json"
513 );
514 assert_eq!(
515 lambda_resp.headers().get("mcp-session-id").unwrap(),
516 "resp-session-456"
517 );
518
519 match lambda_resp.body() {
521 LambdaBody::Text(text) => assert_eq!(text, json_body),
522 _ => panic!("Expected text body"),
523 }
524 }
525
526 #[tokio::test]
527 async fn test_hyper_to_lambda_empty_response() {
528 let empty_body = Full::new(Bytes::new());
529 let boxed_body = empty_body.map_err(|never| match never {}).boxed_unsync();
530
531 let hyper_resp = hyper::Response::builder()
532 .status(StatusCode::NO_CONTENT)
533 .body(boxed_body)
534 .unwrap();
535
536 let lambda_resp = hyper_to_lambda_response(hyper_resp).await.unwrap();
537
538 assert_eq!(lambda_resp.status(), StatusCode::NO_CONTENT);
539 match lambda_resp.body() {
540 LambdaBody::Empty => {} _ => panic!("Expected empty body"),
542 }
543 }
544
545 #[test]
546 fn test_hyper_to_lambda_streaming() {
547 let stream_body = Full::new(Bytes::from("data: test\n\n"));
549 let boxed_body = stream_body.map_err(|never| match never {}).boxed_unsync();
550
551 let hyper_resp = hyper::Response::builder()
552 .status(StatusCode::OK)
553 .header("content-type", "text/event-stream")
554 .header("cache-control", "no-cache")
555 .body(boxed_body)
556 .unwrap();
557
558 let lambda_resp = hyper_to_lambda_streaming(hyper_resp);
560
561 assert_eq!(lambda_resp.status(), StatusCode::OK);
562 assert_eq!(
563 lambda_resp.headers().get("content-type").unwrap(),
564 "text/event-stream"
565 );
566 assert_eq!(
567 lambda_resp.headers().get("cache-control").unwrap(),
568 "no-cache"
569 );
570 }
572
573 #[tokio::test]
574 async fn test_mcp_headers_extraction() {
575 use http::{HeaderValue, Request};
576
577 let mut request = Request::builder()
579 .method("POST")
580 .uri("/mcp")
581 .body(LambdaBody::Empty)
582 .unwrap();
583
584 let headers = request.headers_mut();
585 headers.insert("mcp-session-id", HeaderValue::from_static("sess-123"));
586 headers.insert(
587 "mcp-protocol-version",
588 HeaderValue::from_static("2025-11-25"),
589 );
590 headers.insert("last-event-id", HeaderValue::from_static("event-456"));
591
592 let mcp_headers = extract_mcp_headers(&request);
593
594 assert_eq!(
595 mcp_headers.get("mcp-session-id"),
596 Some(&"sess-123".to_string())
597 );
598 assert_eq!(
599 mcp_headers.get("mcp-protocol-version"),
600 Some(&"2025-11-25".to_string())
601 );
602 assert_eq!(
603 mcp_headers.get("last-event-id"),
604 Some(&"event-456".to_string())
605 );
606 }
607
608 #[tokio::test]
609 async fn test_mcp_headers_injection() {
610 use lambda_http::Body;
611
612 let mut lambda_resp = LambdaResponse::builder()
613 .status(200)
614 .body(Body::Empty)
615 .unwrap();
616
617 let mut headers = HashMap::new();
618 headers.insert("mcp-session-id".to_string(), "sess-789".to_string());
619 headers.insert("mcp-protocol-version".to_string(), "2025-11-25".to_string());
620
621 inject_mcp_headers(&mut lambda_resp, headers);
622
623 assert_eq!(
624 lambda_resp.headers().get("mcp-session-id").unwrap(),
625 "sess-789"
626 );
627 assert_eq!(
628 lambda_resp.headers().get("mcp-protocol-version").unwrap(),
629 "2025-11-25"
630 );
631 }
632
633 mod authorizer_tests {
635 use super::*;
636
637 #[test]
638 fn test_sanitize_field_name_camelcase() {
639 assert_eq!(sanitize_authorizer_field_name("accountId"), "account_id");
641 assert_eq!(sanitize_authorizer_field_name("entityType"), "entity_type");
642 assert_eq!(sanitize_authorizer_field_name("deviceId"), "device_id");
643 assert_eq!(sanitize_authorizer_field_name("userId"), "user_id");
644 assert_eq!(sanitize_authorizer_field_name("tenantId"), "tenant_id");
645 assert_eq!(
646 sanitize_authorizer_field_name("customClaim"),
647 "custom_claim"
648 );
649 }
650
651 #[test]
652 fn test_sanitize_field_name_snake_case() {
653 assert_eq!(sanitize_authorizer_field_name("device_id"), "device_id");
655 assert_eq!(sanitize_authorizer_field_name("user_name"), "user_name");
656 assert_eq!(sanitize_authorizer_field_name("tenant_id"), "tenant_id");
657 }
658
659 #[test]
660 fn test_sanitize_field_name_acronyms() {
661 assert_eq!(sanitize_authorizer_field_name("APIKey"), "api_key");
663 assert_eq!(
664 sanitize_authorizer_field_name("HTTPSEnabled"),
665 "https_enabled"
666 );
667 assert_eq!(sanitize_authorizer_field_name("XMLParser"), "xml_parser");
668 }
669
670 #[test]
671 fn test_sanitize_field_name_with_numbers() {
672 assert_eq!(sanitize_authorizer_field_name("userId123"), "user_id123");
674 assert_eq!(sanitize_authorizer_field_name("device2Id"), "device2_id");
675 }
676
677 #[test]
678 fn test_sanitize_field_name_special_chars() {
679 assert_eq!(sanitize_authorizer_field_name("user@email"), "user-email");
680 assert_eq!(sanitize_authorizer_field_name("test.field"), "test-field");
681 assert_eq!(sanitize_authorizer_field_name("a/b/c"), "a-b-c");
682 }
683
684 #[test]
685 fn test_sanitize_field_name_unicode() {
686 assert_eq!(sanitize_authorizer_field_name("用户"), "--");
688 }
689
690 #[test]
691 fn test_extract_authorizer_no_context() {
692 let lambda_req = Request::builder()
694 .method(Method::POST)
695 .uri("/mcp")
696 .body(LambdaBody::Empty)
697 .unwrap();
698
699 let fields = extract_authorizer_context(&lambda_req);
700 assert!(fields.is_empty());
701 }
702
703 #[test]
704 fn test_lambda_to_hyper_without_authorizer() {
705 let lambda_req = Request::builder()
707 .method(Method::POST)
708 .uri("/mcp")
709 .header("content-type", "application/json")
710 .body(LambdaBody::Empty)
711 .unwrap();
712
713 let hyper_req = lambda_to_hyper_request(lambda_req).unwrap();
714
715 assert!(hyper_req.headers().get("x-authorizer-account_id").is_none());
717 assert_eq!(
718 hyper_req.headers().get("content-type").unwrap(),
719 "application/json"
720 );
721 }
722
723 fn request_with_context(ctx: lambda_http::request::RequestContext) -> LambdaRequest {
725 let mut req = Request::builder()
726 .method(Method::POST)
727 .uri("/mcp")
728 .body(LambdaBody::Empty)
729 .unwrap();
730 req.extensions_mut().insert(ctx);
731 req
732 }
733
734 #[test]
735 fn test_extract_authorizer_v1_top_level_fields() {
736 use aws_lambda_events::apigw::{
740 ApiGatewayProxyRequestContext, ApiGatewayRequestAuthorizer,
741 };
742
743 let mut authorizer = ApiGatewayRequestAuthorizer::default();
744 authorizer
745 .fields
746 .insert("userId".to_string(), serde_json::json!("user-123"));
747 authorizer
748 .fields
749 .insert("tenantId".to_string(), serde_json::json!("tenant-456"));
750 authorizer
751 .fields
752 .insert("role".to_string(), serde_json::json!("admin"));
753
754 let mut v1_ctx = ApiGatewayProxyRequestContext::default();
755 v1_ctx.authorizer = authorizer;
756
757 let req =
758 request_with_context(lambda_http::request::RequestContext::ApiGatewayV1(v1_ctx));
759 let fields = extract_authorizer_context(&req);
760
761 assert_eq!(fields.get("user_id"), Some(&"user-123".to_string()));
762 assert_eq!(fields.get("tenant_id"), Some(&"tenant-456".to_string()));
763 assert_eq!(fields.get("role"), Some(&"admin".to_string()));
764 }
765
766 #[test]
767 fn test_extract_authorizer_v1_nested_lambda() {
768 use aws_lambda_events::apigw::{
771 ApiGatewayProxyRequestContext, ApiGatewayRequestAuthorizer,
772 };
773
774 let mut authorizer = ApiGatewayRequestAuthorizer::default();
775 authorizer.fields.insert(
776 "lambda".to_string(),
777 serde_json::json!({
778 "userId": "user-123",
779 "tenantId": "tenant-456"
780 }),
781 );
782
783 let mut v1_ctx = ApiGatewayProxyRequestContext::default();
784 v1_ctx.authorizer = authorizer;
785
786 let req =
787 request_with_context(lambda_http::request::RequestContext::ApiGatewayV1(v1_ctx));
788 let fields = extract_authorizer_context(&req);
789
790 assert_eq!(fields.get("user_id"), Some(&"user-123".to_string()));
791 assert_eq!(fields.get("tenant_id"), Some(&"tenant-456".to_string()));
792 }
793
794 #[test]
795 fn test_extract_authorizer_v1_skips_internal_fields() {
796 use aws_lambda_events::apigw::{
798 ApiGatewayProxyRequestContext, ApiGatewayRequestAuthorizer,
799 };
800
801 let mut authorizer = ApiGatewayRequestAuthorizer::default();
802 authorizer
803 .fields
804 .insert("userId".to_string(), serde_json::json!("user-123"));
805 authorizer.fields.insert(
806 "principalId".to_string(),
807 serde_json::json!("principal-abc"),
808 );
809 authorizer
810 .fields
811 .insert("integrationLatency".to_string(), serde_json::json!(42));
812 authorizer.fields.insert(
813 "usageIdentifierKey".to_string(),
814 serde_json::json!("api-key-xyz"),
815 );
816
817 let mut v1_ctx = ApiGatewayProxyRequestContext::default();
818 v1_ctx.authorizer = authorizer;
819
820 let req =
821 request_with_context(lambda_http::request::RequestContext::ApiGatewayV1(v1_ctx));
822 let fields = extract_authorizer_context(&req);
823
824 assert_eq!(fields.get("user_id"), Some(&"user-123".to_string()));
825 assert!(
826 !fields.contains_key("principal_id"),
827 "principalId should be skipped"
828 );
829 assert!(
830 !fields.contains_key("integration_latency"),
831 "integrationLatency should be skipped"
832 );
833 assert!(
834 !fields.contains_key("usage_identifier_key"),
835 "usageIdentifierKey should be skipped"
836 );
837 }
838
839 #[test]
840 fn test_extract_authorizer_v1_non_string_values() {
841 use aws_lambda_events::apigw::{
843 ApiGatewayProxyRequestContext, ApiGatewayRequestAuthorizer,
844 };
845
846 let mut authorizer = ApiGatewayRequestAuthorizer::default();
847 authorizer
848 .fields
849 .insert("maxAge".to_string(), serde_json::json!(3600));
850 authorizer
851 .fields
852 .insert("isAdmin".to_string(), serde_json::json!(true));
853
854 let mut v1_ctx = ApiGatewayProxyRequestContext::default();
855 v1_ctx.authorizer = authorizer;
856
857 let req =
858 request_with_context(lambda_http::request::RequestContext::ApiGatewayV1(v1_ctx));
859 let fields = extract_authorizer_context(&req);
860
861 assert_eq!(fields.get("max_age"), Some(&"3600".to_string()));
862 assert_eq!(fields.get("is_admin"), Some(&"true".to_string()));
863 }
864
865 #[test]
866 fn test_extract_authorizer_v1_empty() {
867 use aws_lambda_events::apigw::ApiGatewayProxyRequestContext;
869
870 let v1_ctx = ApiGatewayProxyRequestContext::default();
871
872 let req =
873 request_with_context(lambda_http::request::RequestContext::ApiGatewayV1(v1_ctx));
874 let fields = extract_authorizer_context(&req);
875
876 assert!(fields.is_empty());
877 }
878
879 #[test]
880 fn test_extract_authorizer_v2_lambda_fields() {
881 use aws_lambda_events::apigw::{
883 ApiGatewayRequestAuthorizer, ApiGatewayV2httpRequestContext,
884 };
885
886 let mut authorizer = ApiGatewayRequestAuthorizer::default();
887 authorizer
888 .fields
889 .insert("userId".to_string(), serde_json::json!("user-v2"));
890 authorizer
891 .fields
892 .insert("scope".to_string(), serde_json::json!("read write"));
893
894 let mut v2_ctx = ApiGatewayV2httpRequestContext::default();
895 v2_ctx.authorizer = Some(authorizer);
896
897 let req =
898 request_with_context(lambda_http::request::RequestContext::ApiGatewayV2(v2_ctx));
899 let fields = extract_authorizer_context(&req);
900
901 assert_eq!(fields.get("user_id"), Some(&"user-v2".to_string()));
902 assert_eq!(fields.get("scope"), Some(&"read write".to_string()));
903 }
904 }
905}