Skip to content

Commit 84d41c9

Browse files
committed
feat(lambda-http): implement tower::Service (part 3)
1 parent 3f84dfc commit 84d41c9

File tree

5 files changed

+80
-58
lines changed

5 files changed

+80
-58
lines changed

lambda-http/examples/hello-http.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
use lambda_http::{
2-
handler,
3-
lambda_runtime::{self, Context, Error},
4-
IntoResponse, Request, RequestExt, Response,
5-
};
1+
use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response};
62

73
#[tokio::main]
84
async fn main() -> Result<(), Error> {
9-
lambda_runtime::run(handler(func)).await?;
5+
lambda_http::run(service_fn(func)).await?;
106
Ok(())
117
}
128

13-
async fn func(event: Request, _: Context) -> Result<impl IntoResponse, Error> {
9+
async fn func(event: Request) -> Result<impl IntoResponse, Error> {
1410
Ok(match event.query_string_parameters().get("first_name") {
1511
Some(first_name) => format!("Hello, {}!", first_name).into_response(),
1612
_ => Response::builder()

lambda-http/examples/shared-resources-example.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
use lambda_http::{
2-
handler,
3-
lambda_runtime::{self, Context, Error},
4-
IntoResponse, Request, RequestExt, Response,
5-
};
1+
use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response};
62

73
struct SharedClient {
84
name: &'static str,
@@ -23,9 +19,11 @@ async fn main() -> Result<(), Error> {
2319
let shared_client_ref = &shared_client;
2420

2521
// Define a closure here that makes use of the shared client.
26-
let handler_func_closure = move |event: Request, ctx: Context| async move {
22+
let handler_func_closure = move |event: Request| async move {
2723
Ok(match event.query_string_parameters().get("first_name") {
28-
Some(first_name) => shared_client_ref.response(ctx.request_id, first_name).into_response(),
24+
Some(first_name) => shared_client_ref
25+
.response(event.lambda_context().request_id, first_name)
26+
.into_response(),
2927
_ => Response::builder()
3028
.status(400)
3129
.body("Empty first name".into())
@@ -34,6 +32,6 @@ async fn main() -> Result<(), Error> {
3432
};
3533

3634
// Pass the closure to the runtime here.
37-
lambda_runtime::run(handler(handler_func_closure)).await?;
35+
lambda_http::run(service_fn(handler_func_closure)).await?;
3836
Ok(())
3937
}

lambda-http/src/ext.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Extension methods for `http::Request` types
22
33
use crate::{request::RequestContext, strmap::StrMap, Body};
4+
use lambda_runtime::Context;
45
use serde::{de::value::Error as SerdeError, Deserialize};
56
use std::{error::Error, fmt};
67

@@ -167,6 +168,12 @@ pub trait RequestExt {
167168
fn payload<D>(&self) -> Result<Option<D>, PayloadError>
168169
where
169170
for<'de> D: Deserialize<'de>;
171+
172+
/// Return the Lambda function context associated with the request
173+
fn lambda_context(&self) -> Context;
174+
175+
/// Configures instance with lambda context
176+
fn with_lambda_context(self, context: Context) -> Self;
170177
}
171178

172179
impl RequestExt for http::Request<Body> {
@@ -226,6 +233,19 @@ impl RequestExt for http::Request<Body> {
226233
.expect("Request did not contain a request context")
227234
}
228235

236+
fn lambda_context(&self) -> Context {
237+
self.extensions()
238+
.get::<Context>()
239+
.cloned()
240+
.expect("Request did not contain a lambda context")
241+
}
242+
243+
fn with_lambda_context(self, context: Context) -> Self {
244+
let mut s = self;
245+
s.extensions_mut().insert(context);
246+
s
247+
}
248+
229249
fn payload<D>(&self) -> Result<Option<D>, PayloadError>
230250
where
231251
for<'de> D: Deserialize<'de>,

lambda-http/src/lib.rs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
//! your function's execution path.
1818
//!
1919
//! ```rust,no_run
20-
//! use lambda_http::{handler, lambda_runtime::{self, Error}};
20+
//! use lambda_http::{service_fn, Error};
2121
//!
2222
//! #[tokio::main]
2323
//! async fn main() -> Result<(), Error> {
2424
//! // initialize dependencies once here for the lifetime of your
2525
//! // lambda task
26-
//! lambda_runtime::run(handler(|request, context| async { Ok("👋 world!") })).await?;
26+
//! lambda_http::run(service_fn(|request| async { Ok("👋 world!") })).await?;
2727
//! Ok(())
2828
//! }
2929
//! ```
@@ -34,17 +34,16 @@
3434
//! with the [`RequestExt`](trait.RequestExt.html) trait.
3535
//!
3636
//! ```rust,no_run
37-
//! use lambda_http::{handler, lambda_runtime::{self, Context, Error}, IntoResponse, Request, RequestExt};
37+
//! use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt};
3838
//!
3939
//! #[tokio::main]
4040
//! async fn main() -> Result<(), Error> {
41-
//! lambda_runtime::run(handler(hello)).await?;
41+
//! lambda_http::run(service_fn(hello)).await?;
4242
//! Ok(())
4343
//! }
4444
//!
4545
//! async fn hello(
46-
//! request: Request,
47-
//! _: Context
46+
//! request: Request
4847
//! ) -> Result<impl IntoResponse, Error> {
4948
//! Ok(format!(
5049
//! "hello {}",
@@ -62,11 +61,8 @@
6261
extern crate maplit;
6362

6463
pub use http::{self, Response};
65-
pub use lambda_runtime::{self, Context};
66-
use lambda_runtime::{
67-
tower::util::{service_fn, ServiceFn},
68-
Error, LambdaEvent, Service,
69-
};
64+
pub use lambda_runtime::{self, tower::util::service_fn, Context, Error};
65+
use lambda_runtime::{LambdaEvent, Service};
7066

7167
mod body;
7268
pub mod ext;
@@ -88,27 +84,10 @@ use std::{
8884
/// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type
8985
pub type Request = http::Request<Body>;
9086

91-
/// Wraps a function that takes 2 arguments into one that only takes a [`LambdaEvent`]
92-
fn handler_wrapper<A, Fut>(f: impl Fn(A, Context) -> Fut) -> impl Fn(LambdaEvent<A>) -> Fut {
93-
move |req| f(req.event, req.context)
94-
}
95-
96-
/// Adapts a [`Service`] into another [`Service`].
97-
pub fn handler<'a, R, Fut>(
98-
f: impl Fn(Request, Context) -> Fut,
99-
) -> Adapter<'a, R, ServiceFn<impl Fn(LambdaEvent<Request>) -> Fut>>
100-
where
101-
R: IntoResponse,
102-
Fut: Future<Output = Result<R, Error>>,
103-
{
104-
Adapter {
105-
service: service_fn(handler_wrapper(f)),
106-
_phantom_data: PhantomData,
107-
}
108-
}
109-
110-
#[doc(hidden)]
111-
pub struct TransformResponse<'a, R, E> {
87+
/// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
88+
///
89+
/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
90+
struct TransformResponse<'a, R, E> {
11291
request_origin: RequestOrigin,
11392
fut: Pin<Box<dyn Future<Output = Result<R, E>> + Send + 'a>>,
11493
}
@@ -129,15 +108,31 @@ where
129108
}
130109
}
131110

132-
#[doc(hidden)]
133-
pub struct Adapter<'a, R, S> {
111+
/// Wraps a `Service<Request>` in a `Service<LambdaEvent<Request>>`
112+
///
113+
/// This is completely internal to the `lambda_http::run` function.
114+
struct Adapter<'a, R, S> {
134115
service: S,
135116
_phantom_data: PhantomData<&'a R>,
136117
}
137118

119+
impl<'a, R, S> From<S> for Adapter<'a, R, S>
120+
where
121+
S: Service<Request, Response = R, Error = Error> + Send,
122+
S::Future: Send + 'a,
123+
R: IntoResponse,
124+
{
125+
fn from(service: S) -> Self {
126+
Adapter {
127+
service,
128+
_phantom_data: PhantomData,
129+
}
130+
}
131+
}
132+
138133
impl<'a, R, S> Service<LambdaEvent<LambdaRequest<'a>>> for Adapter<'a, R, S>
139134
where
140-
S: Service<LambdaEvent<Request>, Response = R, Error = Error> + Send,
135+
S: Service<Request, Response = R, Error = Error> + Send,
141136
S::Future: Send + 'a,
142137
R: IntoResponse,
143138
{
@@ -151,7 +146,22 @@ where
151146

152147
fn call(&mut self, req: LambdaEvent<LambdaRequest<'a>>) -> Self::Future {
153148
let request_origin = req.event.request_origin();
154-
let fut = Box::pin(self.service.call(LambdaEvent::new(req.event.into(), req.context)));
149+
let event: Request = req.event.into();
150+
let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));
155151
TransformResponse { request_origin, fut }
156152
}
157153
}
154+
155+
/// Starts the Lambda Rust runtime and begins polling for events on the [Lambda
156+
/// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html).
157+
///
158+
/// This takes care of transforming the LambdaEvent into a [`Request`] and then
159+
/// converting the result into a [`LambdaResponse`].
160+
pub async fn run<'a, S, R>(handler: S) -> Result<(), Error>
161+
where
162+
S: Service<Request, Response = R, Error = Error> + Send,
163+
S::Future: Send + 'a,
164+
R: IntoResponse,
165+
{
166+
lambda_runtime::run(Adapter::from(handler)).await
167+
}

lambda-integration-tests/src/bin/http-fn.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
use lambda_http::{
2-
lambda_runtime::{self, Context, Error},
3-
IntoResponse, Request, Response,
4-
};
1+
use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response};
52
use tracing::info;
63

7-
async fn handler(event: Request, _context: Context) -> Result<impl IntoResponse, Error> {
4+
async fn handler(event: Request) -> Result<impl IntoResponse, Error> {
5+
let _context = event.lambda_context();
86
info!("[http-fn] Received event {} {}", event.method(), event.uri().path());
97

108
Ok(Response::builder().status(200).body("Hello, world!").unwrap())
@@ -23,6 +21,6 @@ async fn main() -> Result<(), Error> {
2321
.without_time()
2422
.init();
2523

26-
let handler = lambda_http::handler(handler);
27-
lambda_runtime::run(handler).await
24+
let handler = service_fn(handler);
25+
lambda_http::run(handler).await
2826
}

0 commit comments

Comments
 (0)