Skip to content

Commit 771e598

Browse files
softpropsdavidbarsky
authored andcommitted
Introduce lambda_http::IntoResponse (#31)
1 parent b974460 commit 771e598

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

lambda-http/examples/basic.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ extern crate lambda_runtime as runtime;
33
extern crate log;
44
extern crate simple_logger;
55

6-
use http::{lambda, Body, Request, RequestExt, Response};
6+
use http::{lambda, Body, IntoResponse, Request, RequestExt, Response};
77
use runtime::{error::HandlerError, Context};
88

99
use log::error;
@@ -16,14 +16,14 @@ fn main() -> Result<(), Box<dyn Error>> {
1616
Ok(())
1717
}
1818

19-
fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
19+
fn my_handler(e: Request, c: Context) -> Result<impl IntoResponse, HandlerError> {
2020
Ok(match e.query_string_parameters().get("first_name") {
21-
Some(first_name) => Response::new(format!("Hello, {}!", first_name).into()),
21+
Some(first_name) => format!("Hello, {}!", first_name).into_response(),
2222
_ => {
2323
error!("Empty first name in request {}", c.aws_request_id);
2424
Response::builder()
2525
.status(400)
26-
.body::<Body>("Empty first name".into())
26+
.body("Empty first name".into())
2727
.expect("failed to render response")
2828
}
2929
})

lambda-http/src/lib.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! extern crate lambda_runtime as lambda;
1010
//!
1111
//! use lambda::{Context, HandlerError};
12-
//! use lambda_http::{Body, Request, Response, RequestExt};
12+
//! use lambda_http::{Request, IntoResponse, RequestExt};
1313
//!
1414
//! fn main() {
1515
//! lambda!(handler)
@@ -18,16 +18,14 @@
1818
//! fn handler(
1919
//! request: Request,
2020
//! ctx: Context
21-
//! ) -> Result<Response<Body>, HandlerError> {
21+
//! ) -> Result<impl IntoResponse, HandlerError> {
2222
//! Ok(
23-
//! Response::new(
2423
//! format!(
2524
//! "hello {}",
2625
//! request.query_string_parameters()
2726
//! .get("name")
2827
//! .unwrap_or_else(|| "stranger")
29-
//! ).into()
30-
//! )
28+
//! )
3129
//! )
3230
//! }
3331
//! ```
@@ -60,22 +58,23 @@ pub use body::Body;
6058
pub use ext::RequestExt;
6159
use request::GatewayRequest;
6260
use response::GatewayResponse;
61+
pub use response::IntoResponse;
6362
pub use strmap::StrMap;
6463

6564
/// Type alias for `http::Request`s with a fixed `lambda_http::Body` body
6665
pub type Request = HttpRequest<Body>;
6766

6867
/// Functions acting as API Gateway handlers must conform to this type.
69-
pub trait Handler {
68+
pub trait Handler<R> {
7069
/// Run the handler.
71-
fn run(&mut self, event: Request, ctx: Context) -> Result<Response<Body>, HandlerError>;
70+
fn run(&mut self, event: Request, ctx: Context) -> Result<R, HandlerError>;
7271
}
7372

74-
impl<F> Handler for F
73+
impl<F, R> Handler<R> for F
7574
where
76-
F: FnMut(Request, Context) -> Result<Response<Body>, HandlerError>,
75+
F: FnMut(Request, Context) -> Result<R, HandlerError>,
7776
{
78-
fn run(&mut self, event: Request, ctx: Context) -> Result<Response<Body>, HandlerError> {
77+
fn run(&mut self, event: Request, ctx: Context) -> Result<R, HandlerError> {
7978
(*self)(event, ctx)
8079
}
8180
}
@@ -88,11 +87,17 @@ where
8887
///
8988
/// # Panics
9089
/// The function panics if the Lambda environment variables are not set.
91-
pub fn start(f: impl Handler, runtime: Option<TokioRuntime>) {
90+
pub fn start<R>(f: impl Handler<R>, runtime: Option<TokioRuntime>)
91+
where
92+
R: IntoResponse,
93+
{
9294
// handler requires a mutable ref
9395
let mut func = f;
9496
lambda::start(
95-
|req: GatewayRequest, ctx: Context| func.run(req.into(), ctx).map(GatewayResponse::from),
97+
|req: GatewayRequest, ctx: Context| {
98+
func.run(req.into(), ctx)
99+
.map(|resp| GatewayResponse::from(resp.into_response()))
100+
},
96101
runtime,
97102
)
98103
}

lambda-http/src/response.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ops::Not;
55

66
use http::{
77
header::{HeaderMap, HeaderValue},
8-
Response as HttpResponse,
8+
Response,
99
};
1010
use serde::{
1111
ser::{Error as SerError, SerializeMap},
@@ -50,11 +50,11 @@ where
5050
map.end()
5151
}
5252

53-
impl<T> From<HttpResponse<T>> for GatewayResponse
53+
impl<T> From<Response<T>> for GatewayResponse
5454
where
5555
T: Into<Body>,
5656
{
57-
fn from(value: HttpResponse<T>) -> Self {
57+
fn from(value: Response<T>) -> Self {
5858
let (parts, bod) = value.into_parts();
5959
let (is_base64_encoded, body) = match bod.into() {
6060
Body::Empty => (false, None),
@@ -70,11 +70,89 @@ where
7070
}
7171
}
7272

73+
/// A conversion of self into a `Response`
74+
///
75+
/// Implementations for `Response<B> where B: Into<Body>`,
76+
/// `B where B: Into<Body>` and `serde_json::Value` are provided
77+
/// by default
78+
///
79+
/// # example
80+
///
81+
/// ```rust
82+
/// use lambda_http::{Body, IntoResponse, Response};
83+
///
84+
/// assert_eq!(
85+
/// "hello".into_response().body(),
86+
/// Response::new(Body::from("hello")).body()
87+
/// );
88+
/// ```
89+
pub trait IntoResponse {
90+
/// Return a translation of `self` into a `Response<Body>`
91+
fn into_response(self) -> Response<Body>;
92+
}
93+
94+
impl<B> IntoResponse for Response<B>
95+
where
96+
B: Into<Body>,
97+
{
98+
fn into_response(self) -> Response<Body> {
99+
let (parts, body) = self.into_parts();
100+
Response::from_parts(parts, body.into())
101+
}
102+
}
103+
104+
impl<B> IntoResponse for B
105+
where
106+
B: Into<Body>,
107+
{
108+
fn into_response(self) -> Response<Body> {
109+
Response::new(self.into())
110+
}
111+
}
112+
113+
impl IntoResponse for serde_json::Value {
114+
fn into_response(self) -> Response<Body> {
115+
Response::builder()
116+
.header(http::header::CONTENT_TYPE, "application/json")
117+
.body(
118+
serde_json::to_string(&self)
119+
.expect("unable to serialize serde_json::Value")
120+
.into(),
121+
)
122+
.expect("unable to build http::Response")
123+
}
124+
}
125+
73126
#[cfg(test)]
74127
mod tests {
75128

76-
use super::GatewayResponse;
77-
use serde_json;
129+
use super::{Body, GatewayResponse, IntoResponse};
130+
use serde_json::{self, json};
131+
132+
#[test]
133+
fn json_into_response() {
134+
let response = json!({ "hello": "lambda"}).into_response();
135+
match response.body() {
136+
Body::Text(json) => assert_eq!(json, r#"{"hello":"lambda"}"#),
137+
_ => panic!("invalid body"),
138+
}
139+
assert_eq!(
140+
response
141+
.headers()
142+
.get(http::header::CONTENT_TYPE)
143+
.map(|h| h.to_str().expect("invalid header")),
144+
Some("application/json")
145+
)
146+
}
147+
148+
#[test]
149+
fn text_into_response() {
150+
let response = "text".into_response();
151+
match response.body() {
152+
Body::Text(text) => assert_eq!(text, "text"),
153+
_ => panic!("invalid body"),
154+
}
155+
}
78156

79157
#[test]
80158
fn default_response() {

0 commit comments

Comments
 (0)