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}