#![warn(missing_docs, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(test)]
#[macro_use]
extern crate maplit;
pub use http::{self, Response};
#[cfg(feature = "tracing")]
#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
pub use lambda_runtime::tracing;
use lambda_runtime::Diagnostic;
pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service};
use request::RequestFuture;
use response::ResponseFuture;
mod deserializer;
pub mod ext;
pub mod request;
mod response;
pub use crate::{
ext::{RequestExt, RequestPayloadExt},
response::IntoResponse,
};
use crate::{
request::{LambdaRequest, RequestOrigin},
response::LambdaResponse,
};
pub use aws_lambda_events;
pub use aws_lambda_events::encodings::Body;
use std::{
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context as TaskContext, Poll},
};
mod streaming;
pub use streaming::{run_with_streaming_response, StreamAdapter};
pub type Request = http::Request<Body>;
#[non_exhaustive]
#[doc(hidden)]
pub enum TransformResponse<'a, R, E> {
Request(RequestOrigin, RequestFuture<'a, R, E>),
Response(RequestOrigin, ResponseFuture),
}
impl<R, E> Future for TransformResponse<'_, R, E>
where
R: IntoResponse,
{
type Output = Result<LambdaResponse, E>;
fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
match *self {
TransformResponse::Request(ref mut origin, ref mut request) => match request.as_mut().poll(cx) {
Poll::Ready(Ok(resp)) => {
*self = TransformResponse::Response(origin.clone(), resp.into_response());
self.poll(cx)
}
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => Poll::Pending,
},
TransformResponse::Response(ref mut origin, ref mut response) => match response.as_mut().poll(cx) {
Poll::Ready(resp) => Poll::Ready(Ok(LambdaResponse::from_response(origin, resp))),
Poll::Pending => Poll::Pending,
},
}
}
}
#[non_exhaustive]
#[doc(hidden)]
pub struct Adapter<'a, R, S> {
service: S,
_phantom_data: PhantomData<&'a R>,
}
impl<'a, R, S, E> From<S> for Adapter<'a, R, S>
where
S: Service<Request, Response = R, Error = E>,
S::Future: Send + 'a,
R: IntoResponse,
{
fn from(service: S) -> Self {
Adapter {
service,
_phantom_data: PhantomData,
}
}
}
impl<'a, R, S, E> Service<LambdaEvent<LambdaRequest>> for Adapter<'a, R, S>
where
S: Service<Request, Response = R, Error = E>,
S::Future: Send + 'a,
R: IntoResponse,
{
type Response = LambdaResponse;
type Error = E;
type Future = TransformResponse<'a, R, Self::Error>;
fn poll_ready(&mut self, cx: &mut core::task::Context<'_>) -> core::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: LambdaEvent<LambdaRequest>) -> Self::Future {
let request_origin = req.payload.request_origin();
let event: Request = req.payload.into();
let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));
TransformResponse::Request(request_origin, fut)
}
}
pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error>
where
S: Service<Request, Response = R, Error = E>,
S::Future: Send + 'a,
R: IntoResponse,
E: std::fmt::Debug + Into<Diagnostic>,
{
lambda_runtime::run(Adapter::from(handler)).await
}
#[cfg(test)]
mod test_adapter {
use std::task::{Context, Poll};
use crate::{
http::{Response, StatusCode},
lambda_runtime::LambdaEvent,
request::LambdaRequest,
response::LambdaResponse,
tower::{util::BoxService, Service, ServiceBuilder, ServiceExt},
Adapter, Body, Request,
};
struct LogService<S> {
inner: S,
}
impl<S> Service<LambdaEvent<LambdaRequest>> for LogService<S>
where
S: Service<LambdaEvent<LambdaRequest>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, event: LambdaEvent<LambdaRequest>) -> Self::Future {
println!("Lambda event: {event:#?}");
self.inner.call(event)
}
}
#[test]
fn adapter_is_boxable() {
let _service: BoxService<LambdaEvent<LambdaRequest>, LambdaResponse, http::Error> = ServiceBuilder::new()
.layer_fn(|service| {
LogService { inner: service }
})
.layer_fn(Adapter::from)
.service_fn(|_event: Request| async move { Response::builder().status(StatusCode::OK).body(Body::Empty) })
.boxed();
}
}