lambda_http/
lib.rs

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