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")]
71#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
72pub use lambda_runtime::tracing;
73use lambda_runtime::Diagnostic;
74pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service};
75use request::RequestFuture;
76use response::ResponseFuture;
77
78mod deserializer;
79pub mod ext;
80pub mod request;
81mod response;
82pub use crate::{
83    ext::{RequestExt, RequestPayloadExt},
84    response::IntoResponse,
85};
86use crate::{
87    request::{LambdaRequest, RequestOrigin},
88    response::LambdaResponse,
89};
90
91// Reexported in its entirety, regardless of what feature flags are enabled
92// because working with many of these types requires other types in, or
93// reexported by, this crate.
94pub use aws_lambda_events;
95
96pub use aws_lambda_events::encodings::Body;
97use std::{
98    future::Future,
99    marker::PhantomData,
100    pin::Pin,
101    task::{Context as TaskContext, Poll},
102};
103
104mod streaming;
105pub use streaming::{run_with_streaming_response, StreamAdapter};
106
107/// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type
108pub type Request = http::Request<Body>;
109
110/// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
111///
112/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
113#[non_exhaustive]
114#[doc(hidden)]
115pub enum TransformResponse<'a, R, E> {
116    Request(RequestOrigin, RequestFuture<'a, R, E>),
117    Response(RequestOrigin, ResponseFuture),
118}
119
120impl<R, E> Future for TransformResponse<'_, R, E>
121where
122    R: IntoResponse,
123{
124    type Output = Result<LambdaResponse, E>;
125
126    fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
127        match *self {
128            TransformResponse::Request(ref mut origin, ref mut request) => match request.as_mut().poll(cx) {
129                Poll::Ready(Ok(resp)) => {
130                    *self = TransformResponse::Response(origin.clone(), resp.into_response());
131                    self.poll(cx)
132                }
133                Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
134                Poll::Pending => Poll::Pending,
135            },
136            TransformResponse::Response(ref mut origin, ref mut response) => match response.as_mut().poll(cx) {
137                Poll::Ready(resp) => Poll::Ready(Ok(LambdaResponse::from_response(origin, resp))),
138                Poll::Pending => Poll::Pending,
139            },
140        }
141    }
142}
143
144/// Wraps a `Service<Request>` in a `Service<LambdaEvent<Request>>`
145///
146/// This is completely internal to the `lambda_http::run` function.
147#[non_exhaustive]
148#[doc(hidden)]
149pub struct Adapter<'a, R, S> {
150    service: S,
151    _phantom_data: PhantomData<&'a R>,
152}
153
154impl<'a, R, S, E> From<S> for Adapter<'a, R, S>
155where
156    S: Service<Request, Response = R, Error = E>,
157    S::Future: Send + 'a,
158    R: IntoResponse,
159{
160    fn from(service: S) -> Self {
161        Adapter {
162            service,
163            _phantom_data: PhantomData,
164        }
165    }
166}
167
168impl<'a, R, S, E> Service<LambdaEvent<LambdaRequest>> for Adapter<'a, R, S>
169where
170    S: Service<Request, Response = R, Error = E>,
171    S::Future: Send + 'a,
172    R: IntoResponse,
173{
174    type Response = LambdaResponse;
175    type Error = E;
176    type Future = TransformResponse<'a, R, Self::Error>;
177
178    fn poll_ready(&mut self, cx: &mut core::task::Context<'_>) -> core::task::Poll<Result<(), Self::Error>> {
179        self.service.poll_ready(cx)
180    }
181
182    fn call(&mut self, req: LambdaEvent<LambdaRequest>) -> Self::Future {
183        let request_origin = req.payload.request_origin();
184        let event: Request = req.payload.into();
185        let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));
186
187        TransformResponse::Request(request_origin, fut)
188    }
189}
190
191/// Starts the Lambda Rust runtime and begins polling for events on the [Lambda
192/// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html).
193///
194/// This takes care of transforming the LambdaEvent into a [`Request`] and then
195/// converting the result into a `LambdaResponse`.
196pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error>
197where
198    S: Service<Request, Response = R, Error = E>,
199    S::Future: Send + 'a,
200    R: IntoResponse,
201    E: std::fmt::Debug + Into<Diagnostic>,
202{
203    lambda_runtime::run(Adapter::from(handler)).await
204}
205
206#[cfg(test)]
207mod test_adapter {
208    use std::task::{Context, Poll};
209
210    use crate::{
211        http::{Response, StatusCode},
212        lambda_runtime::LambdaEvent,
213        request::LambdaRequest,
214        response::LambdaResponse,
215        tower::{util::BoxService, Service, ServiceBuilder, ServiceExt},
216        Adapter, Body, Request,
217    };
218
219    // A middleware that logs requests before forwarding them to another service
220    struct LogService<S> {
221        inner: S,
222    }
223
224    impl<S> Service<LambdaEvent<LambdaRequest>> for LogService<S>
225    where
226        S: Service<LambdaEvent<LambdaRequest>>,
227    {
228        type Response = S::Response;
229        type Error = S::Error;
230        type Future = S::Future;
231
232        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
233            self.inner.poll_ready(cx)
234        }
235
236        fn call(&mut self, event: LambdaEvent<LambdaRequest>) -> Self::Future {
237            // Log the request
238            println!("Lambda event: {event:#?}");
239
240            self.inner.call(event)
241        }
242    }
243
244    /// This tests that `Adapter` can be used in a `tower::Service` where the user
245    /// may require additional middleware between `lambda_runtime::run` and where
246    /// the `LambdaEvent` is converted into a `Request`.
247    #[test]
248    fn adapter_is_boxable() {
249        let _service: BoxService<LambdaEvent<LambdaRequest>, LambdaResponse, http::Error> = ServiceBuilder::new()
250            .layer_fn(|service| {
251                // This could be any middleware that logs, inspects, or manipulates
252                // the `LambdaEvent` before it's converted to a `Request` by `Adapter`.
253
254                LogService { inner: service }
255            })
256            .layer_fn(Adapter::from)
257            .service_fn(|_event: Request| async move { Response::builder().status(StatusCode::OK).body(Body::Empty) })
258            .boxed();
259    }
260}