Skip to content

Commit 1e16502

Browse files
authored
Allow Adapter to be .boxed() (#540)
Allowing `Adapter` to be [`.boxed()`](https://docs.rs/tower/0.4.13/tower/trait.ServiceExt.html#method.boxed) enables easier composition of `tower::Service`s that may include additional middleware prior to `Adapter` converting the `LambdaEvent` to a `Request`.
1 parent bab0235 commit 1e16502

File tree

3 files changed

+69
-10
lines changed

3 files changed

+69
-10
lines changed

lambda-http/src/lib.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub struct Adapter<'a, R, S> {
140140
impl<'a, R, S, E> From<S> for Adapter<'a, R, S>
141141
where
142142
S: Service<Request, Response = R, Error = E>,
143-
S::Future: 'a,
143+
S::Future: Send + 'a,
144144
R: IntoResponse,
145145
{
146146
fn from(service: S) -> Self {
@@ -154,7 +154,7 @@ where
154154
impl<'a, R, S, E> Service<LambdaEvent<LambdaRequest>> for Adapter<'a, R, S>
155155
where
156156
S: Service<Request, Response = R, Error = E>,
157-
S::Future: 'a,
157+
S::Future: Send + 'a,
158158
R: IntoResponse,
159159
{
160160
type Response = LambdaResponse;
@@ -182,9 +182,65 @@ where
182182
pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error>
183183
where
184184
S: Service<Request, Response = R, Error = E>,
185-
S::Future: 'a,
185+
S::Future: Send + 'a,
186186
R: IntoResponse,
187187
E: std::fmt::Debug + std::fmt::Display,
188188
{
189189
lambda_runtime::run(Adapter::from(handler)).await
190190
}
191+
192+
#[cfg(test)]
193+
mod test_adapter {
194+
use std::task::{Context, Poll};
195+
196+
use crate::{
197+
http::{Response, StatusCode},
198+
lambda_runtime::LambdaEvent,
199+
request::LambdaRequest,
200+
response::LambdaResponse,
201+
tower::{util::BoxService, Service, ServiceBuilder, ServiceExt},
202+
Adapter, Body, Request,
203+
};
204+
205+
// A middleware that logs requests before forwarding them to another service
206+
struct LogService<S> {
207+
inner: S,
208+
}
209+
210+
impl<S> Service<LambdaEvent<LambdaRequest>> for LogService<S>
211+
where
212+
S: Service<LambdaEvent<LambdaRequest>>,
213+
{
214+
type Response = S::Response;
215+
type Error = S::Error;
216+
type Future = S::Future;
217+
218+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
219+
self.inner.poll_ready(cx)
220+
}
221+
222+
fn call(&mut self, event: LambdaEvent<LambdaRequest>) -> Self::Future {
223+
// Log the request
224+
println!("Lambda event: {:#?}", event);
225+
226+
self.inner.call(event)
227+
}
228+
}
229+
230+
/// This tests that `Adapter` can be used in a `tower::Service` where the user
231+
/// may require additional middleware between `lambda_runtime::run` and where
232+
/// the `LambdaEvent` is converted into a `Request`.
233+
#[test]
234+
fn adapter_is_boxable() {
235+
let _service: BoxService<LambdaEvent<LambdaRequest>, LambdaResponse, http::Error> = ServiceBuilder::new()
236+
.layer_fn(|service| {
237+
// This could be any middleware that logs, inspects, or manipulates
238+
// the `LambdaEvent` before it's converted to a `Request` by `Adapter`.
239+
240+
LogService { inner: service }
241+
})
242+
.layer_fn(Adapter::from)
243+
.service_fn(|_event: Request| async move { Response::builder().status(StatusCode::OK).body(Body::Empty) })
244+
.boxed();
245+
}
246+
}

lambda-http/src/request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl LambdaRequest {
6161
}
6262

6363
/// RequestFuture type
64-
pub type RequestFuture<'a, R, E> = Pin<Box<dyn Future<Output = Result<R, E>> + 'a>>;
64+
pub type RequestFuture<'a, R, E> = Pin<Box<dyn Future<Output = Result<R, E>> + Send + 'a>>;
6565

6666
/// Represents the origin from which the lambda was requested from.
6767
#[doc(hidden)]

lambda-http/src/response.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub trait IntoResponse {
129129

130130
impl<B> IntoResponse for Response<B>
131131
where
132-
B: ConvertBody + 'static,
132+
B: ConvertBody + Send + 'static,
133133
{
134134
fn into_response(self) -> ResponseFuture {
135135
let (parts, body) = self.into_parts();
@@ -180,15 +180,16 @@ impl IntoResponse for serde_json::Value {
180180
}
181181
}
182182

183-
pub type ResponseFuture = Pin<Box<dyn Future<Output = Response<Body>>>>;
183+
pub type ResponseFuture = Pin<Box<dyn Future<Output = Response<Body>> + Send>>;
184184

185185
pub trait ConvertBody {
186186
fn convert(self, parts: HeaderMap) -> BodyFuture;
187187
}
188188

189189
impl<B> ConvertBody for B
190190
where
191-
B: HttpBody + Unpin + 'static,
191+
B: HttpBody + Unpin + Send + 'static,
192+
B::Data: Send,
192193
B::Error: fmt::Debug,
193194
{
194195
fn convert(self, headers: HeaderMap) -> BodyFuture {
@@ -227,15 +228,17 @@ where
227228

228229
fn convert_to_binary<B>(body: B) -> BodyFuture
229230
where
230-
B: HttpBody + Unpin + 'static,
231+
B: HttpBody + Unpin + Send + 'static,
232+
B::Data: Send,
231233
B::Error: fmt::Debug,
232234
{
233235
Box::pin(async move { Body::from(to_bytes(body).await.expect("unable to read bytes from body").to_vec()) })
234236
}
235237

236238
fn convert_to_text<B>(body: B, content_type: &str) -> BodyFuture
237239
where
238-
B: HttpBody + Unpin + 'static,
240+
B: HttpBody + Unpin + Send + 'static,
241+
B::Data: Send,
239242
B::Error: fmt::Debug,
240243
{
241244
let mime_type = content_type.parse::<Mime>();
@@ -260,7 +263,7 @@ where
260263
})
261264
}
262265

263-
pub type BodyFuture = Pin<Box<dyn Future<Output = Body>>>;
266+
pub type BodyFuture = Pin<Box<dyn Future<Output = Body> + Send>>;
264267

265268
#[cfg(test)]
266269
mod tests {

0 commit comments

Comments
 (0)