Skip to content

[dont merge] support both apigw and alb events as http::{Request,Response} (cargo features) #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lambda-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ serde_urlencoded = "0.5"
log = "^0.4"
simple_logger = "^1"

[features]
default = ['apigw', 'alb']
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big drawback here. if a consumer sets default-flags to false you'll get neither apigw nor alb! I don't think there's a way to express "there can be at least one!" in cargo

apigw = []
alb = []
1 change: 1 addition & 0 deletions lambda-http/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ mod tests {
foo: String,
baz: usize,
}

let lambda_request: LambdaRequest<'_> = LambdaRequest {
path: "/foo".into(),
headers,
Expand Down
14 changes: 11 additions & 3 deletions lambda-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,17 @@ where
let mut func = f;
lambda::start(
|req: LambdaRequest<'_>, ctx: Context| {
let is_alb = req.request_context.is_alb();
func.run(req.into(), ctx)
.map(|resp| LambdaResponse::from_response(is_alb, resp.into_response()))
#[cfg(feature = "alb")]
{
let is_alb = req.request_context.is_alb();
func.run(req.into(), ctx)
.map(|resp| LambdaResponse::from_response(is_alb, resp.into_response()))
}
#[cfg(not(feature = "alb"))]
{
func.run(req.into(), ctx)
.map(|resp| LambdaResponse::from_response(false, resp.into_response()))
}
},
runtime,
)
Expand Down
66 changes: 54 additions & 12 deletions lambda-http/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub(crate) struct LambdaRequest<'a> {
/// the `lambda.multi_value_headers.enabled` target group setting turned on
#[serde(default, deserialize_with = "nullable_default")]
pub(crate) multi_value_query_string_parameters: StrMap,
/// ALB events do not have path parameters.
#[cfg(feature = "apigw")]
#[serde(default, deserialize_with = "nullable_default")]
pub(crate) path_parameters: StrMap,
/// ALB events do not have stage variables.
#[cfg(feature = "apigw")]
#[serde(default, deserialize_with = "nullable_default")]
pub(crate) stage_variables: StrMap,
pub(crate) body: Option<Cow<'a, str>>,
Expand All @@ -58,6 +58,7 @@ pub(crate) struct LambdaRequest<'a> {

/// Event request context as an enumeration of request contexts
/// for both ALB and API Gateway http events
#[cfg(all(feature = "apigw", feature = "alb"))]
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RequestContext {
Expand All @@ -81,6 +82,17 @@ pub enum RequestContext {
Alb { elb: Elb },
}

/// ALB request context
#[cfg(all(feature = "alb", not(all(feature = "apigw", feature = "alb"))))]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL learned cfg attrs are like a smaller dialect of lisp!

#[derive(Default, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestContext {
pub elb: Elb,
}

/// Api Gateway request context
#[cfg(all(feature = "apigw", not(all(feature = "apigw", feature = "alb"))))]
#[derive(Default, Deserialize, Debug, Clone)]
impl Default for RequestContext {
fn default() -> Self {
RequestContext::ApiGateway {
Expand All @@ -97,16 +109,6 @@ impl Default for RequestContext {
}
}

impl RequestContext {
/// Return true if this request context represents an ALB request
pub fn is_alb(&self) -> bool {
match self {
RequestContext::Alb { .. } => true,
_ => false,
}
}
}

/// Elastic load balancer context information
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
Expand All @@ -115,6 +117,41 @@ pub struct Elb {
pub target_group_arn: String,
}

#[cfg(all(feature = "apigw", feature = "alb"))]
impl Default for RequestContext {
fn default() -> Self {
RequestContext::ApiGateway {
account_id: Default::default(),
resource_id: Default::default(),
stage: Default::default(),
request_id: Default::default(),
resource_path: Default::default(),
http_method: Default::default(),
authorizer: Default::default(),
api_id: Default::default(),
identity: Default::default(),
}
}
}

#[cfg(feature = "alb")]
impl RequestContext {
/// Return true if this request context represents an ALB request
pub fn is_alb(&self) -> bool {
#[cfg(all(feature = "apigw", feature = "alb"))]
{
match self {
RequestContext::Alb { .. } => true,
_ => false,
}
}
#[cfg(all(feature = "alb", not(all(feature = "apigw", feature = "alb"))))]
{
true
}
}
}

/// Identity assoicated with API Gateway request
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -336,6 +373,7 @@ mod tests {
fn requests_convert() {
let mut headers = HeaderMap::new();
headers.insert("Host", "www.rust-lang.org".parse().unwrap());

let lambda_request: LambdaRequest<'_> = LambdaRequest {
path: "/foo".into(),
headers,
Expand All @@ -348,6 +386,7 @@ mod tests {
assert_eq!(expected.method(), actual.method());
}

#[cfg(feature = "apigw")]
#[test]
fn deserializes_apigw_request_events() {
// from the docs
Expand All @@ -357,6 +396,7 @@ mod tests {
assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result));
}

#[cfg(feature = "alb")]
#[test]
fn deserialize_alb_request_events() {
// from the docs
Expand All @@ -366,6 +406,7 @@ mod tests {
assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result));
}

#[cfg(feature = "apigw")]
#[test]
fn deserializes_apigw_multi_value_request_events() {
// from docs
Expand All @@ -388,6 +429,7 @@ mod tests {
);
}

#[cfg(feature = "alb")]
#[test]
fn deserializes_alb_multi_value_request_events() {
// from docs
Expand Down
3 changes: 3 additions & 0 deletions lambda-http/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub(crate) struct LambdaResponse {
pub status_code: u16,
// ALB requires a statusDescription i.e. "200 OK" field but API Gateway returns an error
// when one is provided. only populate this for ALB responses
#[cfg(feature = "alb")]
#[serde(skip_serializing_if = "Option::is_none")]
pub status_description: Option<String>,
#[serde(serialize_with = "serialize_headers")]
Expand All @@ -36,6 +37,7 @@ impl Default for LambdaResponse {
fn default() -> Self {
Self {
status_code: 200,
#[cfg(feature = "alb")]
status_description: Default::default(),
headers: Default::default(),
multi_value_headers: Default::default(),
Expand Down Expand Up @@ -88,6 +90,7 @@ impl LambdaResponse {
};
Self {
status_code: parts.status.as_u16(),
#[cfg(feature = "alb")]
status_description: if is_alb {
Some(format!(
"{} {}",
Expand Down