Skip to content
Merged
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
1 change: 1 addition & 0 deletions lambda-events/src/event/alb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub struct ElbContext {
}

/// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AlbTargetGroupResponse {
Expand Down
4 changes: 4 additions & 0 deletions lambda-events/src/event/apigw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub struct ApiGatewayProxyRequest {
}

/// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiGatewayProxyResponse {
Expand Down Expand Up @@ -349,6 +350,7 @@ pub struct ApiGatewayV2httpRequestContextHttpDescription {
}

/// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiGatewayV2httpResponse {
Expand Down Expand Up @@ -897,6 +899,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequest {
}

/// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response.
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiGatewayCustomAuthorizerResponse<T1 = Value>
Expand Down Expand Up @@ -963,6 +966,7 @@ where
}

/// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct ApiGatewayCustomAuthorizerPolicy {
Expand Down
1 change: 1 addition & 0 deletions lambda-events/src/event/iam/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct IamPolicyDocument {
}

/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct IamPolicyStatement {
Expand Down
4 changes: 4 additions & 0 deletions lambda-http/src/ext/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,27 @@ use lambda_runtime::Context;
use crate::request::RequestContext;

/// ALB/API gateway pre-parsed http query string parameters
#[non_exhaustive]
#[derive(Clone)]
pub(crate) struct QueryStringParameters(pub(crate) QueryMap);

/// API gateway pre-extracted url path parameters
///
/// These will always be empty for ALB requests
#[non_exhaustive]
#[derive(Clone)]
pub(crate) struct PathParameters(pub(crate) QueryMap);

/// API gateway configured
/// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html)
///
/// These will always be empty for ALB requests
#[non_exhaustive]
#[derive(Clone)]
pub(crate) struct StageVariables(pub(crate) QueryMap);

/// ALB/API gateway raw http path without any stage information
#[non_exhaustive]
#[derive(Clone)]
pub(crate) struct RawHttpPath(pub(crate) String);

Expand Down
6 changes: 3 additions & 3 deletions lambda-http/src/ext/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::Body;
/// Request payload deserialization errors
///
/// Returned by [`RequestPayloadExt::payload()`]
#[derive(Debug)]
#[non_exhaustive]
#[derive(Debug)]
pub enum PayloadError {
/// Returned when `application/json` bodies fail to deserialize a payload
Json(serde_json::Error),
Expand All @@ -22,16 +22,16 @@ pub enum PayloadError {
}

/// Indicates a problem processing a JSON payload.
#[derive(Debug)]
#[non_exhaustive]
#[derive(Debug)]
pub enum JsonPayloadError {
/// Problem deserializing a JSON payload.
Parsing(serde_json::Error),
}

/// Indicates a problem processing an x-www-form-urlencoded payload.
#[derive(Debug)]
#[non_exhaustive]
#[derive(Debug)]
pub enum FormUrlEncodedPayloadError {
/// Problem deserializing an x-www-form-urlencoded payload.
Parsing(SerdeError),
Expand Down
2 changes: 2 additions & 0 deletions lambda-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub type Request = http::Request<Body>;
/// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
///
/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
#[non_exhaustive]
#[doc(hidden)]
pub enum TransformResponse<'a, R, E> {
Request(RequestOrigin, RequestFuture<'a, R, E>),
Expand Down Expand Up @@ -143,6 +144,7 @@ where
/// Wraps a `Service<Request>` in a `Service<LambdaEvent<Request>>`
///
/// This is completely internal to the `lambda_http::run` function.
#[non_exhaustive]
#[doc(hidden)]
pub struct Adapter<'a, R, S> {
service: S,
Expand Down
3 changes: 3 additions & 0 deletions lambda-http/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use url::Url;
///
/// This is not intended to be a type consumed by crate users directly. The order
/// of the variants are notable. Serde will try to deserialize in this order.
#[non_exhaustive]
#[doc(hidden)]
#[derive(Debug)]
pub enum LambdaRequest {
Expand Down Expand Up @@ -85,6 +86,7 @@ impl LambdaRequest {
pub type RequestFuture<'a, R, E> = Pin<Box<dyn Future<Output = Result<R, E>> + Send + 'a>>;

/// Represents the origin from which the lambda was requested from.
#[non_exhaustive]
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum RequestOrigin {
Expand Down Expand Up @@ -388,6 +390,7 @@ fn apigw_path_with_stage(stage: &Option<String>, path: &str) -> String {

/// Event request context as an enumeration of request contexts
/// for both ALB and API Gateway and HTTP API events
#[non_exhaustive]
#[derive(Deserialize, Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum RequestContext {
Expand Down
81 changes: 50 additions & 31 deletions lambda-http/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const TEXT_ENCODING_PREFIXES: [&str; 5] = [
const TEXT_ENCODING_SUFFIXES: [&str; 3] = ["+xml", "+yaml", "+json"];

/// Representation of Lambda response
#[non_exhaustive]
#[doc(hidden)]
#[derive(Serialize, Debug)]
#[serde(untagged)]
Expand Down Expand Up @@ -70,17 +71,22 @@ impl LambdaResponse {

match request_origin {
#[cfg(feature = "apigw_rest")]
RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse {
body,
is_base64_encoded,
status_code: status_code as i64,
RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1({
let mut response = ApiGatewayProxyResponse::default();

response.body = body;
response.is_base64_encoded = is_base64_encoded;
response.status_code = status_code as i64;
// Explicitly empty, as API gateway v1 will merge "headers" and
// "multi_value_headers" fields together resulting in duplicate response headers.
headers: HeaderMap::new(),
multi_value_headers: headers,
response.headers = HeaderMap::new();
response.multi_value_headers = headers;
// Today, this implementation doesn't provide any additional fields
#[cfg(feature = "catch-all-fields")]
other: Default::default()
{
response.other = Default::default();
}
response
}),
#[cfg(feature = "apigw_http")]
RequestOrigin::ApiGatewayV2 => {
Expand All @@ -96,51 +102,64 @@ impl LambdaResponse {
.collect();
headers.remove(SET_COOKIE);

LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse {
body,
is_base64_encoded,
status_code: status_code as i64,
cookies,
LambdaResponse::ApiGatewayV2({
let mut response = ApiGatewayV2httpResponse::default();
response.body = body;
response.is_base64_encoded = is_base64_encoded;
response.status_code = status_code as i64;
response.cookies = cookies;
// API gateway v2 doesn't have "multi_value_headers" field. Duplicate headers
// are combined with commas and included in the headers field.
headers,
multi_value_headers: HeaderMap::new(),
response.headers = headers;
response.multi_value_headers = HeaderMap::new();
// Today, this implementation doesn't provide any additional fields
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
response.other = Default::default();
}
response
})
}
#[cfg(feature = "alb")]
RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse {
body,
status_code: status_code as i64,
is_base64_encoded,
RequestOrigin::Alb => LambdaResponse::Alb({
let mut response = AlbTargetGroupResponse::default();

response.body = body;
response.is_base64_encoded = is_base64_encoded;
response.status_code = status_code as i64;
// ALB responses are used for ALB integration, which can be configured to use
// either "headers" or "multi_value_headers" field. We need to return both fields
// to ensure both configuration work correctly.
headers: headers.clone(),
multi_value_headers: headers,
status_description: Some(format!(
response.headers = headers.clone();
response.multi_value_headers = headers;
response.status_description = Some(format!(
"{} {}",
status_code,
parts.status.canonical_reason().unwrap_or_default()
)),
));
// Today, this implementation doesn't provide any additional fields
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
response.other = Default::default();
}
response
}),
#[cfg(feature = "apigw_websockets")]
RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse {
body,
is_base64_encoded,
status_code: status_code as i64,
RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1({
let mut response = ApiGatewayProxyResponse::default();
response.body = body;
response.is_base64_encoded = is_base64_encoded;
response.status_code = status_code as i64;
// Explicitly empty, as API gateway v1 will merge "headers" and
// "multi_value_headers" fields together resulting in duplicate response headers.
headers: HeaderMap::new(),
multi_value_headers: headers,
response.headers = HeaderMap::new();
response.multi_value_headers = headers;
// Today, this implementation doesn't provide any additional fields
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
response.other = Default::default();
}
response
}),
#[cfg(feature = "pass_through")]
RequestOrigin::PassThrough => {
Expand Down
2 changes: 2 additions & 0 deletions lambda-http/src/streaming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use std::{future::Future, marker::PhantomData};
/// An adapter that lifts a standard [`Service<Request>`] into a
/// [`Service<LambdaEvent<LambdaRequest>>`] which produces streaming Lambda HTTP
/// responses.
#[non_exhaustive]
pub struct StreamAdapter<'a, S, B> {
service: S,
_phantom_data: PhantomData<&'a B>,
Expand Down Expand Up @@ -147,6 +148,7 @@ where
}

pin_project_lite::pin_project! {
#[non_exhaustive]
pub struct BodyStream<B> {
#[pin]
pub(crate) body: B,
Expand Down
44 changes: 27 additions & 17 deletions lambda-integration-tests/src/authorizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,36 @@ async fn func(
}

fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse {
let stmt = IamPolicyStatement {
action: vec!["execute-api:Invoke".to_string()],
resource: vec![method_arn.to_owned()],
effect: aws_lambda_events::iam::IamPolicyEffect::Allow,
condition: None,
let stmt = {
let mut statement = IamPolicyStatement::default();
statement.action = vec!["execute-api:Invoke".to_string()];
statement.resource = vec![method_arn.to_owned()];
statement.effect = aws_lambda_events::iam::IamPolicyEffect::Allow;
statement.condition = None;
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
statement.other = Default::default();
}
statement
};
let policy = ApiGatewayCustomAuthorizerPolicy {
version: Some("2012-10-17".to_string()),
statement: vec![stmt],
let policy = {
let mut policy = ApiGatewayCustomAuthorizerPolicy::default();
policy.version = Some("2012-10-17".to_string());
policy.statement = vec![stmt];
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
policy.other = Default::default();
}
policy
};
ApiGatewayCustomAuthorizerResponse {
principal_id: Some("user".to_owned()),
policy_document: policy,
context: json!({ "hello": "world" }),
usage_identifier_key: None,
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
let mut response = ApiGatewayCustomAuthorizerResponse::default();
response.principal_id = Some("user".to_owned());
response.policy_document = policy;
response.context = json!({ "hello": "world" });
response.usage_identifier_key = None;
#[cfg(feature = "catch-all-fields")]
{
response.other = Default::default();
}
response
}
18 changes: 11 additions & 7 deletions lambda-integration-tests/src/helloworld.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ async fn main() -> Result<(), Error> {
async fn func(_event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<ApiGatewayProxyResponse, Error> {
let mut headers = HeaderMap::new();
headers.insert("content-type", "text/html".parse().unwrap());
let resp = ApiGatewayProxyResponse {
status_code: 200,
multi_value_headers: headers.clone(),
is_base64_encoded: false,
body: Some("Hello world!".into()),
headers,
let resp = {
let mut response = ApiGatewayProxyResponse::default();
response.status_code = 200;
response.multi_value_headers = headers.clone();
response.is_base64_encoded = false;
response.body = Some("Hello world!".into());
response.headers = headers;
#[cfg(feature = "catch-all-fields")]
other: Default::default(),
{
response.other = Default::default();
}
response
};
Ok(resp)
}
Loading