use crate::{
ext::{PathParameters, QueryStringParameters, StageVariables},
strmap::StrMap,
};
use aws_lambda_events::encodings::Body;
use aws_lambda_events::event::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext};
use aws_lambda_events::event::apigw::{
ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext,
};
use http::header::HeaderName;
use serde::Deserialize;
use serde_json::error::Error as JsonError;
use std::{io::Read, mem};
#[doc(hidden)]
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum LambdaRequest {
ApiGatewayV1(ApiGatewayProxyRequest),
ApiGatewayV2(ApiGatewayV2httpRequest),
Alb(AlbTargetGroupRequest),
}
impl LambdaRequest {
pub fn request_origin(&self) -> RequestOrigin {
match self {
LambdaRequest::ApiGatewayV1 { .. } => RequestOrigin::ApiGatewayV1,
LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2,
LambdaRequest::Alb { .. } => RequestOrigin::Alb,
}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub enum RequestOrigin {
ApiGatewayV1,
ApiGatewayV2,
Alb,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RequestContext {
ApiGatewayV1(ApiGatewayProxyRequestContext),
ApiGatewayV2(ApiGatewayV2httpRequestContext),
Alb(AlbTargetGroupRequestContext),
}
impl<'a> From<LambdaRequest> for http::Request<Body> {
fn from(value: LambdaRequest) -> Self {
match value {
LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag),
LambdaRequest::ApiGatewayV1(ag) => into_proxy_request(ag),
LambdaRequest::Alb(alb) => into_alb_request(alb),
}
}
}
pub(crate) fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Body> {
let http_method = ag.request_context.http.method.clone();
let builder = http::Request::builder()
.uri({
let scheme = ag
.headers
.get(x_forwarded_proto())
.and_then(|s| s.to_str().ok())
.unwrap_or("https");
let host = ag
.headers
.get(http::header::HOST)
.and_then(|s| s.to_str().ok())
.or_else(|| ag.request_context.domain_name.as_deref())
.unwrap_or("localhost");
let mut url = format!("{}://{}{}", scheme, host, ag.raw_path.as_deref().unwrap_or_default());
if let Some(query) = ag.raw_query_string {
url.push('?');
url.push_str(&query);
}
url
})
.extension(QueryStringParameters(StrMap::from(ag.query_string_parameters)))
.extension(PathParameters(StrMap::from(ag.path_parameters)))
.extension(StageVariables(StrMap::from(ag.stage_variables)))
.extension(RequestContext::ApiGatewayV2(ag.request_context));
let mut headers = ag.headers;
if let Some(cookies) = ag.cookies {
if let Ok(header_value) = http::header::HeaderValue::from_str(&cookies.join(";")) {
headers.append(http::header::COOKIE, header_value);
}
}
let base64 = ag.is_base64_encoded;
let mut req = builder
.body(
ag.body
.as_deref()
.map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
)
.expect("failed to build request");
let _ = mem::replace(req.headers_mut(), headers);
let _ = mem::replace(req.method_mut(), http_method);
req
}
pub(crate) fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
let http_method = ag.http_method;
let builder = http::Request::builder()
.uri({
let scheme = ag
.headers
.get(x_forwarded_proto())
.and_then(|s| s.to_str().ok())
.unwrap_or("https");
let host = ag
.headers
.get(http::header::HOST)
.and_then(|s| s.to_str().ok())
.unwrap_or("localhost");
format!("{}://{}{}", scheme, host, ag.path.unwrap_or_default())
})
.extension(QueryStringParameters(
if ag.multi_value_query_string_parameters.is_empty() {
StrMap::from(ag.query_string_parameters)
} else {
StrMap::from(ag.multi_value_query_string_parameters)
},
))
.extension(PathParameters(StrMap::from(ag.path_parameters)))
.extension(StageVariables(StrMap::from(ag.stage_variables)))
.extension(RequestContext::ApiGatewayV1(ag.request_context));
let mut headers = ag.multi_value_headers;
headers.extend(ag.headers);
let base64 = ag.is_base64_encoded.unwrap_or_default();
let mut req = builder
.body(
ag.body
.as_deref()
.map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
)
.expect("failed to build request");
let _ = mem::replace(req.headers_mut(), headers);
let _ = mem::replace(req.method_mut(), http_method);
req
}
pub(crate) fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
let http_method = alb.http_method;
let builder = http::Request::builder()
.uri({
let scheme = alb
.headers
.get(x_forwarded_proto())
.and_then(|s| s.to_str().ok())
.unwrap_or("https");
let host = alb
.headers
.get(http::header::HOST)
.and_then(|s| s.to_str().ok())
.unwrap_or("localhost");
format!("{}://{}{}", scheme, host, alb.path.unwrap_or_default())
})
.extension(QueryStringParameters(
if alb.multi_value_query_string_parameters.is_empty() {
StrMap::from(alb.query_string_parameters)
} else {
StrMap::from(alb.multi_value_query_string_parameters)
},
))
.extension(RequestContext::Alb(alb.request_context));
let mut headers = alb.multi_value_headers;
headers.extend(alb.headers);
let base64 = alb.is_base64_encoded;
let mut req = builder
.body(
alb.body
.as_deref()
.map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
)
.expect("failed to build request");
let _ = mem::replace(req.headers_mut(), headers);
let _ = mem::replace(req.method_mut(), http_method);
req
}
pub fn from_reader<R>(rdr: R) -> Result<crate::Request, JsonError>
where
R: Read,
{
serde_json::from_reader(rdr).map(LambdaRequest::into)
}
pub fn from_str(s: &str) -> Result<crate::Request, JsonError> {
serde_json::from_str(s).map(LambdaRequest::into)
}
fn x_forwarded_proto() -> HeaderName {
HeaderName::from_static("x-forwarded-proto")
}