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}