lambda_http/
lib.rs

1#![warn(missing_docs, rust_2018_idioms)]
2//#![deny(warnings)]
3//! Enriches the `lambda` crate with [`http`](https://github.com/hyperium/http)
4//! types targeting AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST and HTTP API lambda integrations.
5//!
6//! This crate abstracts over all of these trigger events using standard [`http`](https://github.com/hyperium/http) types minimizing the mental overhead
7//! of understanding the nuances and variation between trigger details allowing you to focus more on your application while also giving you to the maximum flexibility to
8//! transparently use whichever lambda trigger suits your application and cost optimizations best.
9//!
10//! # Examples
11//!
12//! ## Hello World
13//!
14//! The following example is how you would structure your Lambda such that you have a `main` function where you explicitly invoke
15//! `lambda_http::run` in combination with the [`service_fn`](fn.service_fn.html) function. This pattern allows you to utilize global initialization
16//! of tools such as loggers, to use on warm invokes to the same Lambda function after the first request, helping to reduce the latency of
17//! your function's execution path.
18//!
19//! ```rust,no_run
20//! use lambda_http::{service_fn, Error};
21//!
22//! #[tokio::main]
23//! async fn main() -> Result<(), Error> {
24//!     // initialize dependencies once here for the lifetime of your
25//!     // lambda task
26//!     lambda_http::run(service_fn(|request| async {
27//!         Result::<&str, std::convert::Infallible>::Ok("👋 world!")
28//!     })).await?;
29//!     Ok(())
30//! }
31//! ```
32//!
33//! ## Leveraging trigger provided data
34//!
35//! You can also access information provided directly from the underlying trigger events,
36//! like query string parameters, or Lambda function context, with the [`RequestExt`] trait.
37//!
38//! ```rust,no_run
39//! use lambda_http::{service_fn, Error, RequestExt, IntoResponse, Request};
40//!
41//! #[tokio::main]
42//! async fn main() -> Result<(), Error> {
43//!     lambda_http::run(service_fn(hello)).await?;
44//!     Ok(())
45//! }
46//!
47//! async fn hello(
48//!     request: Request
49//! ) -> Result<impl IntoResponse, std::convert::Infallible> {
50//!     let _context = request.lambda_context_ref();
51//!
52//!     Ok(format!(
53//!         "hello {}",
54//!         request
55//!             .query_string_parameters_ref()
56//!             .and_then(|params| params.first("name"))
57//!             .unwrap_or_else(|| "stranger")
58//!     ))
59//! }
60//! ```
61
62// only externed because maplit doesn't seem to play well with 2018 edition imports
63#[cfg(test)]
64#[macro_use]
65extern crate maplit;
66
67pub use http::{self, Response};
68use lambda_runtime::LambdaEvent;
69pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service};
70use request::RequestFuture;
71use response::ResponseFuture;
72
73mod deserializer;
74pub mod ext;
75pub mod request;
76mod response;
77pub use crate::{
78    ext::{RequestExt, RequestPayloadExt},
79    response::IntoResponse,
80};
81use crate::{
82    request::{LambdaRequest, RequestOrigin},
83    response::LambdaResponse,
84};
85
86// Reexported in its entirety, regardless of what feature flags are enabled
87// because working with many of these types requires other types in, or
88// reexported by, this crate.
89pub use aws_lambda_events;
90
91pub use aws_lambda_events::encodings::Body;
92use std::{
93    future::Future,
94    marker::PhantomData,
95    pin::Pin,
96    task::{Context as TaskContext, Poll},
97};
98
99mod streaming;
100pub use streaming::run_with_streaming_response;
101
102/// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type
103pub type Request = http::Request<Body>;
104
105/// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
106///
107/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
108#[doc(hidden)]
109pub enum TransformResponse<'a, R, E> {
110    Request(RequestOrigin, RequestFuture<'a, R, E>),
111    Response(RequestOrigin, ResponseFuture),
112}
113
114impl<'a, R, E> Future for TransformResponse<'a, R, E>
115where
116    R: IntoResponse,
117{
118    type Output = Result<LambdaResponse, E>;
119
120    fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
121        match *self {
122            TransformResponse::Request(ref mut origin, ref mut request) => match request.as_mut().poll(cx) {
123                Poll::Ready(Ok(resp)) => {
124                    *self = TransformResponse::Response(origin.clone(), resp.into_response());
125                    self.poll(cx)
126                }
127                Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
128                Poll::Pending => Poll::Pending,
129            },
130            TransformResponse::Response(ref mut origin, ref mut response) => match response.as_mut().poll(cx) {
131                Poll::Ready(resp) => Poll::Ready(Ok(LambdaResponse::from_response(origin, resp))),
132                Poll::Pending => Poll::Pending,
133            },
134        }
135    }
136}
137
138/// Wraps a `Service<Request>` in a `Service<LambdaEvent<Request>>`
139///
140/// This is completely internal to the `lambda_http::run` function.
141#[doc(hidden)]
142pub struct Adapter<'a, R, S> {
143    service: S,
144    _phantom_data: PhantomData<&'a R>,
145}
146
147impl<'a, R, S, E> From<S> for Adapter<'a, R, S>
148where
149    S: Service<Request, Response = R, Error = E>,
150    S::Future: Send + 'a,
151    R: IntoResponse,
152{
153    fn from(service: S) -> Self {
154        Adapter {
155            service,
156            _phantom_data: PhantomData,
157        }
158    }
159}
160
161impl<'a, R, S, E> Service<LambdaEvent<LambdaRequest>> for Adapter<'a, R, S>
162where
163    S: Service<Request, Response = R, Error = E>,
164    S::Future: Send + 'a,
165    R: IntoResponse,
166{
167    type Response = LambdaResponse;
168    type Error = E;
169    type Future = TransformResponse<'a, R, Self::Error>;
170
171    fn poll_ready(&mut self, cx: &mut core::task::Context<'_>) -> core::task::Poll<Result<(), Self::Error>> {
172        self.service.poll_ready(cx)
173    }
174
175    fn call(&mut self, req: LambdaEvent<LambdaRequest>) -> Self::Future {
176        let request_origin = req.payload.request_origin();
177        let event: Request = req.payload.into();
178        let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));
179
180        TransformResponse::Request(request_origin, fut)
181    }
182}
183
184/// Starts the Lambda Rust runtime and begins polling for events on the [Lambda
185/// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html).
186///
187/// This takes care of transforming the LambdaEvent into a [`Request`] and then
188/// converting the result into a [`LambdaResponse`].
189pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error>
190where
191    S: Service<Request, Response = R, Error = E>,
192    S::Future: Send + 'a,
193    R: IntoResponse,
194    E: std::fmt::Debug + std::fmt::Display,
195{
196    lambda_runtime::run(Adapter::from(handler)).await
197}
198
199#[cfg(test)]
200mod test_adapter {
201    use std::task::{Context, Poll};
202
203    use crate::{
204        http::{Response, StatusCode},
205        lambda_runtime::LambdaEvent,
206        request::LambdaRequest,
207        response::LambdaResponse,
208        tower::{util::BoxService, Service, ServiceBuilder, ServiceExt},
209        Adapter, Body, Request,
210    };
211
212    // A middleware that logs requests before forwarding them to another service
213    struct LogService<S> {
214        inner: S,
215    }
216
217    impl<S> Service<LambdaEvent<LambdaRequest>> for LogService<S>
218    where
219        S: Service<LambdaEvent<LambdaRequest>>,
220    {
221        type Response = S::Response;
222        type Error = S::Error;
223        type Future = S::Future;
224
225        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
226            self.inner.poll_ready(cx)
227        }
228
229        fn call(&mut self, event: LambdaEvent<LambdaRequest>) -> Self::Future {
230            // Log the request
231            println!("Lambda event: {event:#?}");
232
233            self.inner.call(event)
234        }
235    }
236
237    /// This tests that `Adapter` can be used in a `tower::Service` where the user
238    /// may require additional middleware between `lambda_runtime::run` and where
239    /// the `LambdaEvent` is converted into a `Request`.
240    #[test]
241    fn adapter_is_boxable() {
242        let _service: BoxService<LambdaEvent<LambdaRequest>, LambdaResponse, http::Error> = ServiceBuilder::new()
243            .layer_fn(|service| {
244                // This could be any middleware that logs, inspects, or manipulates
245                // the `LambdaEvent` before it's converted to a `Request` by `Adapter`.
246
247                LogService { inner: service }
248            })
249            .layer_fn(Adapter::from)
250            .service_fn(|_event: Request| async move { Response::builder().status(StatusCode::OK).body(Body::Empty) })
251            .boxed();
252    }
253}