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}