Skip to content

lambda_http: Add convenience methods to get references to data in the request #602

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

Merged
merged 2 commits into from
Feb 21, 2023
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
123 changes: 99 additions & 24 deletions lambda-http/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl Error for PayloadError {
}
}

/// Extentions for `lambda_http::Request` structs that
/// Extensions for `lambda_http::Request` structs that
/// provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format)
/// and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html)
/// features.
Expand Down Expand Up @@ -109,6 +109,9 @@ pub trait RequestExt {
/// Return the raw http path for a request without any stage information.
fn raw_http_path(&self) -> String;

/// Return the raw http path for a request without any stage information.
fn raw_http_path_str(&self) -> &str;

/// Configures instance with the raw http path.
fn with_raw_http_path(self, path: &str) -> Self;

Expand All @@ -118,12 +121,24 @@ pub trait RequestExt {
///
/// The yielded value represents both single and multi-valued
/// parameters alike. When multiple query string parameters with the same
/// name are expected, `query_string_parameters().get_all("many")` to retrieve them all.
/// name are expected, `query_string_parameters().all("many")` to retrieve them all.
///
/// No query parameters
/// will yield an empty `QueryMap`.
/// No query parameters will yield an empty `QueryMap`.
fn query_string_parameters(&self) -> QueryMap;

/// Return pre-parsed http query string parameters, parameters
/// provided after the `?` portion of a url,
/// associated with the API gateway request.
///
/// The yielded value represents both single and multi-valued
/// parameters alike. When multiple query string parameters with the same
/// name are expected,
/// `query_string_parameters_ref().and_then(|params| params.all("many"))` to
/// retrieve them all.
///
/// No query parameters will yield `None`.
fn query_string_parameters_ref(&self) -> Option<&QueryMap>;

/// Configures instance with query string parameters
///
/// This is intended for use in mock testing contexts.
Expand All @@ -139,6 +154,14 @@ pub trait RequestExt {
/// These will always be empty for ALB triggered requests
fn path_parameters(&self) -> QueryMap;

/// Return pre-extracted path parameters, parameter provided in url placeholders
/// `/foo/{bar}/baz/{boom}`,
/// associated with the API gateway request. No path parameters
/// will yield `None`
///
/// These will always be `None` for ALB triggered requests
fn path_parameters_ref(&self) -> Option<&QueryMap>;

/// Configures instance with path parameters
///
/// This is intended for use in mock testing contexts.
Expand All @@ -153,6 +176,13 @@ pub trait RequestExt {
/// These will always be empty for ALB triggered requests
fn stage_variables(&self) -> QueryMap;

/// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html)
/// associated with the API gateway request. No stage parameters
/// will yield `None`
///
/// These will always be `None` for ALB triggered requests
fn stage_variables_ref(&self) -> Option<&QueryMap>;

/// Configures instance with stage variables under #[cfg(test)] configurations
///
/// This is intended for use in mock testing contexts.
Expand All @@ -164,6 +194,10 @@ pub trait RequestExt {
/// Return request context data associated with the ALB or API gateway request
fn request_context(&self) -> RequestContext;

/// Return a reference to the request context data associated with the ALB or
/// API gateway request
fn request_context_ref(&self) -> Option<&RequestContext>;

/// Configures instance with request context
///
/// This is intended for use in mock testing contexts.
Expand All @@ -185,15 +219,23 @@ pub trait RequestExt {
/// Return the Lambda function context associated with the request
fn lambda_context(&self) -> Context;

/// Return a reference to the the Lambda function context associated with the
/// request
fn lambda_context_ref(&self) -> Option<&Context>;

/// Configures instance with lambda context
fn with_lambda_context(self, context: Context) -> Self;
}

impl RequestExt for http::Request<Body> {
fn raw_http_path(&self) -> String {
self.raw_http_path_str().to_string()
}

fn raw_http_path_str(&self) -> &str {
self.extensions()
.get::<RawHttpPath>()
.map(|ext| ext.0.clone())
.map(|RawHttpPath(path)| path.as_str())
.unwrap_or_default()
}

Expand All @@ -204,10 +246,19 @@ impl RequestExt for http::Request<Body> {
}

fn query_string_parameters(&self) -> QueryMap {
self.extensions()
.get::<QueryStringParameters>()
.map(|ext| ext.0.clone())
.unwrap_or_default()
self.query_string_parameters_ref().cloned().unwrap_or_default()
}

fn query_string_parameters_ref(&self) -> Option<&QueryMap> {
self.extensions().get::<QueryStringParameters>().and_then(
|QueryStringParameters(params)| {
if params.is_empty() {
None
} else {
Some(params)
}
},
)
}

fn with_query_string_parameters<Q>(self, parameters: Q) -> Self
Expand All @@ -220,10 +271,19 @@ impl RequestExt for http::Request<Body> {
}

fn path_parameters(&self) -> QueryMap {
self.extensions()
.get::<PathParameters>()
.map(|ext| ext.0.clone())
.unwrap_or_default()
self.path_parameters_ref().cloned().unwrap_or_default()
}

fn path_parameters_ref(&self) -> Option<&QueryMap> {
self.extensions().get::<PathParameters>().and_then(
|PathParameters(params)| {
if params.is_empty() {
None
} else {
Some(params)
}
},
)
}

fn with_path_parameters<P>(self, parameters: P) -> Self
Expand All @@ -236,10 +296,19 @@ impl RequestExt for http::Request<Body> {
}

fn stage_variables(&self) -> QueryMap {
self.extensions()
.get::<StageVariables>()
.map(|ext| ext.0.clone())
.unwrap_or_default()
self.stage_variables_ref().cloned().unwrap_or_default()
}

fn stage_variables_ref(&self) -> Option<&QueryMap> {
self.extensions().get::<StageVariables>().and_then(
|StageVariables(vars)| {
if vars.is_empty() {
None
} else {
Some(vars)
}
},
)
}

#[cfg(test)]
Expand All @@ -253,25 +322,31 @@ impl RequestExt for http::Request<Body> {
}

fn request_context(&self) -> RequestContext {
self.extensions()
.get::<RequestContext>()
self.request_context_ref()
.cloned()
.expect("Request did not contain a request context")
}

fn request_context_ref(&self) -> Option<&RequestContext> {
self.extensions().get::<RequestContext>()
}

fn with_request_context(self, context: RequestContext) -> Self {
let mut s = self;
s.extensions_mut().insert(context);
s
}

fn lambda_context(&self) -> Context {
self.extensions()
.get::<Context>()
self.lambda_context_ref()
.cloned()
.expect("Request did not contain a lambda context")
}

fn lambda_context_ref(&self) -> Option<&Context> {
self.extensions().get::<Context>()
}

fn with_lambda_context(self, context: Context) -> Self {
let mut s = self;
s.extensions_mut().insert(context);
Expand Down Expand Up @@ -358,7 +433,7 @@ mod tests {
}

#[test]
fn requests_have_json_parseable_payloads() {
fn requests_have_json_parsable_payloads() {
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct Payload {
foo: String,
Expand Down Expand Up @@ -421,15 +496,15 @@ mod tests {
}

#[test]
fn requests_omiting_content_types_do_not_support_parseable_payloads() {
fn requests_omitting_content_types_do_not_support_parsable_payloads() {
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct Payload {
foo: String,
baz: usize,
}
let request = http::Request::builder()
.body(Body::from(r#"{"foo":"bar", "baz": 2}"#))
.expect("failed to bulid request");
.expect("failed to build request");
let payload: Option<Payload> = request.payload().unwrap_or_default();
assert_eq!(payload, None);
}
Expand Down
2 changes: 1 addition & 1 deletion lambda-http/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request<
.extension(RequestContext::WebSocket(ag.request_context));

// merge headers into multi_value_headers and make
// multi-value_headers our cannoncial source of request headers
// multi-value_headers our canonical source of request headers
let mut headers = ag.multi_value_headers;
headers.extend(ag.headers);
update_xray_trace_id_header(&mut headers);
Expand Down