From 5c49bccf732673349f9fe9497697a39ca17ed0e0 Mon Sep 17 00:00:00 2001 From: Marc Bowes Date: Fri, 15 May 2020 14:34:04 -0700 Subject: [PATCH 001/394] Fix Receiver typo (#224) --- lambda/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda/src/client.rs b/lambda/src/client.rs index 8d0ddb58..a320df35 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -232,7 +232,7 @@ mod endpoint_tests { assert_eq!(rsp.status(), StatusCode::ACCEPTED); // shutdown server - tx.send(()).expect("Reciever has been dropped"); + tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), From 91d2a60d4a0d040332521576f9ac349add3db128 Mon Sep 17 00:00:00 2001 From: Marc Bowes Date: Fri, 15 May 2020 14:46:12 -0700 Subject: [PATCH 002/394] Fix Reciever typo (#225) --- lambda/src/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda/src/client.rs b/lambda/src/client.rs index a320df35..7bc833a2 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -200,7 +200,7 @@ mod endpoint_tests { assert_eq!(rsp.headers()[header], &HeaderValue::try_from("1542409706888")?); // shutdown server... - tx.send(()).expect("Reciever has been dropped"); + tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), @@ -266,7 +266,7 @@ mod endpoint_tests { assert_eq!(rsp.status(), StatusCode::ACCEPTED); // shutdown server - tx.send(()).expect("Reciever has been dropped"); + tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), @@ -317,7 +317,7 @@ mod endpoint_tests { // assert_eq!(rsp.status(), http::StatusCode::OK); // // shutdown server - // tx.send(()).expect("Reciever has been dropped"); + // tx.send(()).expect("Receiver has been dropped"); // match server.await { // Ok(_) => Ok(()), // Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), From ed3fd167528125b73ce47abfadc38cd274bf59bc Mon Sep 17 00:00:00 2001 From: Doug Tangren Date: Thu, 28 May 2020 10:27:02 -0400 Subject: [PATCH 003/394] replace anyhow with std error and an type alias (#231) Resolves #229. --- Cargo.lock | 7 ------- .../{basic.rs => hello-http-without-macros.rs} | 0 lambda-http/examples/{hello.rs => hello-http.rs} | 0 lambda-http/src/lib.rs | 15 ++++++++------- lambda/Cargo.toml | 1 - lambda/src/client.rs | 14 ++++++++------ lambda/src/lib.rs | 10 ++++++---- lambda/src/requests.rs | 3 +-- lambda/src/types.rs | 3 +-- 9 files changed, 24 insertions(+), 29 deletions(-) rename lambda-http/examples/{basic.rs => hello-http-without-macros.rs} (100%) rename lambda-http/examples/{hello.rs => hello-http.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 6e8f9d85..49c98444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "anyhow" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "arc-swap" version = "0.4.4" @@ -286,7 +281,6 @@ dependencies = [ name = "lambda" version = "0.1.0" dependencies = [ - "anyhow 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "genawaiter 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -851,7 +845,6 @@ dependencies = [ ] [metadata] -"checksum anyhow 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff" "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" diff --git a/lambda-http/examples/basic.rs b/lambda-http/examples/hello-http-without-macros.rs similarity index 100% rename from lambda-http/examples/basic.rs rename to lambda-http/examples/hello-http-without-macros.rs diff --git a/lambda-http/examples/hello.rs b/lambda-http/examples/hello-http.rs similarity index 100% rename from lambda-http/examples/hello.rs rename to lambda-http/examples/hello-http.rs diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index bca6287a..077356bf 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -23,6 +23,7 @@ //! //! ```rust,no_run //! use lambda_http::{lambda, Request, IntoResponse}; +//! //! type Error = Box; //! //! #[lambda(http)] @@ -40,6 +41,7 @@ //! //! ```rust,no_run //! use lambda_http::{handler, lambda}; +//! //! type Error = Box; //! //! #[tokio::main] @@ -59,6 +61,7 @@ //! //! ```rust,no_run //! use lambda_http::{handler, lambda, IntoResponse, Request, RequestExt}; +//! //! type Error = Box; //! //! #[tokio::main] @@ -89,7 +92,7 @@ pub use http::{self, Response}; use lambda::Handler as LambdaHandler; pub use lambda::{self}; pub use lambda_attributes::lambda; -//pub use lambda_http_attributes::lambda_http; + mod body; pub mod ext; pub mod request; @@ -98,14 +101,13 @@ mod strmap; pub use crate::{body::Body, ext::RequestExt, response::IntoResponse, strmap::StrMap}; use crate::{request::LambdaRequest, response::LambdaResponse}; use std::{ - error::Error, - fmt, future::Future, pin::Pin, task::{Context, Poll}, }; -type Err = Box; +/// Error type that lambdas may result in +pub(crate) type Error = Box; /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; @@ -134,11 +136,10 @@ impl Handler for F where F: FnMut(Request) -> Fut, R: IntoResponse, - Fut: Future> + Send + 'static, - Err: Into> + fmt::Debug, + Fut: Future> + Send + 'static, { type Response = R; - type Error = Err; + type Error = Error; type Fut = Fut; fn call(&mut self, event: Request) -> Self::Fut { (*self)(event) diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index b3b82041..7acf8cbd 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -25,4 +25,3 @@ futures = "0.3" tracing = "0.1.13" tracing-futures = "0.2.3" tracing-error = "0.1.2" -anyhow = "1.0.27" diff --git a/lambda/src/client.rs b/lambda/src/client.rs index 7bc833a2..64313ebf 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -1,5 +1,7 @@ -use crate::requests::{IntoResponse, NextEventResponse}; -use anyhow::Error; +use crate::{ + requests::{IntoResponse, NextEventResponse}, + Error, +}; use http::{ uri::{PathAndQuery, Scheme}, HeaderValue, Method, Request, Response, StatusCode, Uri, @@ -172,8 +174,8 @@ mod endpoint_tests { requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, simulated::SimulatedConnector, types::Diagnostic, + Error, }; - use anyhow::Error; use http::{HeaderValue, StatusCode, Uri}; use std::convert::TryFrom; use tokio::sync; @@ -203,7 +205,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), + Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } @@ -235,7 +237,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), + Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } @@ -269,7 +271,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), + Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 0c76f78f..17067d83 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -25,7 +25,7 @@ //! of [`lambda::LambdaCtx`]. //! //! ```no_run -//! use lambda::lambda; +//! use lambda::{lambda}; //! use serde_json::Value; //! //! type Error = Box; @@ -37,7 +37,6 @@ //! } //! ``` pub use crate::types::LambdaCtx; -use anyhow::Error; use client::Client; use futures::stream::{Stream, StreamExt}; use genawaiter::{sync::gen, yield_}; @@ -58,6 +57,9 @@ mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; use types::Diagnostic; +/// Error type that lambdas may result in +pub(crate) type Error = Box; + /// Configuration derived from environment variables. #[derive(Debug, Default, Clone, PartialEq)] pub struct Config { @@ -119,7 +121,7 @@ impl Handler for HandlerFn where F: Fn(A) -> Fut, Fut: Future> + Send, - Error: Into> + fmt::Debug, + Error: Into + fmt::Debug, { type Error = Error; type Fut = Fut; @@ -134,7 +136,7 @@ where /// /// # Example /// ```no_run -/// use lambda::handler_fn; +/// use lambda::{handler_fn}; /// use serde_json::Value; /// /// type Error = Box; diff --git a/lambda/src/requests.rs b/lambda/src/requests.rs index 4eabc3c1..2d691b94 100644 --- a/lambda/src/requests.rs +++ b/lambda/src/requests.rs @@ -1,5 +1,4 @@ -use crate::types::Diagnostic; -use anyhow::Error; +use crate::{types::Diagnostic, Error}; use http::{Method, Request, Response, Uri}; use hyper::Body; use serde::Serialize; diff --git a/lambda/src/types.rs b/lambda/src/types.rs index b744cd46..2efcb3d1 100644 --- a/lambda/src/types.rs +++ b/lambda/src/types.rs @@ -1,5 +1,4 @@ -use crate::Config; -use anyhow::Error; +use crate::{Config, Error}; use http::HeaderMap; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, convert::TryFrom}; From ae3ce1d4aee593b9425a045cd19d2b967d5cf367 Mon Sep 17 00:00:00 2001 From: Doug Tangren Date: Tue, 30 Jun 2020 13:06:36 -0400 Subject: [PATCH 004/394] Make lambda::Context a first-class part of the Handler API (#233) --- lambda-attributes/src/lib.rs | 36 +++++++++++-------- .../examples/hello-http-without-macros.rs | 8 +++-- lambda-http/examples/hello-http.rs | 7 ++-- lambda-http/src/ext.rs | 5 +-- lambda-http/src/lib.rs | 31 ++++++++-------- lambda/examples/hello-with-ctx.rs | 10 ------ lambda/examples/hello-without-macro.rs | 4 +-- lambda/examples/hello.rs | 4 +-- lambda/src/lib.rs | 32 ++++++++--------- lambda/src/types.rs | 6 ++-- 10 files changed, 72 insertions(+), 71 deletions(-) delete mode 100644 lambda/examples/hello-with-ctx.rs diff --git a/lambda-attributes/src/lib.rs b/lambda-attributes/src/lib.rs index 6f477495..a17fdfad 100644 --- a/lambda-attributes/src/lib.rs +++ b/lambda-attributes/src/lib.rs @@ -38,28 +38,37 @@ pub fn lambda(attr: TokenStream, item: TokenStream) -> TokenStream { } let result = match inputs.len() { - 1 => { - let event = match inputs.first().unwrap() { + 2 => { + let event = match inputs.first().expect("expected event argument") { FnArg::Typed(arg) => arg, _ => { let tokens = quote_spanned! { inputs.span() => - compile_error!("fn main must take a fully formed argument"); + compile_error!("fn main's first argument must be fully formed"); }; return TokenStream::from(tokens); } }; - let arg_name = &event.pat; - let arg_type = &event.ty; + let event_name = &event.pat; + let event_type = &event.ty; + let context = match inputs.iter().nth(1).expect("expected context argument") { + FnArg::Typed(arg) => arg, + _ => { + let tokens = quote_spanned! { inputs.span() => + compile_error!("fn main's second argument must be fully formed"); + }; + return TokenStream::from(tokens); + } + }; + let context_name = &context.pat; + let context_type = &context.ty; if is_http(&args) { quote_spanned! { input.span() => - use lambda_http::lambda::LambdaCtx; #(#attrs)* #asyncness fn main() { - async fn actual(#arg_name: #arg_type) #ret { - #body - } + async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body + let f = lambda_http::handler(actual); lambda_http::lambda::run(f).await.unwrap(); } @@ -67,13 +76,10 @@ pub fn lambda(attr: TokenStream, item: TokenStream) -> TokenStream { } else { quote_spanned! { input.span() => - use lambda::LambdaCtx; - #(#attrs)* #asyncness fn main() { - async fn actual(#arg_name: #arg_type) #ret { - #body - } + async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body + let f = lambda::handler_fn(actual); lambda::run(f).await.unwrap(); } @@ -82,7 +88,7 @@ pub fn lambda(attr: TokenStream, item: TokenStream) -> TokenStream { } _ => { let tokens = quote_spanned! { inputs.span() => - compile_error!("The #[lambda] macro can accept only a single argument."); + compile_error!("The #[lambda] macro can expects two arguments: a triggered event and lambda context."); }; return TokenStream::from(tokens); } diff --git a/lambda-http/examples/hello-http-without-macros.rs b/lambda-http/examples/hello-http-without-macros.rs index db740c8e..0a609651 100644 --- a/lambda-http/examples/hello-http-without-macros.rs +++ b/lambda-http/examples/hello-http-without-macros.rs @@ -1,4 +1,8 @@ -use lambda_http::{handler, lambda, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{ + handler, + lambda::{self, Context}, + IntoResponse, Request, RequestExt, Response, +}; type Error = Box; @@ -8,7 +12,7 @@ async fn main() -> Result<(), Error> { Ok(()) } -async fn func(event: Request) -> Result { +async fn func(event: Request, _: Context) -> Result { Ok(match event.query_string_parameters().get("first_name") { Some(first_name) => format!("Hello, {}!", first_name).into_response(), _ => Response::builder() diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index 978a50e3..f8fa1f16 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -1,9 +1,12 @@ -use lambda_http::{lambda, IntoResponse, Request}; +use lambda_http::{ + lambda::{lambda, Context}, + IntoResponse, Request, +}; type Error = Box; #[lambda(http)] #[tokio::main] -async fn main(_: Request) -> Result { +async fn main(_: Request, _: Context) -> Result { Ok("👋 world") } diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 761a6417..dd08ae73 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -67,7 +67,7 @@ impl Error for PayloadError { /// as well as `{"x":1, "y":2}` respectively. /// /// ```rust,no_run -/// use lambda_http::{handler, lambda, Body, IntoResponse, Request, Response, RequestExt}; +/// use lambda_http::{handler, lambda::{self, Context}, Body, IntoResponse, Request, Response, RequestExt}; /// use serde_derive::Deserialize; /// /// type Error = Box; @@ -87,7 +87,8 @@ impl Error for PayloadError { /// } /// /// async fn add( -/// request: Request +/// request: Request, +/// _: Context /// ) -> Result, Error> { /// let args: Args = request.payload() /// .unwrap_or_else(|_parse_err| None) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 077356bf..f26fa351 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -22,13 +22,13 @@ //! The full body of your `main` function will be executed on **every** invocation of your lambda task. //! //! ```rust,no_run -//! use lambda_http::{lambda, Request, IntoResponse}; +//! use lambda_http::{lambda::{lambda, Context}, Request, IntoResponse}; //! //! type Error = Box; //! //! #[lambda(http)] //! #[tokio::main] -//! async fn main(_: Request) -> Result { +//! async fn main(_: Request, _: Context) -> Result { //! Ok("👋 world!") //! } //! ``` @@ -48,7 +48,7 @@ //! async fn main() -> Result<(), Error> { //! // initialize dependencies once here for the lifetime of your //! // lambda task -//! lambda::run(handler(|request| async { Ok("👋 world!") })).await?; +//! lambda::run(handler(|request, context| async { Ok("👋 world!") })).await?; //! Ok(()) //! } //! @@ -60,7 +60,7 @@ //! with the [`RequestExt`](trait.RequestExt.html) trait. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda, IntoResponse, Request, RequestExt}; +//! use lambda_http::{handler, lambda::{self, Context}, IntoResponse, Request, RequestExt}; //! //! type Error = Box; //! @@ -72,6 +72,7 @@ //! //! async fn hello( //! request: Request, +//! _: Context //! ) -> Result { //! Ok(format!( //! "hello {}", @@ -90,7 +91,7 @@ extern crate maplit; pub use http::{self, Response}; use lambda::Handler as LambdaHandler; -pub use lambda::{self}; +pub use lambda::{self, Context}; pub use lambda_attributes::lambda; mod body; @@ -103,7 +104,7 @@ use crate::{request::LambdaRequest, response::LambdaResponse}; use std::{ future::Future, pin::Pin, - task::{Context, Poll}, + task::{Context as TaskContext, Poll}, }; /// Error type that lambdas may result in @@ -123,7 +124,7 @@ pub trait Handler: Sized { /// The type of Future this Handler will return type Fut: Future> + 'static; /// Function used to execute handler behavior - fn call(&mut self, event: Request) -> Self::Fut; + fn call(&mut self, event: Request, context: Context) -> Self::Fut; } /// Adapts a [`Handler`](trait.Handler.html) to the `lambda::run` interface @@ -134,15 +135,15 @@ pub fn handler(handler: H) -> Adapter { /// An implementation of `Handler` for a given closure return a `Future` representing the computed response impl Handler for F where - F: FnMut(Request) -> Fut, + F: FnMut(Request, Context) -> Fut, R: IntoResponse, Fut: Future> + Send + 'static, { type Response = R; type Error = Error; type Fut = Fut; - fn call(&mut self, event: Request) -> Self::Fut { - (*self)(event) + fn call(&mut self, event: Request, context: Context) -> Self::Fut { + (*self)(event, context) } } @@ -157,7 +158,7 @@ where R: IntoResponse, { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll { match self.fut.as_mut().poll(cx) { Poll::Ready(result) => { Poll::Ready(result.map(|resp| LambdaResponse::from_response(self.is_alb, resp.into_response()))) @@ -182,17 +183,17 @@ impl Handler for Adapter { type Response = H::Response; type Error = H::Error; type Fut = H::Fut; - fn call(&mut self, event: Request) -> Self::Fut { - self.handler.call(event) + fn call(&mut self, event: Request, context: Context) -> Self::Fut { + self.handler.call(event, context) } } impl LambdaHandler, LambdaResponse> for Adapter { type Error = H::Error; type Fut = TransformResponse; - fn call(&mut self, event: LambdaRequest<'_>) -> Self::Fut { + fn call(&mut self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { let is_alb = event.is_alb(); - let fut = Box::pin(self.handler.call(event.into())); + let fut = Box::pin(self.handler.call(event.into(), context)); TransformResponse { is_alb, fut } } } diff --git a/lambda/examples/hello-with-ctx.rs b/lambda/examples/hello-with-ctx.rs deleted file mode 100644 index 957c2a58..00000000 --- a/lambda/examples/hello-with-ctx.rs +++ /dev/null @@ -1,10 +0,0 @@ -use lambda::lambda; -use serde_json::Value; - -type Error = Box; - -#[lambda] -#[tokio::main] -async fn main(event: Value) -> Result { - Ok(event) -} diff --git a/lambda/examples/hello-without-macro.rs b/lambda/examples/hello-without-macro.rs index dd20bd10..2aec334f 100644 --- a/lambda/examples/hello-without-macro.rs +++ b/lambda/examples/hello-without-macro.rs @@ -1,4 +1,4 @@ -use lambda::handler_fn; +use lambda::{handler_fn, Context}; use serde_json::Value; type Error = Box; @@ -10,6 +10,6 @@ async fn main() -> Result<(), Error> { Ok(()) } -async fn func(event: Value) -> Result { +async fn func(event: Value, _: Context) -> Result { Ok(event) } diff --git a/lambda/examples/hello.rs b/lambda/examples/hello.rs index 957c2a58..99472874 100644 --- a/lambda/examples/hello.rs +++ b/lambda/examples/hello.rs @@ -1,10 +1,10 @@ -use lambda::lambda; +use lambda::{lambda, Context}; use serde_json::Value; type Error = Box; #[lambda] #[tokio::main] -async fn main(event: Value) -> Result { +async fn main(event: Value, _: Context) -> Result { Ok(event) } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 17067d83..adf50f83 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -17,26 +17,23 @@ //! to the the `lambda::run` function, which launches and runs the Lambda runtime. //! //! An asynchronous function annotated with the `#[lambda]` attribute must -//! accept an argument of type `A` which implements [`serde::Deserialize`] and +//! accept an argument of type `A` which implements [`serde::Deserialize`], a [`lambda::Context`] and //! return a `Result`, where `B` implements [`serde::Serializable`]. `E` is //! any type that implements `Into>`. //! -//! Optionally, the `#[lambda]` annotated function can accept an argument -//! of [`lambda::LambdaCtx`]. -//! //! ```no_run -//! use lambda::{lambda}; +//! use lambda::{lambda, Context}; //! use serde_json::Value; //! //! type Error = Box; //! //! #[lambda] //! #[tokio::main] -//! async fn main(event: Value) -> Result { +//! async fn main(event: Value, _: Context) -> Result { //! Ok(event) //! } //! ``` -pub use crate::types::LambdaCtx; +pub use crate::types::Context; use client::Client; use futures::stream::{Stream, StreamExt}; use genawaiter::{sync::gen, yield_}; @@ -93,7 +90,7 @@ impl Config { } tokio::task_local! { - pub static INVOCATION_CTX: types::LambdaCtx; + pub static INVOCATION_CTX: types::Context; } /// A trait describing an asynchronous function `A` to `B. @@ -102,8 +99,8 @@ pub trait Handler { type Error; /// The future response value of this handler. type Fut: Future>; - /// Process the incoming event and return the response asynchronously. - fn call(&mut self, event: A) -> Self::Fut; + /// Process the incoming event and `Context` then return the response asynchronously. + fn call(&mut self, event: A, context: Context) -> Self::Fut; } /// Returns a new `HandlerFn` with the given closure. @@ -119,15 +116,14 @@ pub struct HandlerFn { impl Handler for HandlerFn where - F: Fn(A) -> Fut, + F: Fn(A, Context) -> Fut, Fut: Future> + Send, Error: Into + fmt::Debug, { type Error = Error; type Fut = Fut; - fn call(&mut self, req: A) -> Self::Fut { - // we pass along the context here - (self.f)(req) + fn call(&mut self, req: A, ctx: Context) -> Self::Fut { + (self.f)(req, ctx) } } @@ -136,7 +132,7 @@ where /// /// # Example /// ```no_run -/// use lambda::{handler_fn}; +/// use lambda::{handler_fn, Context}; /// use serde_json::Value; /// /// type Error = Box; @@ -148,7 +144,7 @@ where /// Ok(()) /// } /// -/// async fn func(event: Value) -> Result { +/// async fn func(event: Value, _: Context) -> Result { /// Ok(event) /// } /// ``` @@ -211,12 +207,12 @@ where let event = event?; let (parts, body) = event.into_parts(); - let ctx: LambdaCtx = LambdaCtx::try_from(parts.headers)?; + let ctx: Context = Context::try_from(parts.headers)?; let body = hyper::body::to_bytes(body).await?; let body = serde_json::from_slice(&body)?; let request_id = &ctx.request_id.clone(); - let f = INVOCATION_CTX.scope(ctx, { handler.call(body) }); + let f = INVOCATION_CTX.scope(ctx.clone(), handler.call(body, ctx)); let req = match f.await { Ok(res) => EventCompletionRequest { request_id, body: res }.into_req()?, diff --git a/lambda/src/types.rs b/lambda/src/types.rs index 2efcb3d1..c3e11498 100644 --- a/lambda/src/types.rs +++ b/lambda/src/types.rs @@ -94,7 +94,7 @@ pub struct CognitoIdentity { /// and the headers returned by the poll request to the Runtime APIs. #[non_exhaustive] #[derive(Clone, Debug, PartialEq, Default)] -pub struct LambdaCtx { +pub struct Context { /// The AWS request ID generated by the Lambda service. pub request_id: String, /// The execution deadline for the current invocation in milliseconds. @@ -116,10 +116,10 @@ pub struct LambdaCtx { pub env_config: Config, } -impl TryFrom for LambdaCtx { +impl TryFrom for Context { type Error = Error; fn try_from(headers: HeaderMap) -> Result { - let ctx = LambdaCtx { + let ctx = Context { request_id: headers["lambda-runtime-aws-request-id"] .to_str() .expect("Missing Request ID") From e193e700cf158e357b6ab31eaec0e9990102e601 Mon Sep 17 00:00:00 2001 From: Doug Tangren Date: Wed, 1 Jul 2020 09:26:07 -0400 Subject: [PATCH 005/394] Retire INVOCATION_CTX task local; lambda::Context is now a normal argument (#237) --- lambda/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index adf50f83..4d6b3839 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -89,10 +89,6 @@ impl Config { } } -tokio::task_local! { - pub static INVOCATION_CTX: types::Context; -} - /// A trait describing an asynchronous function `A` to `B. pub trait Handler { /// Errors returned by this handler. @@ -212,7 +208,7 @@ where let body = serde_json::from_slice(&body)?; let request_id = &ctx.request_id.clone(); - let f = INVOCATION_CTX.scope(ctx.clone(), handler.call(body, ctx)); + let f = handler.call(body, ctx); let req = match f.await { Ok(res) => EventCompletionRequest { request_id, body: res }.into_req()?, From c8dbcd39e0b1cf9ecf395e2b2f9df6c6c0d97780 Mon Sep 17 00:00:00 2001 From: Doug Tangren Date: Thu, 2 Jul 2020 11:15:29 -0400 Subject: [PATCH 006/394] Publish latest rustdocs to gh-pages (#234) --- .github/workflows/build.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5fec94a..1387be08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,4 +67,24 @@ jobs: components: rustfmt override: true - name: Run fmt check - run: cargo fmt --all -- --check \ No newline at end of file + run: cargo fmt --all -- --check + + # publish rustdoc to a gh-pages branch on pushes to master + # this can be helpful to those depending on the mainline branch + publish-docs: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Set up Rust + uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@v2 + - name: Generate Docs + run: | + cargo doc --no-deps + echo "" > target/doc/index.html + - name: Publish + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc \ No newline at end of file From 834be267b83542d7987bad0f42828d230f808428 Mon Sep 17 00:00:00 2001 From: mx Date: Wed, 15 Jul 2020 03:26:32 +1200 Subject: [PATCH 007/394] Corrected --target name in README, added links. (#238) --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 12882634..ae54457d 100644 --- a/README.md +++ b/README.md @@ -53,17 +53,23 @@ The code above is the same as the [basic example](https://github.com/awslabs/aws ### Deployment -There are currently multiple ways of building this package: manually, and the [Serverless framework](https://serverless.com/framework/). +There are currently multiple ways of building this package: manually, and with the [Serverless framework](https://serverless.com/framework/). #### AWS CLI -To deploy the basic sample as a Lambda function using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html), we first need to manually build it with [`cargo`](https://doc.rust-lang.org/cargo/). Since Lambda uses Amazon Linux, you'll need to target your executable for an `x86_64-linux` platform. +To deploy the basic sample as a Lambda function using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html), we first need to manually build it with [`cargo`](https://doc.rust-lang.org/cargo/). Since Lambda uses Amazon Linux, you'll need to target your executable for an `x86_64-unknown-linux-musl` platform. +Run this script once to add the new target: ```bash -$ cargo build -p lambda --example hello --release +$ rustup target add x86_64-unknown-linux-musl ``` -For a custom runtime, AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. +Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: +```bash +$ cargo build -p lambda --example hello --release --target x86_64-unknown-linux-musl +``` + +For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. ```bash $ cp ./target/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap From e528ef6650b16e6c7e50ba22048f6097c3d0e595 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 14 Jul 2020 20:55:13 +0300 Subject: [PATCH 008/394] Fix docs generation (#239) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1387be08..f23ef207 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,9 +82,9 @@ jobs: - name: Generate Docs run: | cargo doc --no-deps - echo "" > target/doc/index.html + echo "" > target/doc/index.html - name: Publish uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc \ No newline at end of file + publish_dir: ./target/doc From f40461785625d8ac23e7c248367b1d724ef2b5ba Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Fri, 17 Jul 2020 17:37:23 -0400 Subject: [PATCH 009/394] Replace `genawaiter` with `async-stream` (#240) --- Cargo.lock | 97 +++++++++++------------------------------------ lambda/Cargo.toml | 2 +- lambda/src/lib.rs | 13 ++++--- 3 files changed, 30 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49c98444..5cd113cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,25 @@ name = "arc-swap" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-stream-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "autocfg" version = "1.0.0" @@ -136,34 +155,6 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "genawaiter" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "genawaiter-macro 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)", - "genawaiter-proc-macro 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "genawaiter-macro" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "genawaiter-proc-macro" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "h2" version = "0.2.1" @@ -281,9 +272,9 @@ dependencies = [ name = "lambda" version = "0.1.0" dependencies = [ + "async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "genawaiter 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "lambda-attributes 0.1.0", @@ -466,30 +457,6 @@ name = "pin-utils" version = "0.1.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "proc-macro-error" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro-error-attr" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "proc-macro-hack" version = "0.5.11" @@ -626,16 +593,6 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "time" version = "0.1.42" @@ -792,11 +749,6 @@ dependencies = [ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "version_check" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "want" version = "0.3.0" @@ -846,6 +798,8 @@ dependencies = [ [metadata] "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" +"checksum async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +"checksum async-stream-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" @@ -864,9 +818,6 @@ dependencies = [ "checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" "checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" "checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" -"checksum genawaiter 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" -"checksum genawaiter-macro 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" -"checksum genawaiter-proc-macro 0.99.1 (registry+https://github.com/rust-lang/crates.io-index)" = "784f84eebc366e15251c4a8c3acee82a6a6f427949776ecb88377362a9621738" "checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" "checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" @@ -896,8 +847,6 @@ dependencies = [ "checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" "checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" -"checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" -"checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" @@ -915,7 +864,6 @@ dependencies = [ "checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" -"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b" "checksum tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c" @@ -933,7 +881,6 @@ dependencies = [ "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index 7acf8cbd..eb718e80 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -20,7 +20,7 @@ tower-service = "0.3" bytes = "0.5" http = "0.2" lambda-attributes = { path = "../lambda-attributes", version = "0.1.0", optional = true} -genawaiter = { version = "0.99", features = ["futures03"] } +async-stream = "0.2" futures = "0.3" tracing = "0.1.13" tracing-futures = "0.2.3" diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 4d6b3839..24ef4929 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -36,7 +36,6 @@ pub use crate::types::Context; use client::Client; use futures::stream::{Stream, StreamExt}; -use genawaiter::{sync::gen, yield_}; pub use lambda_attributes::lambda; use serde::{Deserialize, Serialize}; use std::{ @@ -179,17 +178,18 @@ where } fn incoming(client: &Client) -> impl Stream, Error>> + '_ { - gen!({ + async_stream::stream! { loop { let req = NextEventRequest.into_req().expect("Unable to construct request"); - yield_!(client.call(req).await) + let res = client.call(req).await; + yield res; } - }) + } } async fn run_inner( client: &Client, - incoming: impl Stream, Error>> + Unpin, + incoming: impl Stream, Error>>, handler: &mut F, ) -> Result<(), Error> where @@ -198,7 +198,8 @@ where A: for<'de> Deserialize<'de>, B: Serialize, { - let mut incoming = incoming; + tokio::pin!(incoming); + while let Some(event) = incoming.next().await { let event = event?; let (parts, body) = event.into_parts(); From a9de2fcb24030a00e402348aba3c368b717feb6d Mon Sep 17 00:00:00 2001 From: Yogendra Sharma Date: Fri, 24 Jul 2020 21:59:13 +0530 Subject: [PATCH 010/394] #245 Fix for Invalid URL in README (#246) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae54457d..bd8a6d4a 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,11 @@ pub trait Handler { `Handler` provides a default implementation that enables you to provide a Rust closure or function pointer to the `lambda!()` macro. -Optionally, you can pass your own instance of Tokio runtime to the `lambda!()` macro. See our [`with_custom_runtime.rs` example](https://github.com/awslabs/aws-lambda-rust-runtime/tree/master/lambda-runtime/examples/with_custom_runtime.rs) +Optionally, you can pass your own instance of Tokio runtime to the `lambda!()` macro: +``` +let rt = tokio::runtime::Runtime::new()?; +lambda!(my_handler, rt); +``` ## AWS event objects From ed40f5a7b7e8f09a679f7b6b6334e5e3ed86b1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20Pr=C3=B6=C3=9Fdorf?= Date: Thu, 17 Sep 2020 01:55:56 +0300 Subject: [PATCH 011/394] Pass cookies from LambdaRequest::ApiGatewayV2 to Request (#258) --- lambda-http/src/request.rs | 16 +++++++++++++++- .../tests/data/apigw_v2_proxy_request.json | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 852e1e04..347cb29e 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -311,15 +311,22 @@ impl<'a> From> for http::Request { LambdaRequest::ApiGatewayV2 { raw_path, raw_query_string, - headers, + mut headers, query_string_parameters, path_parameters, stage_variables, body, is_base64_encoded, request_context, + cookies, .. } => { + if let Some(cookies) = cookies { + if let Ok(header_value) = http::header::HeaderValue::from_str(&cookies.join(";")) { + headers.append(http::header::COOKIE, header_value); + } + } + let builder = http::Request::builder() .method(request_context.http.method.as_ref()) .uri({ @@ -569,8 +576,15 @@ mod tests { format!("event was not parsed as expected {:?} given {}", result, input) ); let req = result.expect("failed to parse request"); + let cookie_header = req + .headers() + .get(http::header::COOKIE) + .ok_or_else(|| "Cookie header not found".to_string()) + .and_then(|v| v.to_str().map_err(|e| e.to_string())); + assert_eq!(req.method(), "POST"); assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value"); + assert_eq!(cookie_header, Ok("cookie1=value1;cookie2=value2")); } #[test] diff --git a/lambda-http/tests/data/apigw_v2_proxy_request.json b/lambda-http/tests/data/apigw_v2_proxy_request.json index 3df88e3d..2412895f 100644 --- a/lambda-http/tests/data/apigw_v2_proxy_request.json +++ b/lambda-http/tests/data/apigw_v2_proxy_request.json @@ -3,7 +3,7 @@ "routeKey": "$default", "rawPath": "/my/path", "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", - "cookies": [ "cookie1", "cookie2" ], + "cookies": [ "cookie1=value1", "cookie2=value2" ], "headers": { "Header1": "value1", "Header2": "value2" From c36409c5e65f994c7ff48510cd111905b4aa77c9 Mon Sep 17 00:00:00 2001 From: Johan Smits Date: Thu, 17 Sep 2020 03:14:50 +0200 Subject: [PATCH 012/394] Allow mock methods to be used in external apps (#253) Make mock api available --- lambda-http/src/ext.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index dd08ae73..3257bccf 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -121,7 +121,6 @@ pub trait RequestExt { /// Configures instance with query string parameters under #[cfg(test)] configurations /// /// This is intended for use in mock testing contexts. - #[cfg(test)] fn with_query_string_parameters(self, parameters: Q) -> Self where Q: Into; @@ -137,7 +136,6 @@ pub trait RequestExt { /// Configures instance with path parameters under #[cfg(test)] configurations /// /// This is intended for use in mock testing contexts. - #[cfg(test)] fn with_path_parameters

(self, parameters: P) -> Self where P: Into; @@ -182,7 +180,6 @@ impl RequestExt for http::Request { .unwrap_or_default() } - #[cfg(test)] fn with_query_string_parameters(self, parameters: Q) -> Self where Q: Into, @@ -199,7 +196,6 @@ impl RequestExt for http::Request { .unwrap_or_default() } - #[cfg(test)] fn with_path_parameters

(self, parameters: P) -> Self where P: Into, From 13aa8f01813e6949ca91aedafb286591fdf32217 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Tue, 27 Oct 2020 13:41:24 -0700 Subject: [PATCH 013/394] lambda: Fill in env_config field when constructing Context from a HeaderMap (#248) --- lambda/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 24ef4929..1f2c1824 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -204,7 +204,8 @@ where let event = event?; let (parts, body) = event.into_parts(); - let ctx: Context = Context::try_from(parts.headers)?; + let mut ctx: Context = Context::try_from(parts.headers)?; + ctx.env_config = Config::from_env()?; let body = hyper::body::to_bytes(body).await?; let body = serde_json::from_slice(&body)?; From d9998e7922cb22c4a92e2e60822d07695b16f9ad Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Wed, 20 Jan 2021 01:48:44 +0200 Subject: [PATCH 014/394] Add missing license fields in lambda and lambda-attributes crates (#251) --- lambda-attributes/Cargo.toml | 1 + lambda/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/lambda-attributes/Cargo.toml b/lambda-attributes/Cargo.toml index d0c0e6e7..856c099a 100644 --- a/lambda-attributes/Cargo.toml +++ b/lambda-attributes/Cargo.toml @@ -3,6 +3,7 @@ name = "lambda-attributes" version = "0.1.0" authors = ["David Barsky "] edition = "2018" +license = "Apache-2.0" [lib] proc-macro = true diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index eb718e80..84ddae3b 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["David Barsky "] description = "AWS Lambda Runtime." edition = "2018" +license = "Apache-2.0" [features] # no features by default From ddea860c4c81a9b04f49f0ca751ce544173c5030 Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Wed, 20 Jan 2021 01:49:40 +0200 Subject: [PATCH 015/394] Remove the misleading comment about default features (#263) --- lambda/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index 84ddae3b..bdc03971 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" license = "Apache-2.0" [features] -# no features by default default = ["simulated", "derive"] simulated = [] derive = ["lambda-attributes"] From eab54b6bb15fb973927a0e1e5d0e5cf4ba19ce41 Mon Sep 17 00:00:00 2001 From: Andrii Radyk Date: Wed, 20 Jan 2021 01:14:49 +0100 Subject: [PATCH 016/394] remove travis configuration and update readme build status (#171) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bd8a6d4a..85abaf75 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Rust Runtime for AWS Lambda +[![Build Status](https://github.com/awslabs/aws-lambda-rust-runtime/workflows/Rust/badge.svg)](https://github.com/awslabs/aws-lambda-rust-runtime/actions) + This package makes it easy to run AWS Lambda Functions written in Rust. This workspace includes multiple crates: - [![Docs](https://docs.rs/lambda_runtime_client/badge.svg)](https://docs.rs/lambda_runtime_client) **`lambda-runtime-client`** is a client SDK for the Lambda Runtime APIs. You probably don't need to use this crate directly! From 0009201455729bb5dacfd37bbd760ed3ec55acd2 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:44:05 -0600 Subject: [PATCH 017/394] Upgrade to tokio 1.0 (#276) * Upgrade to tokio 1.0 * Don't enable tokio/full feature * Use proc-macro2 1.0 * Leave out hyper/http2 --- Cargo.lock | 715 +++++++++++++---------------------- lambda-attributes/Cargo.toml | 2 +- lambda-http/Cargo.toml | 2 +- lambda/Cargo.toml | 9 +- lambda/src/client.rs | 2 +- lambda/src/simulated.rs | 21 +- 6 files changed, 289 insertions(+), 462 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cd113cd..62e432ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,890 +1,719 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "arc-swap" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "async-stream" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c" dependencies = [ - "async-stream-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-stream-impl", + "futures-core", ] [[package]] name = "async-stream-impl" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "base64" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" [[package]] -name = "bitflags" -version = "1.2.1" +name = "bytes" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] name = "bytes" -version = "0.5.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dtoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" [[package]] name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" [[package]] name = "futures" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] name = "futures-channel" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-sink", ] [[package]] name = "futures-core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" [[package]] name = "futures-executor" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-task", + "futures-util", ] [[package]] name = "futures-io" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" [[package]] name = "futures-macro" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" dependencies = [ - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "futures-sink" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" [[package]] name = "futures-task" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" [[package]] name = "futures-util" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "h2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] name = "hermit-abi" -version = "0.1.6" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "http" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4", + "fnv", + "itoa", ] [[package]] name = "http-body" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 1.0.0", + "http", ] [[package]] name = "httparse" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" [[package]] -name = "hyper" -version = "0.13.1" +name = "httpdate" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" [[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "indexmap" -version = "1.3.1" +name = "hyper" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 1.0.0", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.3", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "idna" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "lambda" version = "0.1.0" dependencies = [ - "async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lambda-attributes 0.1.0", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-error 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-futures 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "async-stream", + "bytes 1.0.0", + "futures", + "http", + "hyper", + "lambda-attributes", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-error", + "tracing-futures", ] [[package]] name = "lambda-attributes" version = "0.1.0" dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "lambda_http" version = "0.2.0-beta.1" dependencies = [ - "base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lambda 0.1.0", - "lambda-attributes 0.1.0", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "base64", + "http", + "lambda", + "lambda-attributes", + "log", + "maplit", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "tokio", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", ] [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" [[package]] name = "mio" -version = "0.6.21" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", + "miow", + "ntapi", + "winapi", ] [[package]] -name = "mio-named-pipes" -version = "0.1.6" +name = "miow" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2", + "winapi", ] [[package]] -name = "mio-uds" -version = "0.6.7" +name = "ntapi" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] -name = "miow" -version = "0.2.1" +name = "num_cpus" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] -name = "miow" -version = "0.3.3" +name = "percent-encoding" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "net2" -version = "0.2.33" +name = "pin-project" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 0.4.8", ] [[package]] -name = "num_cpus" -version = "1.12.0" +name = "pin-project" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" dependencies = [ - "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 1.0.3", ] [[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-project" +name = "pin-project-internal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" dependencies = [ - "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-internal" -version = "0.4.8" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" [[package]] name = "pin-utils" version = "0.1.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" [[package]] name = "proc-macro-hack" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "proc-macro-nested" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro2" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" [[package]] name = "serde" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "serde_urlencoded" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" dependencies = [ - "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa", + "itoa", + "serde", + "url", ] [[package]] name = "sharded-slab" version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "signal-hook-registry" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "socket2" -version = "0.3.11" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "libc", + "winapi", ] [[package]] name = "syn" -version = "1.0.14" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "tokio" -version = "0.2.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258221f566b6c803c7b4714abadc080172b272090cdc5e244a6d4dd13c3a6bd" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "bytes 1.0.0", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "tokio-macros", ] [[package]] name = "tokio-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-util" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tower-service" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-attributes 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "tracing-attributes", + "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "syn", ] [[package]] name = "tracing-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "tracing-error" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-subscriber 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "tracing-subscriber", ] [[package]] name = "tracing-futures" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" dependencies = [ - "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.8", + "tracing", ] [[package]] name = "tracing-subscriber" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d53c40489aa69c9aed21ff483f26886ca8403df33bdc2d2f87c60c1617826d2" dependencies = [ - "sharded-slab 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "sharded-slab", + "tracing-core", ] [[package]] name = "try-lock" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", ] [[package]] name = "unicode-normalization" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec", ] -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "idna", + "matches", + "percent-encoding", ] [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "try-lock", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" -"checksum async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" -"checksum async-stream-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" -"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" -"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" -"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" -"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" -"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" -"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" -"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" -"checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" -"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" -"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" -"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum hyper 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8bf49cfb32edee45d890537d9057d1b02ed55f53b7b6a30bae83a38c9231749e" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum indexmap 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" -"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" -"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" -"checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" -"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" -"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" -"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -"checksum sharded-slab 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" -"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" -"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b" -"checksum tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c" -"checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" -"checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" -"checksum tracing 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" -"checksum tracing-attributes 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" -"checksum tracing-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" -"checksum tracing-error 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -"checksum tracing-futures 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" -"checksum tracing-subscriber 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1d53c40489aa69c9aed21ff483f26886ca8403df33bdc2d2f87c60c1617826d2" -"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/lambda-attributes/Cargo.toml b/lambda-attributes/Cargo.toml index 856c099a..26bc2365 100644 --- a/lambda-attributes/Cargo.toml +++ b/lambda-attributes/Cargo.toml @@ -9,6 +9,6 @@ license = "Apache-2.0" proc-macro = true [dependencies] -proc-macro2 = { version = "0.4.30" } +proc-macro2 = "1.0" syn = { version = "1.0.5", features = ["full"] } quote = "1" diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 125b1714..f0cfa6b7 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -28,4 +28,4 @@ serde_urlencoded = "0.6" [dev-dependencies] log = "^0.4" maplit = "1.0" -tokio = { version = "0.2", features = ["macros"] } +tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index bdc03971..02320bb3 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -12,15 +12,14 @@ simulated = [] derive = ["lambda-attributes"] [dependencies] -tokio = { version = "0.2.4", features = ["full"] } -hyper = "0.13" +tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } serde = { version = "1", features = ["derive"] } serde_json = "1.0.39" -tower-service = "0.3" -bytes = "0.5" +bytes = "1.0" http = "0.2" lambda-attributes = { path = "../lambda-attributes", version = "0.1.0", optional = true} -async-stream = "0.2" +async-stream = "0.3" futures = "0.3" tracing = "0.1.13" tracing-futures = "0.2.3" diff --git a/lambda/src/client.rs b/lambda/src/client.rs index 64313ebf..e1f667dd 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -39,7 +39,7 @@ async fn handle_incoming(req: Request) -> Result, Error> { } #[instrument(skip(io, rx))] -async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::error::Error> +async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> where I: AsyncRead + AsyncWrite + Unpin + 'static, { diff --git a/lambda/src/simulated.rs b/lambda/src/simulated.rs index b59f0754..843a2842 100644 --- a/lambda/src/simulated.rs +++ b/lambda/src/simulated.rs @@ -9,8 +9,7 @@ use std::{ sync::{Arc, Mutex}, task::{Context, Poll, Waker}, }; -use tokio::io::{AsyncRead, AsyncWrite}; -use tower_service::Service; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; /// Creates a pair of AsyncReadWrite data streams, where the write end of each member of the pair /// is the read end of the other member of the pair. This allows us to emulate the behavior of a TcpStream @@ -41,7 +40,7 @@ pub struct SimulatedConnector { pub inner: SimStream, } -impl Service for SimulatedConnector { +impl hyper::service::Service for SimulatedConnector { type Response = SimStream; type Error = std::io::Error; type Future = Pin> + Send>>; @@ -89,7 +88,7 @@ impl AsyncWrite for SimStream { /// Delegates to the underlying `read` member's methods impl AsyncRead for SimStream { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { Pin::new(&mut self.read).poll_read(cx, buf) } } @@ -123,11 +122,11 @@ impl BufferState { } /// Read data from the end of the deque byte buffer - fn read(&mut self, to_buf: &mut [u8]) -> usize { + fn read(&mut self, to_buf: &mut ReadBuf<'_>) -> usize { // Read no more bytes than we have available, and no more bytes than we were asked for - let bytes_to_read = min(to_buf.len(), self.buffer.len()); - for i in 0..bytes_to_read { - to_buf[i] = self.buffer.pop_back().unwrap(); + let bytes_to_read = min(to_buf.remaining(), self.buffer.len()); + for _ in 0..bytes_to_read { + to_buf.put_slice(&[self.buffer.pop_back().unwrap()]); } bytes_to_read @@ -173,7 +172,7 @@ pub struct ReadHalf { } impl AsyncRead for ReadHalf { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { // Acquire the lock for the buffer let mut read_from = self .buffer @@ -186,12 +185,12 @@ impl AsyncRead for ReadHalf { // means that someone trying to read from a VecDeque that hasn't been written to yet // would get an Eof error (as I learned the hard way). Instead we should return Poll:Pending // to indicate that there could be more to read in the future. - if (bytes_read) == 0 { + if bytes_read == 0 { read_from.read_waker = Some(cx.waker().clone()); Poll::Pending } else { //read_from.read_waker = Some(cx.waker().clone()); - Poll::Ready(Ok(bytes_read)) + Poll::Ready(Ok(())) } } } From 04712a34d2cb7a6598dc0f4e8e727c1a20da8532 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Wed, 27 Jan 2021 00:16:10 +0100 Subject: [PATCH 018/394] fix: fix RequestExt.payload() for content-type with charset (#255) * fix: fix RequestExt.payload() for content-type with charset * fix: cargo fmt --- lambda-http/src/ext.rs | 61 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 3257bccf..81db5ed7 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -236,12 +236,19 @@ impl RequestExt for http::Request { self.headers() .get(http::header::CONTENT_TYPE) .map(|ct| match ct.to_str() { - Ok("application/x-www-form-urlencoded") => serde_urlencoded::from_bytes::(self.body().as_ref()) - .map_err(PayloadError::WwwFormUrlEncoded) - .map(Some), - Ok("application/json") => serde_json::from_slice::(self.body().as_ref()) - .map_err(PayloadError::Json) - .map(Some), + Ok(content_type) => { + if content_type.starts_with("application/x-www-form-urlencoded") { + return serde_urlencoded::from_bytes::(self.body().as_ref()) + .map_err(PayloadError::WwwFormUrlEncoded) + .map(Some); + } else if content_type.starts_with("application/json") { + return serde_json::from_slice::(self.body().as_ref()) + .map_err(PayloadError::Json) + .map(Some); + } + + Ok(None) + } _ => Ok(None), }) .unwrap_or_else(|| Ok(None)) @@ -322,6 +329,48 @@ mod tests { ); } + #[test] + fn requests_match_form_post_content_type_with_charset() { + #[derive(Deserialize, PartialEq, Debug)] + struct Payload { + foo: String, + baz: usize, + } + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + + #[test] + fn requests_match_json_content_type_with_charset() { + #[derive(Deserialize, PartialEq, Debug)] + struct Payload { + foo: String, + baz: usize, + } + let request = http::Request::builder() + .header("Content-Type", "application/json; charset=UTF-8") + .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + #[test] fn requests_omiting_content_types_do_not_support_parseable_payloads() { #[derive(Deserialize, PartialEq, Debug)] From feab8b7b17e361e841465b8615390b5f56ecf5d8 Mon Sep 17 00:00:00 2001 From: Ryan Gerstenkorn Date: Thu, 28 Jan 2021 22:13:38 -0600 Subject: [PATCH 019/394] Add note to README.md build for Mac OS (#265) * Add note to README.md build for Mac OS If you follow the directions here on Mac OS you get the following error: ``` = note: clang: warning: argument unused during compilation: '-static-pie' [-Wunused-command-line-argument] ld: unknown option: --as-needed clang: error: linker command failed with exit code 1 (use -v to see invocation) ``` Following the steps [here](https://aws.amazon.com/blogs/opensource/rust-runtime-for-aws-lambda/) resolved the issue for me, which was basically just installing and using the linker from filosottile/musl-cross/musl-cross. * Addressing nit comments Co-authored-by: Colton Weaver --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 85abaf75..14f64d8d 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,14 @@ Run this script once to add the new target: $ rustup target add x86_64-unknown-linux-musl ``` + + * **Note:** If you are running on Mac OS you'll need to install the linker for the target platform. You do this using the `musl-cross` tap from [Homebrew](https://brew.sh/) which provides a complete cross-compilation toolchain for Mac OS. Once `musl-cross` is installed we will also need to inform cargo of the newly installed linker when building for the `x86_64-unknown-linux-musl` platform. +```bash +$ brew install filosottile/musl-cross/musl-cross +$ mkdir .cargo +$ echo $'[target.x86_64-unknown-linux-musl]\nlinker = "x86_64-linux-musl-gcc"' > .cargo/config +``` + Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: ```bash $ cargo build -p lambda --example hello --release --target x86_64-unknown-linux-musl From 721f616291ea19146798deda19d13024b2cd22c0 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 28 Jan 2021 20:20:59 -0800 Subject: [PATCH 020/394] Bring README up to date with `master` branch (#247) * First pass of changes to bring README up to date with `master` branch, preparing for next release * First round of PR feedback - address typos and `extern crate` * Apply suggestions from softprops and davidbarsky Co-authored-by: David Barsky * Remove mentions of attribute macro per discussion in PR. * Return hello functionality to README and referenced example, while leaving custom object handling to later in the document * Update Deployment to reflect new example behavior * Suggestion from softprops Co-authored-by: David Barsky --- README.md | 86 +++++++------------------- lambda/examples/hello-without-macro.rs | 6 +- 2 files changed, 25 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 14f64d8d..32385540 100644 --- a/README.md +++ b/README.md @@ -4,58 +4,38 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This workspace includes multiple crates: -- [![Docs](https://docs.rs/lambda_runtime_client/badge.svg)](https://docs.rs/lambda_runtime_client) **`lambda-runtime-client`** is a client SDK for the Lambda Runtime APIs. You probably don't need to use this crate directly! -- [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that makes it easy to write Lambda functions in Rust. +- [![Docs](https://docs.rs/lambda/badge.svg)](https://docs.rs/lambda) **`lambda`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. ## Example function -The code below creates a simple function that receives an event with a `greeting` and `name` field and returns a `GreetingResponse` message for the given name and greeting. Notice: to run these examples, we require a minimum Rust version of 1.31. +The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. ```rust,no_run -use std::error::Error; - -use lambda_runtime::{error::HandlerError, lambda, Context}; -use log::{self, error}; -use serde_derive::{Deserialize, Serialize}; -use simple_error::bail; -use simple_logger; - -#[derive(Deserialize)] -struct CustomEvent { - #[serde(rename = "firstName")] - first_name: String, -} +use lambda::{handler_fn, Context}; +use serde_json::{json, Value}; -#[derive(Serialize)] -struct CustomOutput { - message: String, -} - -fn main() -> Result<(), Box> { - simple_logger::init_with_level(log::Level::Debug)?; - lambda!(my_handler); +type Error = Box; +#[tokio::main] +async fn main() -> Result<(), Error> { + let func = handler_fn(func); + lambda::run(func).await?; Ok(()) } -fn my_handler(e: CustomEvent, c: Context) -> Result { - if e.first_name == "" { - error!("Empty first name in request {}", c.aws_request_id); - bail!("Empty first name"); - } +async fn func(event: Value, _: Context) -> Result { + let first_name = event["firstName"].as_str().unwrap_or("world"); - Ok(CustomOutput { - message: format!("Hello, {}!", e.first_name), - }) + Ok(json!({ "message": format!("Hello, {}!", first_name) })) } ``` -The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/tree/master/lambda-runtime/examples/basic.rs) in the `lambda-runtime` crate. +The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda/examples/hello-without-macro.rs) in the `lambda` crate. ### Deployment -There are currently multiple ways of building this package: manually, and with the [Serverless framework](https://serverless.com/framework/). +There are currently multiple ways of building this package: manually with the AWS CLI, and with the [Serverless framework](https://serverless.com/framework/). #### AWS CLI @@ -103,7 +83,7 @@ You can now test the function using the AWS CLI or the AWS Lambda console $ aws lambda invoke --function-name rustTest \ --payload '{"firstName": "world"}' \ output.json -$ cat output.json # Prints: {"message":"Hello, world!"} +$ cat output.json # Prints: {"message": "Hello, world!"} ``` **Note:** `--cli-binary-format raw-in-base64-out` is a required @@ -177,34 +157,14 @@ $ unzip -o \ # Ctrl-D to yield control back to your function ``` -## lambda-runtime-client - -Defines the `RuntimeClient` trait and provides its `HttpRuntimeClient` implementation. The client fetches events and returns output as `Vec`. - -For error reporting to the runtime APIs the library defines the `RuntimeApiError` trait and the `ErrorResponse` object. Custom errors for the APIs should implement the `to_response() -> ErrorResponse` method of the `RuntimeApiError` trait. +## `lambda` -## lambda-runtime +`lambda` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides a few major components: -This library makes it easy to create Rust executables for AWS lambda. The library defines a `lambda!()` macro. Call the `lambda!()` macro from your main method with an implementation the `Handler` type: +- `Handler`, a trait that defines interactions between customer-authored code and this library. +- `lambda::run`, function that runs an `Handler`. -```rust -pub trait Handler { - /// Run the handler. - fn run( - &mut self, - event: E, - ctx: Context - ) -> Result; -} -``` - -`Handler` provides a default implementation that enables you to provide a Rust closure or function pointer to the `lambda!()` macro. - -Optionally, you can pass your own instance of Tokio runtime to the `lambda!()` macro: -``` -let rt = tokio::runtime::Runtime::new()?; -lambda!(my_handler, rt); -``` +The function `handler_fn` converts a rust function or closure to `Handler`, which can then be run by `lambda::run`. ## AWS event objects @@ -215,11 +175,7 @@ This project does not currently include Lambda event struct definitions though w To serialize and deserialize events and responses, we suggest using the use the [`serde`](https://github.com/serde-rs/serde) library. To receive custom events, annotate your structure with Serde's macros: ```rust -extern crate serde; -extern crate serde_derive; -extern crate serde_json; - -use serde_derive::{Serialize, Deserialize}; +use serde::{Serialize, Deserialize}; use serde_json::json; use std::error::Error; diff --git a/lambda/examples/hello-without-macro.rs b/lambda/examples/hello-without-macro.rs index 2aec334f..d94ee322 100644 --- a/lambda/examples/hello-without-macro.rs +++ b/lambda/examples/hello-without-macro.rs @@ -1,5 +1,5 @@ use lambda::{handler_fn, Context}; -use serde_json::Value; +use serde_json::{json, Value}; type Error = Box; @@ -11,5 +11,7 @@ async fn main() -> Result<(), Error> { } async fn func(event: Value, _: Context) -> Result { - Ok(event) + let first_name = event["firstName"].as_str().unwrap_or("world"); + + Ok(json!({ "message": format!("Hello, {}!", first_name) })) } From 5ae037ef9ffabdac5a2cfcd22cd2c090e104e914 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Sat, 30 Jan 2021 20:42:05 -0800 Subject: [PATCH 021/394] Remove lambda attributes (#282) --- Cargo.lock | 296 +++++++++--------- Cargo.toml | 1 - lambda-attributes/Cargo.toml | 14 - lambda-attributes/src/lib.rs | 98 ------ lambda-http/Cargo.toml | 1 - .../examples/hello-http-without-macros.rs | 23 -- lambda-http/examples/hello-http.rs | 21 +- lambda-http/src/lib.rs | 33 +- lambda/Cargo.toml | 4 +- lambda/examples/hello-without-macro.rs | 17 - lambda/examples/hello.rs | 17 +- lambda/src/lib.rs | 33 +- 12 files changed, 191 insertions(+), 367 deletions(-) delete mode 100644 lambda-attributes/Cargo.toml delete mode 100644 lambda-attributes/src/lib.rs delete mode 100644 lambda-http/examples/hello-http-without-macros.rs delete mode 100644 lambda/examples/hello-without-macro.rs diff --git a/Cargo.lock b/Cargo.lock index 62e432ab..7a444286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,33 +23,21 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" - -[[package]] -name = "bytes" -version = "0.5.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bytes" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" - -[[package]] -name = "cfg-if" -version = "0.1.10" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "cfg-if" @@ -59,21 +47,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dtoa" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] [[package]] name = "futures" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" +checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" dependencies = [ "futures-channel", "futures-core", @@ -86,9 +84,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" dependencies = [ "futures-core", "futures-sink", @@ -96,15 +94,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" +checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" [[package]] name = "futures-executor" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" +checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" dependencies = [ "futures-core", "futures-task", @@ -113,15 +111,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" +checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" [[package]] name = "futures-macro" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" +checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -131,21 +129,24 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" +checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" [[package]] name = "futures-task" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" +checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" +dependencies = [ + "once_cell", +] [[package]] name = "futures-util" -version = "0.3.1" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" dependencies = [ "futures-channel", "futures-core", @@ -154,6 +155,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -162,20 +164,20 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "http" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 0.5.4", + "bytes", "fnv", "itoa", ] @@ -186,7 +188,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes 1.0.0", + "bytes", "http", ] @@ -208,7 +210,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "bytes 1.0.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -217,7 +219,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project 1.0.3", + "pin-project 1.0.4", "socket2", "tokio", "tower-service", @@ -238,20 +240,19 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "lambda" version = "0.1.0" dependencies = [ "async-stream", - "bytes 1.0.0", + "bytes", "futures", "http", "hyper", - "lambda-attributes", "serde", "serde_json", "tokio", @@ -260,15 +261,6 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "lambda-attributes" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "lambda_http" version = "0.2.0-beta.1" @@ -276,7 +268,6 @@ dependencies = [ "base64", "http", "lambda", - "lambda-attributes", "log", "maplit", "serde", @@ -294,17 +285,17 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" [[package]] name = "log" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] @@ -321,9 +312,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "mio" @@ -350,9 +341,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ "winapi", ] @@ -367,6 +358,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -375,27 +372,27 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.8" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" dependencies = [ - "pin-project-internal 0.4.8", + "pin-project-internal 0.4.27", ] [[package]] name = "pin-project" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" +checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2" dependencies = [ - "pin-project-internal 1.0.3", + "pin-project-internal 1.0.4", ] [[package]] name = "pin-project-internal" -version = "0.4.8" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ "proc-macro2", "quote", @@ -404,9 +401,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" +checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" dependencies = [ "proc-macro2", "quote", @@ -415,32 +412,27 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" [[package]] name = "pin-utils" -version = "0.1.0-alpha.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" @@ -453,33 +445,33 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.2" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" -version = "1.0.104" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2", "quote", @@ -488,9 +480,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.45" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ "itoa", "ryu", @@ -511,9 +503,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.0.9" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" dependencies = [ "lazy_static", ] @@ -524,42 +516,60 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smallvec" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" - [[package]] name = "socket2" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "winapi", ] [[package]] name = "syn" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "thread_local" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258221f566b6c803c7b4714abadc080172b272090cdc5e244a6d4dd13c3a6bd" +checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195" dependencies = [ "autocfg", - "bytes 1.0.0", + "bytes", "libc", "memchr", "mio", @@ -581,36 +591,38 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.13" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", + "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" +checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" dependencies = [ + "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" -version = "0.1.10" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" dependencies = [ "lazy_static", ] @@ -631,25 +643,26 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" dependencies = [ - "pin-project 0.4.8", + "pin-project 0.4.27", "tracing", ] [[package]] name = "tracing-subscriber" -version = "0.2.5" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d53c40489aa69c9aed21ff483f26886ca8403df33bdc2d2f87c60c1617826d2" +checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" dependencies = [ "sharded-slab", + "thread_local", "tracing-core", ] [[package]] name = "try-lock" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "unicode-bidi" @@ -662,25 +675,26 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ - "smallvec", + "tinyvec", ] [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" dependencies = [ + "form_urlencoded", "idna", "matches", "percent-encoding", @@ -698,9 +712,9 @@ dependencies = [ [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", diff --git a/Cargo.toml b/Cargo.toml index 7942bf40..4c4ad33d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ "lambda", - "lambda-attributes", "lambda-http" ] \ No newline at end of file diff --git a/lambda-attributes/Cargo.toml b/lambda-attributes/Cargo.toml deleted file mode 100644 index 26bc2365..00000000 --- a/lambda-attributes/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "lambda-attributes" -version = "0.1.0" -authors = ["David Barsky "] -edition = "2018" -license = "Apache-2.0" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -syn = { version = "1.0.5", features = ["full"] } -quote = "1" diff --git a/lambda-attributes/src/lib.rs b/lambda-attributes/src/lib.rs deleted file mode 100644 index a17fdfad..00000000 --- a/lambda-attributes/src/lib.rs +++ /dev/null @@ -1,98 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote_spanned; -use syn::{spanned::Spanned, AttributeArgs, FnArg, ItemFn, Meta, NestedMeta}; - -/// Return true if attribute macro args declares http flavor in the form `#[lambda(http)]` -fn is_http(args: &AttributeArgs) -> bool { - args.iter().any(|arg| match arg { - NestedMeta::Meta(Meta::Path(path)) => path.is_ident("http"), - _ => false, - }) -} - -#[proc_macro_attribute] -pub fn lambda(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as ItemFn); - let args = syn::parse_macro_input!(attr as AttributeArgs); - let ret = &input.sig.output; - let name = &input.sig.ident; - let body = &input.block; - let attrs = &input.attrs; - let asyncness = &input.sig.asyncness; - let inputs = &input.sig.inputs; - - if name != "main" { - let tokens = quote_spanned! { name.span() => - compile_error!("only the main function can be tagged with #[lambda]"); - }; - return TokenStream::from(tokens); - } - - if asyncness.is_none() { - let tokens = quote_spanned! { input.span() => - compile_error!("the async keyword is missing from the function declaration"); - }; - return TokenStream::from(tokens); - } - - let result = match inputs.len() { - 2 => { - let event = match inputs.first().expect("expected event argument") { - FnArg::Typed(arg) => arg, - _ => { - let tokens = quote_spanned! { inputs.span() => - compile_error!("fn main's first argument must be fully formed"); - }; - return TokenStream::from(tokens); - } - }; - let event_name = &event.pat; - let event_type = &event.ty; - let context = match inputs.iter().nth(1).expect("expected context argument") { - FnArg::Typed(arg) => arg, - _ => { - let tokens = quote_spanned! { inputs.span() => - compile_error!("fn main's second argument must be fully formed"); - }; - return TokenStream::from(tokens); - } - }; - let context_name = &context.pat; - let context_type = &context.ty; - - if is_http(&args) { - quote_spanned! { input.span() => - - #(#attrs)* - #asyncness fn main() { - async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body - - let f = lambda_http::handler(actual); - lambda_http::lambda::run(f).await.unwrap(); - } - } - } else { - quote_spanned! { input.span() => - - #(#attrs)* - #asyncness fn main() { - async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body - - let f = lambda::handler_fn(actual); - lambda::run(f).await.unwrap(); - } - } - } - } - _ => { - let tokens = quote_spanned! { inputs.span() => - compile_error!("The #[lambda] macro can expects two arguments: a triggered event and lambda context."); - }; - return TokenStream::from(tokens); - } - }; - - result.into() -} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index f0cfa6b7..4abbc02c 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -19,7 +19,6 @@ maintenance = { status = "actively-developed" } base64 = "0.12" http = "0.2" lambda = { path = "../lambda", version = "0.1" } -lambda-attributes = { path = "../lambda-attributes", version = "0.1" } serde = "^1" serde_derive = "^1" serde_json = "^1" diff --git a/lambda-http/examples/hello-http-without-macros.rs b/lambda-http/examples/hello-http-without-macros.rs deleted file mode 100644 index 0a609651..00000000 --- a/lambda-http/examples/hello-http-without-macros.rs +++ /dev/null @@ -1,23 +0,0 @@ -use lambda_http::{ - handler, - lambda::{self, Context}, - IntoResponse, Request, RequestExt, Response, -}; - -type Error = Box; - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda::run(handler(func)).await?; - Ok(()) -} - -async fn func(event: Request, _: Context) -> Result { - Ok(match event.query_string_parameters().get("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response(), - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) -} diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index f8fa1f16..0a609651 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -1,12 +1,23 @@ use lambda_http::{ - lambda::{lambda, Context}, - IntoResponse, Request, + handler, + lambda::{self, Context}, + IntoResponse, Request, RequestExt, Response, }; type Error = Box; -#[lambda(http)] #[tokio::main] -async fn main(_: Request, _: Context) -> Result { - Ok("👋 world") +async fn main() -> Result<(), Error> { + lambda::run(handler(func)).await?; + Ok(()) +} + +async fn func(event: Request, _: Context) -> Result { + Ok(match event.query_string_parameters().get("first_name") { + Some(first_name) => format!("Hello, {}!", first_name).into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }) } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index f26fa351..80796e10 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -11,33 +11,10 @@ //! //! ## Hello World //! -//! `lambda_http` handlers adapt to the standard `lambda::Handler` interface using the [`handler`](fn.handler.html) function. -//! -//! The simplest case of an http handler is a function of an `http::Request` to a type that can be lifted into an `http::Response`. -//! You can learn more about these types [here](trait.IntoResponse.html). -//! -//! Adding an `#[lambda(http)]` attribute to a `#[tokio::run]`-decorated `main` function will setup and run the Lambda function. -//! -//! Note: this comes at the expense of any onetime initialization your lambda task might find value in. -//! The full body of your `main` function will be executed on **every** invocation of your lambda task. -//! -//! ```rust,no_run -//! use lambda_http::{lambda::{lambda, Context}, Request, IntoResponse}; -//! -//! type Error = Box; -//! -//! #[lambda(http)] -//! #[tokio::main] -//! async fn main(_: Request, _: Context) -> Result { -//! Ok("👋 world!") -//! } -//! ``` -//! -//! ## Hello World, Without Macros -//! -//! For cases where your lambda might benfit from one time function initializiation might -//! prefer a plain `main` function and invoke `lambda::run` explicitly in combination with the [`handler`](fn.handler.html) function. -//! Depending on the runtime cost of your dependency bootstrapping, this can reduce the overall latency of your functions execution path. +//! The following example is how you would structure your Lambda such that you have a `main` function where you explicitly invoke +//! `lambda::run` in combination with the [`handler`](fn.handler.html) function. This pattern allows you to utilize global initialization +//! 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 +//! your function's execution path. //! //! ```rust,no_run //! use lambda_http::{handler, lambda}; @@ -51,7 +28,6 @@ //! lambda::run(handler(|request, context| async { Ok("👋 world!") })).await?; //! Ok(()) //! } -//! //! ``` //! //! ## Leveraging trigger provided data @@ -92,7 +68,6 @@ extern crate maplit; pub use http::{self, Response}; use lambda::Handler as LambdaHandler; pub use lambda::{self, Context}; -pub use lambda_attributes::lambda; mod body; pub mod ext; diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index 02320bb3..b4d24894 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -7,9 +7,8 @@ edition = "2018" license = "Apache-2.0" [features] -default = ["simulated", "derive"] +default = ["simulated"] simulated = [] -derive = ["lambda-attributes"] [dependencies] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } @@ -18,7 +17,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1.0.39" bytes = "1.0" http = "0.2" -lambda-attributes = { path = "../lambda-attributes", version = "0.1.0", optional = true} async-stream = "0.3" futures = "0.3" tracing = "0.1.13" diff --git a/lambda/examples/hello-without-macro.rs b/lambda/examples/hello-without-macro.rs deleted file mode 100644 index d94ee322..00000000 --- a/lambda/examples/hello-without-macro.rs +++ /dev/null @@ -1,17 +0,0 @@ -use lambda::{handler_fn, Context}; -use serde_json::{json, Value}; - -type Error = Box; - -#[tokio::main] -async fn main() -> Result<(), Error> { - let func = handler_fn(func); - lambda::run(func).await?; - Ok(()) -} - -async fn func(event: Value, _: Context) -> Result { - let first_name = event["firstName"].as_str().unwrap_or("world"); - - Ok(json!({ "message": format!("Hello, {}!", first_name) })) -} diff --git a/lambda/examples/hello.rs b/lambda/examples/hello.rs index 99472874..d94ee322 100644 --- a/lambda/examples/hello.rs +++ b/lambda/examples/hello.rs @@ -1,10 +1,17 @@ -use lambda::{lambda, Context}; -use serde_json::Value; +use lambda::{handler_fn, Context}; +use serde_json::{json, Value}; type Error = Box; -#[lambda] #[tokio::main] -async fn main(event: Value, _: Context) -> Result { - Ok(event) +async fn main() -> Result<(), Error> { + let func = handler_fn(func); + lambda::run(func).await?; + Ok(()) +} + +async fn func(event: Value, _: Context) -> Result { + let first_name = event["firstName"].as_str().unwrap_or("world"); + + Ok(json!({ "message": format!("Hello, {}!", first_name) })) } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 1f2c1824..6f420f56 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -3,40 +3,13 @@ //! The official Rust runtime for AWS Lambda. //! -//! There are two mechanisms available for defining a Lambda function: -//! 1. The `#[lambda]` attribute, which generates the boilerplate to -//! to launch and run a Lambda function. +//! The mechanism available for defining a Lambda function is as follows: //! -//! The `#[lambda]` attribute _must_ be placed on an asynchronous main function. -//! However, as asynchronous main functions are not legal valid Rust -//! this means that the main function must also be decorated using a -//! `#[tokio::main]` attribute macro. This is available from -//! the [tokio](https://github.com/tokio-rs/tokio) crate. -//! -//! 2. A type that conforms to the [`Handler`] trait. This type can then be passed -//! to the the `lambda::run` function, which launches and runs the Lambda runtime. -//! -//! An asynchronous function annotated with the `#[lambda]` attribute must -//! accept an argument of type `A` which implements [`serde::Deserialize`], a [`lambda::Context`] and -//! return a `Result`, where `B` implements [`serde::Serializable`]. `E` is -//! any type that implements `Into>`. -//! -//! ```no_run -//! use lambda::{lambda, Context}; -//! use serde_json::Value; -//! -//! type Error = Box; -//! -//! #[lambda] -//! #[tokio::main] -//! async fn main(event: Value, _: Context) -> Result { -//! Ok(event) -//! } -//! ``` +//! Create a type that conforms to the [`Handler`] trait. This type can then be passed +//! to the the `lambda::run` function, which launches and runs the Lambda runtime. pub use crate::types::Context; use client::Client; use futures::stream::{Stream, StreamExt}; -pub use lambda_attributes::lambda; use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, From e0803e18676d05ca51e714eb84a23f1bea53f887 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Wed, 3 Feb 2021 20:46:51 -0600 Subject: [PATCH 022/394] Error logging and examples (#284) * reorg * Replace `genawaiter` with `async-stream` (#240) * don't depend on serde_derive directly * Add Cargo.lock to gitignore; whoops. * Cleanup docs; handle panics in lambda functions correctly. * Create a reusable runtime struct + builder * Log handler errors in runtime. Issue #241 * Added basic.rs example, comments to other examples * Tidied up examples readme. * Fixed headers in examples readme. * Formatting fix in error-handling.rs. * Fixed comment in lib.rs * Corrected handler_fn interface misconception in basic.rs Co-authored-by: Veetaha * Upper-cased and edited examples/readme.md * Add log and tracing examples; fix `tracing` dependency features. * disable time on `tracing_subscriber` * Removed unused refs from basic.rs * Added error handling for #241, interim, broken. * Added err logging to lib.rs, consolidated examples * Log panic as Debug+Display, improved examples * client.rs formatting fix * Fixed "Process exited" error in lib.rs. * Added some tracing!() to lib.rs. * Enforced Send+Sync for http Handler impl. * Added docs and missing _vars to reduce warnings * Removed unnecessary + Sync on handler's future * All tests passing * Remove deprecated simple_logger calls * Remove Cargo.lock * cargo fmt changes Co-authored-by: David Barsky Co-authored-by: Lucio Franco Co-authored-by: Ubuntu Co-authored-by: rimutaka Co-authored-by: Veetaha --- .gitignore | 1 + Cargo.lock | 733 ------------------------------ lambda-http/Cargo.toml | 3 +- lambda-http/src/ext.rs | 7 +- lambda-http/src/lib.rs | 18 +- lambda-http/src/request.rs | 67 ++- lambda-http/src/response.rs | 6 +- lambda-http/src/strmap.rs | 11 +- lambda/Cargo.toml | 19 +- lambda/examples/README.md | 254 +++++++++++ lambda/examples/basic.rs | 54 +++ lambda/examples/error-handling.rs | 117 +++++ lambda/examples/hello.rs | 17 - lambda/src/client.rs | 349 +++++++------- lambda/src/lib.rs | 288 ++++++++---- lambda/src/requests.rs | 2 +- lambda/src/simulated.rs | 11 +- 17 files changed, 876 insertions(+), 1081 deletions(-) delete mode 100644 Cargo.lock create mode 100644 lambda/examples/README.md create mode 100644 lambda/examples/basic.rs create mode 100644 lambda/examples/error-handling.rs delete mode 100644 lambda/examples/hello.rs diff --git a/.gitignore b/.gitignore index 911cf87c..94f3d847 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target /.cargo lambda-runtime/libtest.rmeta +Cargo.lock # Built AWS Lambda zipfile lambda.zip diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 7a444286..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,733 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "async-stream" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "bytes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "dtoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" - -[[package]] -name = "futures-executor" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" - -[[package]] -name = "futures-macro" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" - -[[package]] -name = "futures-task" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" -dependencies = [ - "once_cell", -] - -[[package]] -name = "futures-util" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "proc-macro-hack", - "proc-macro-nested", - "slab", -] - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "http" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "httpdate" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" - -[[package]] -name = "hyper" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project 1.0.4", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - -[[package]] -name = "lambda" -version = "0.1.0" -dependencies = [ - "async-stream", - "bytes", - "futures", - "http", - "hyper", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-error", - "tracing-futures", -] - -[[package]] -name = "lambda_http" -version = "0.2.0-beta.1" -dependencies = [ - "base64", - "http", - "lambda", - "log", - "maplit", - "serde", - "serde_derive", - "serde_json", - "serde_urlencoded", - "tokio", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "memchr" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" - -[[package]] -name = "mio" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" -dependencies = [ - "socket2", - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" -dependencies = [ - "pin-project-internal 0.4.27", -] - -[[package]] -name = "pin-project" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2" -dependencies = [ - "pin-project-internal 1.0.4", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - -[[package]] -name = "proc-macro2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "serde" -version = "1.0.123" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.123" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url", -] - -[[package]] -name = "sharded-slab" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if", - "libc", - "winapi", -] - -[[package]] -name = "syn" -version = "1.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "thread_local" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tinyvec" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-error" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-futures" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" -dependencies = [ - "pin-project 0.4.27", - "tracing", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "url" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4abbc02c..575c2d3e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -19,8 +19,7 @@ maintenance = { status = "actively-developed" } base64 = "0.12" http = "0.2" lambda = { path = "../lambda", version = "0.1" } -serde = "^1" -serde_derive = "^1" +serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.6" diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 81db5ed7..13740f49 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -1,10 +1,9 @@ //! Extension methods for `http::Request` types +use crate::{request::RequestContext, strmap::StrMap, Body}; use serde::{de::value::Error as SerdeError, Deserialize}; use std::{error::Error, fmt}; -use crate::{request::RequestContext, strmap::StrMap, Body}; - /// ALB/API gateway pre-parsed http query string parameters pub(crate) struct QueryStringParameters(pub(crate) StrMap); @@ -68,7 +67,7 @@ impl Error for PayloadError { /// /// ```rust,no_run /// use lambda_http::{handler, lambda::{self, Context}, Body, IntoResponse, Request, Response, RequestExt}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// /// type Error = Box; /// @@ -258,7 +257,7 @@ impl RequestExt for http::Request { #[cfg(test)] mod tests { use crate::{Body, Request, RequestExt}; - use serde_derive::Deserialize; + use serde::Deserialize; #[test] fn requests_can_mock_query_string_parameters_ext() { diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 80796e10..7ee4927e 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -97,9 +97,9 @@ pub trait Handler: Sized { /// The type of Response this Handler will return type Response: IntoResponse; /// The type of Future this Handler will return - type Fut: Future> + 'static; + type Fut: Future> + Send + Sync + 'static; /// Function used to execute handler behavior - fn call(&mut self, event: Request, context: Context) -> Self::Fut; + fn call(&self, event: Request, context: Context) -> Self::Fut; } /// Adapts a [`Handler`](trait.Handler.html) to the `lambda::run` interface @@ -110,22 +110,22 @@ pub fn handler(handler: H) -> Adapter { /// An implementation of `Handler` for a given closure return a `Future` representing the computed response impl Handler for F where - F: FnMut(Request, Context) -> Fut, + F: Fn(Request, Context) -> Fut, R: IntoResponse, - Fut: Future> + Send + 'static, + Fut: Future> + Send + Sync + 'static, { type Response = R; type Error = Error; type Fut = Fut; - fn call(&mut self, event: Request, context: Context) -> Self::Fut { - (*self)(event, context) + fn call(&self, event: Request, context: Context) -> Self::Fut { + (self)(event, context) } } #[doc(hidden)] pub struct TransformResponse { is_alb: bool, - fut: Pin>>>, + fut: Pin> + Send + Sync>>, } impl Future for TransformResponse @@ -158,7 +158,7 @@ impl Handler for Adapter { type Response = H::Response; type Error = H::Error; type Fut = H::Fut; - fn call(&mut self, event: Request, context: Context) -> Self::Fut { + fn call(&self, event: Request, context: Context) -> Self::Fut { self.handler.call(event, context) } } @@ -166,7 +166,7 @@ impl Handler for Adapter { impl LambdaHandler, LambdaResponse> for Adapter { type Error = H::Error; type Fut = TransformResponse; - fn call(&mut self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { + fn call(&self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { let is_alb = event.is_alb(); let fut = Box::pin(self.handler.call(event.into(), context)); TransformResponse { is_alb, fut } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 347cb29e..3444209f 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,16 +3,17 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use serde::de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor}; -use serde_derive::Deserialize; -use serde_json::{error::Error as JsonError, Value}; -use std::{borrow::Cow, collections::HashMap, fmt, io::Read, mem}; - use crate::{ body::Body, ext::{PathParameters, QueryStringParameters, StageVariables}, strmap::StrMap, }; +use serde::{ + de::{Deserializer, Error as DeError, MapAccess, Visitor}, + Deserialize, +}; +use serde_json::{error::Error as JsonError, Value}; +use std::{borrow::Cow, collections::HashMap, fmt, io::Read, mem}; /// Internal representation of an Lambda http event from /// ALB, API Gateway REST and HTTP API proxy event perspectives @@ -101,42 +102,65 @@ impl LambdaRequest<'_> { } } +/// See [context-variable-reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for more detail. #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2RequestContext { + /// The API owner's AWS account ID. pub account_id: String, + /// The identifier API Gateway assigns to your API. pub api_id: String, + /// The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. #[serde(default)] pub authorizer: HashMap, + /// The full domain name used to invoke the API. This should be the same as the incoming Host header. pub domain_name: String, + /// The first label of the $context.domainName. This is often used as a caller/customer identifier. pub domain_prefix: String, + /// The HTTP method used. pub http: Http, + /// The ID that API Gateway assigns to the API request. pub request_id: String, + /// Undocumented, could be resourcePath pub route_key: String, + /// The deployment stage of the API request (for example, Beta or Prod). pub stage: String, + /// Undocumented, could be requestTime pub time: String, + /// Undocumented, could be requestTimeEpoch pub time_epoch: usize, } +/// See [context-variable-reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for more detail. #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestContext { - //pub path: String, + /// The API owner's AWS account ID. pub account_id: String, + /// The identifier that API Gateway assigns to your resource. pub resource_id: String, + /// The deployment stage of the API request (for example, Beta or Prod). pub stage: String, + /// The ID that API Gateway assigns to the API request. pub request_id: String, + /// The path to your resource. For example, for the non-proxy request URI of `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, The $context.resourcePath value is /root/child. pub resource_path: String, + /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. pub http_method: String, + /// The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. #[serde(default)] pub authorizer: HashMap, + /// The identifier API Gateway assigns to your API. pub api_id: String, + /// Cofnito identity information pub identity: Identity, } +/// Elastic load balancer context information #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct AlbRequestContext { + /// Elastic load balancer context information pub elb: Elb, } @@ -166,10 +190,17 @@ pub struct Elb { #[serde(rename_all = "camelCase")] pub struct Http { #[serde(deserialize_with = "deserialize_method")] + /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. pub method: http::Method, + /// The request path. For example, for a non-proxy request URL of + /// `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, + /// the $context.path value is `/{stage}/root/child`. pub path: String, + /// The request protocol, for example, HTTP/1.1. pub protocol: String, + /// The source IP address of the TCP connection making the request to API Gateway. pub source_ip: String, + /// The User-Agent header of the API caller. pub user_agent: String, } @@ -177,17 +208,35 @@ pub struct Http { #[derive(Deserialize, Debug, Default, Clone)] #[serde(rename_all = "camelCase")] pub struct Identity { + /// The source IP address of the TCP connection making the request to API Gateway. pub source_ip: String, + /// The Amazon Cognito identity ID of the caller making the request. + /// Available only if the request was signed with Amazon Cognito credentials. pub cognito_identity_id: Option, + /// The Amazon Cognito identity pool ID of the caller making the request. + /// Available only if the request was signed with Amazon Cognito credentials. pub cognito_identity_pool_id: Option, + /// A comma-separated list of the Amazon Cognito authentication providers used by the caller making the request. + /// Available only if the request was signed with Amazon Cognito credentials. pub cognito_authentication_provider: Option, + /// The Amazon Cognito authentication type of the caller making the request. + /// Available only if the request was signed with Amazon Cognito credentials. pub cognito_authentication_type: Option, + /// The AWS account ID associated with the request. pub account_id: Option, + /// The principal identifier of the caller making the request. pub caller: Option, + /// For API methods that require an API key, this variable is the API key associated with the method request. + /// For methods that don't require an API key, this variable is null. pub api_key: Option, + /// Undocumented. Can be the API key ID associated with an API request that requires an API key. + /// The description of `api_key` and `access_key` may actually be reversed. pub access_key: Option, + /// The principal identifier of the user making the request. Used in Lambda authorizers. pub user: Option, + /// The User-Agent header of the API caller. pub user_agent: Option, + /// The Amazon Resource Name (ARN) of the effective user identified after authentication. pub user_arn: Option, } @@ -358,7 +407,7 @@ impl<'a> From> for http::Request { .expect("failed to build request"); // no builder method that sets headers in batch - mem::replace(req.headers_mut(), headers); + let _ = mem::replace(req.headers_mut(), headers); req } @@ -422,7 +471,7 @@ impl<'a> From> for http::Request { } // no builder method that sets headers in batch - mem::replace(req.headers_mut(), multi_value_headers); + let _ = mem::replace(req.headers_mut(), multi_value_headers); req } @@ -483,7 +532,7 @@ impl<'a> From> for http::Request { } // no builder method that sets headers in batch - mem::replace(req.headers_mut(), multi_value_headers); + let _ = mem::replace(req.headers_mut(), multi_value_headers); req } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 61406426..bdeee5c1 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -1,16 +1,14 @@ //! Response types +use crate::body::Body; use http::{ header::{HeaderMap, HeaderValue, CONTENT_TYPE}, Response, }; use serde::{ ser::{Error as SerError, SerializeMap}, - Serializer, + Serialize, Serializer, }; -use serde_derive::Serialize; - -use crate::body::Body; /// Representation of API Gateway response #[doc(hidden)] diff --git a/lambda-http/src/strmap.rs b/lambda-http/src/strmap.rs index 10801733..281a1101 100644 --- a/lambda-http/src/strmap.rs +++ b/lambda-http/src/strmap.rs @@ -1,14 +1,13 @@ +use serde::{ + de::{MapAccess, Visitor}, + Deserialize, Deserializer, +}; use std::{ collections::{hash_map::Keys, HashMap}, fmt, sync::Arc, }; -use serde::{ - de::{MapAccess, Visitor}, - Deserialize, Deserializer, -}; - /// A read-only view into a map of string data which may contain multiple values /// /// Internally data is always represented as many valued @@ -76,7 +75,7 @@ impl<'a> Iterator for StrMapIter<'a> { /// internal type used when deserializing StrMaps from /// potentially one or many valued maps -#[derive(serde_derive::Deserialize)] +#[derive(Deserialize)] #[serde(untagged)] enum OneOrMany { One(String), diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index b4d24894..37a30f26 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -2,9 +2,11 @@ name = "lambda" version = "0.1.0" authors = ["David Barsky "] -description = "AWS Lambda Runtime." +description = "AWS Lambda Runtime" edition = "2018" -license = "Apache-2.0" +license = "Apache License 2.0" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +readme = "../README.md" [features] default = ["simulated"] @@ -19,6 +21,15 @@ bytes = "1.0" http = "0.2" async-stream = "0.3" futures = "0.3" -tracing = "0.1.13" -tracing-futures = "0.2.3" tracing-error = "0.1.2" +tracing = { version = "0.1", features = ["log"] } +tracing-futures = "0.2" +tower-service = "0.3" +tokio-stream = "0.1.2" + +[dev-dependencies] +tracing-subscriber = "0.2" +once_cell = "1.4.0" +simple_logger = "1.6.0" +log = "0.4" +simple-error = "0.2" diff --git a/lambda/examples/README.md b/lambda/examples/README.md new file mode 100644 index 00000000..51a5a10b --- /dev/null +++ b/lambda/examples/README.md @@ -0,0 +1,254 @@ + +## How to compile and run the examples + +1. Create a Lambda function called _RuntimeTest_ in AWS with a custom runtime and no code. + +2. Compile all examples + +``` +cargo build --release --target x86_64-unknown-linux-musl --examples +``` +3. Prepare the package for the example you want to test, e.g. +``` +cp ./target/x86_64-unknown-linux-musl/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +``` +4. Upload the package to AWS Lambda +``` +aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip +``` +_Feel free to replace the names and IDs with your own values._ + +## basic.rs + +**Deployment**: +```bash +cp ./target/x86_64-unknown-linux-musl/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip +``` + +**Test event JSON (success)**: +```json +{ "command": "do something" } +``` + +Sample response: +```json +{ + "msg": "Command do something executed.", + "req_id": "67a038e4-dc19-4adf-aa32-5ba09312c6ca" +} +``` + +**Test event JSON (error)**: +```json +{ "foo": "do something" } +``` + +Sample response: +```json +{ + "errorType": "Runtime.ExitError", + "errorMessage": "RequestId: 586366df-f271-4e6e-9c30-b3dcab30f4e8 Error: Runtime exited with error: exit status 1" +} +``` +The runtime could not deserialize our invalid input, but it did not give a detailed explanation why the error occurred in the response. More details appear in the CloudWatch log: +``` +START RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Version: $LATEST +Error: Error("missing field `command`", line: 1, column: 22) +END RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f +REPORT RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Duration: 36.34 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 10 MB +RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Error: Runtime exited with error: exit status 1 +Runtime.ExitError +``` + + See _error-handling.rs_ example for more error handling options. + +## macro.rs + +The most basic example using `#[lambda]` macro to reduce the amount of boilerplate code. + +**Deployment**: +```bash +cp ./target/x86_64-unknown-linux-musl/release/examples/macro ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip +``` + +**Test event JSON**: +```json +{ "foo": "bar" } +``` + +Sample response: +```json +{ + "foo": "bar" +} +``` + +## error-handling.rs + +Errors are logged by the runtime only if `log` is initialized by the handler. +These examples use `simple_logger`, but you can use any other provider that works with `log`. +``` +simple_logger::init_with_level(log::Level::Debug)?; +``` + +**Deployment**: +```bash +cp ./target/x86_64-unknown-linux-musl/release/examples/error-handling ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip +``` + +The following input/output examples correspond to different `match` arms in the handler of `error-handling.rs`. + +#### Invalid event JSON + +Test input: +```json +{ + "event_type": "WrongType" +} +``` + +Lambda output: +``` +{ + "errorType": "alloc::boxed::Box", + "errorMessage": "unknown variant `WrongType`, expected one of `Response`, `ExternalError`, `SimpleError`, `CustomError`" +} +``` + +CloudWatch records: +``` +START RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba Version: $LATEST +2020-07-21 04:28:52,630 ERROR [lambda] unknown variant `WrongType`, expected one of `Response`, `ExternalError`, `SimpleError`, `CustomError` +END RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba +REPORT RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba Duration: 2.06 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 28 MB Init Duration: 33.67 ms +``` + +#### A simple text-only error + +Test event JSON: +```json +{ + "event_type": "SimpleError" +} +``` + +Lambda output: +``` +{ + "errorType": "alloc::boxed::Box", + "errorMessage": "A simple error as requested!" +} +``` + +CloudWatch records: +``` +START RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 Version: $LATEST +2020-07-21 04:35:28,044 ERROR [lambda] A simple error as requested! +END RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 +REPORT RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 Duration: 0.98 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 28 MB +``` + +#### A custom error with JSON output for Display trait + +Test event JSON: +```json +{ + "event_type": "CustomError" +} +``` + +Lambda output: +``` +{ + "errorType": "alloc::boxed::Box", + "errorMessage": "{\"is_authenticated\":false,\"msg\":\"A custom error as requested!\",\"req_id\":\"b46b0588-1383-4224-bc7a-42b0d61930c1\"}" +} +``` + +CloudWatch records: +``` +START RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 Version: $LATEST +2020-07-21 04:39:00,133 ERROR [lambda] {"is_authenticated":false,"msg":"A custom error as requested!","req_id":"b46b0588-1383-4224-bc7a-42b0d61930c1"} +END RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 +REPORT RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 Duration: 0.91 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB +``` + +#### A 3rd party error from _std::fs::File::open_ + +Test event JSON: +```json +{ + "event_type": "ExternalError" +} +``` + +Lambda output: +``` +{ + "errorType": "alloc::boxed::Box", + "errorMessage": "No such file or directory (os error 2)" +} +``` + +CloudWatch records: +``` +START RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da Version: $LATEST +2020-07-21 04:43:56,254 ERROR [lambda] No such file or directory (os error 2) +END RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da +REPORT RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da Duration: 1.15 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB +``` + +#### Handler panic + +Test event JSON: +```json +{ + "event_type": "Panic" +} +``` + +Lambda output: +``` +{ + "errorType": "Runtime.ExitError", + "errorMessage": "RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Error: Runtime exited with error: exit status 101" +} +``` + +CloudWatch records: +``` +START RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Version: $LATEST +thread 'main' panicked at 'explicit panic', lambda/examples/error-handling.rs:87:13 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +END RequestId: 2d579019-07f7-409a-a6e6-af7725253307 +REPORT RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Duration: 43.40 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 27 MB Init Duration: 23.15 ms +RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Error: Runtime exited with error: exit status 101 +Runtime.ExitError +``` + +#### A response to a successful Lambda execution + +Test event JSON: +```json +{ + "event_type": "Response" +} +``` + +Lambda output: +``` +{ + "msg": "OK", + "req_id": "9752a3ad-6566-44e4-aafd-74db1fd4f361" +} +``` + +CloudWatch records: +``` +START RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 Version: $LATEST +END RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 +REPORT RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 Duration: 0.89 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB +``` \ No newline at end of file diff --git a/lambda/examples/basic.rs b/lambda/examples/basic.rs new file mode 100644 index 00000000..7b492a44 --- /dev/null +++ b/lambda/examples/basic.rs @@ -0,0 +1,54 @@ +// This example requires the following input to succeed: +// { "command": "do something" } + +use lambda::{handler_fn, Context}; +use log::LevelFilter; +use serde::{Deserialize, Serialize}; +use simple_logger::SimpleLogger; + +/// A shorthand for `Box` +/// type required by aws-lambda-rust-runtime. +pub type Error = Box; + +/// This is also a made-up example. Requests come into the runtime as unicode +/// strings in json format, which can map to any structure that implements `serde::Deserialize` +/// The runtime pays no attention to the contents of the request payload. +#[derive(Deserialize)] +struct Request { + command: String, +} + +/// This is a made-up example of what a response structure may look like. +/// There is no restriction on what it can be. The runtime requires responses +/// to be serialized into json. The runtime pays no attention +/// to the contents of the response payload. +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + // can be replaced with any other method of initializing `log` + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + let func = handler_fn(my_handler); + lambda::run(func).await?; + Ok(()) +} + +pub(crate) async fn my_handler(event: Request, ctx: Context) -> Result { + // extract some useful info from the request + let command = event.command; + + // prepare the response + let resp = Response { + req_id: ctx.request_id, + msg: format!("Command {} executed.", command), + }; + + // return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(resp) +} diff --git a/lambda/examples/error-handling.rs b/lambda/examples/error-handling.rs new file mode 100644 index 00000000..1b061254 --- /dev/null +++ b/lambda/examples/error-handling.rs @@ -0,0 +1,117 @@ +/// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda +use lambda::handler_fn; +use log::LevelFilter; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use simple_logger::SimpleLogger; +use std::fs::File; + +/// A shorthand for `Box` type required by aws-lambda-rust-runtime. +pub type Error = Box; + +/// A simple Lambda request structure with just one field +/// that tells the Lambda what is expected of it. +#[derive(Deserialize)] +struct Request { + event_type: EventType, +} + +/// Event types that tell our Lambda what to do do. +#[derive(Deserialize, PartialEq)] +enum EventType { + Response, + ExternalError, + SimpleError, + CustomError, + Panic, +} + +/// A simple Lambda response structure. +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +#[derive(Debug, Serialize)] +struct CustomError { + is_authenticated: bool, + req_id: String, + msg: String, +} + +impl std::error::Error for CustomError { + // this implementation required `Debug` and `Display` traits +} + +impl std::fmt::Display for CustomError { + /// Display the error struct as a JSON string + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let err_as_json = json!(self).to_string(); + write!(f, "{}", err_as_json) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `log` with `simple_logger` + // or another compatible crate. The runtime is using `tracing` internally. + // You can comment out the `simple_logger` init line and uncomment the following block to + // use `tracing` in the handler function. + // + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + /* + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + */ + + // call the actual handler of the request + let func = handler_fn(func); + lambda::run(func).await?; + Ok(()) +} + +/// The actual handler of the Lambda request. +pub(crate) async fn func(event: Value, ctx: lambda::Context) -> Result { + // check what action was requested + match serde_json::from_value::(event)?.event_type { + EventType::SimpleError => { + // generate a simple text message error using `simple_error` crate + return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))); + } + EventType::CustomError => { + // generate a custom error using our own structure + let cust_err = CustomError { + is_authenticated: ctx.identity.is_some(), + req_id: ctx.request_id, + msg: "A custom error as requested!".into(), + }; + return Err(Box::new(cust_err)); + } + EventType::ExternalError => { + // try to open a non-existent file to get an error and propagate it with `?` + let _file = File::open("non-existent-file.txt")?; + + // it should never execute past the above line + unreachable!(); + } + EventType::Panic => { + panic!(); + } + EventType::Response => { + // generate and return an OK response in JSON format + let resp = Response { + req_id: ctx.request_id, + msg: "OK".into(), + }; + + return Ok(json!(resp)); + } + } +} diff --git a/lambda/examples/hello.rs b/lambda/examples/hello.rs deleted file mode 100644 index d94ee322..00000000 --- a/lambda/examples/hello.rs +++ /dev/null @@ -1,17 +0,0 @@ -use lambda::{handler_fn, Context}; -use serde_json::{json, Value}; - -type Error = Box; - -#[tokio::main] -async fn main() -> Result<(), Error> { - let func = handler_fn(func); - lambda::run(func).await?; - Ok(()) -} - -async fn func(event: Value, _: Context) -> Result { - let first_name = event["firstName"].as_str().unwrap_or("world"); - - Ok(json!({ "message": format!("Hello, {}!", first_name) })) -} diff --git a/lambda/src/client.rs b/lambda/src/client.rs index e1f667dd..b24b2e78 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -1,77 +1,20 @@ -use crate::{ - requests::{IntoResponse, NextEventResponse}, - Error, -}; -use http::{ - uri::{PathAndQuery, Scheme}, - HeaderValue, Method, Request, Response, StatusCode, Uri, -}; -use hyper::{client::HttpConnector, server::conn::Http, service::service_fn, Body}; -use serde_json::json; -use std::convert::TryFrom; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - select, - sync::oneshot, -}; -use tracing::{error, info, instrument}; - -#[instrument] -async fn hello(req: Request) -> Result, Error> { - Ok(Response::new(Body::from("hello"))) -} - -async fn handle_incoming(req: Request) -> Result, Error> { - let path: Vec<&str> = req - .uri() - .path_and_query() - .unwrap() - .as_str() - .split("/") - .collect::>(); - match &path[1..] { - ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, - ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, - ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, - ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), - _ => unimplemented!(), - } -} - -#[instrument(skip(io, rx))] -async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> -where - I: AsyncRead + AsyncWrite + Unpin + 'static, -{ - let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); - select! { - _ = rx => { - info!("Received cancelation signal"); - return Ok(()) - } - res = conn => { - match res { - Ok(()) => return Ok(()), - Err(e) => { - error!(message = "Got error serving connection", e = %e); - return Err(e); - } - } - } - } -} +use crate::Error; +use http::{uri::Scheme, Request, Response, Uri}; +use hyper::{client::HttpConnector, Body}; +use std::fmt::Debug; #[derive(Debug)] pub(crate) struct Client { - base: Uri, - client: hyper::Client, + pub(crate) base: Uri, + pub(crate) client: hyper::Client, } impl Client where C: hyper::client::connect::Connect + Sync + Send + Clone + 'static, { - pub fn with(base: Uri, client: hyper::Client) -> Self { + pub fn with(base: Uri, connector: C) -> Self { + let client = hyper::Client::builder().build(connector); Self { base, client } } @@ -98,104 +41,134 @@ where pub(crate) async fn call(&self, req: Request) -> Result, Error> { let req = self.set_origin(req)?; let (parts, body) = req.into_parts(); - let body = Body::from(body); let req = Request::from_parts(parts, body); let response = self.client.request(req).await?; Ok(response) } } -async fn next_event(req: &Request) -> Result, Error> { - let path = "/2018-06-01/runtime/invocation/next"; - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); - let body = json!({"message": "hello"}); - - let rsp = NextEventResponse { - request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", - deadline: 1_542_409_706_888, - arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", - trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", - body: serde_json::to_vec(&body)?, +#[cfg(test)] +mod endpoint_tests { + use crate::{ + client::Client, + incoming, + requests::{ + EventCompletionRequest, EventErrorRequest, IntoRequest, IntoResponse, NextEventRequest, NextEventResponse, + }, + simulated, + types::Diagnostic, + Error, Runtime, }; - rsp.into_rsp().map_err(|e| e.into()) -} - -async fn complete_event(req: &Request, id: &str) -> Result, Error> { - assert_eq!(Method::POST, req.method()); - let rsp = Response::builder() - .status(StatusCode::ACCEPTED) - .body(Body::empty()) - .expect("Unable to construct response"); + use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; + use hyper::{server::conn::Http, service::service_fn, Body}; + use serde_json::json; + use std::convert::TryFrom; + use tokio::{ + io::{AsyncRead, AsyncWrite}, + select, + sync::{self, oneshot}, + }; + use tokio_stream::StreamExt; + + #[cfg(test)] + async fn next_event(req: &Request) -> Result, Error> { + let path = "/2018-06-01/runtime/invocation/next"; + assert_eq!(req.method(), Method::GET); + assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); + let body = json!({"message": "hello"}); + + let rsp = NextEventResponse { + request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", + deadline: 1_542_409_706_888, + arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", + trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", + body: serde_json::to_vec(&body)?, + }; + rsp.into_rsp() + } - let expected = format!("/2018-06-01/runtime/invocation/{}/response", id); - assert_eq!(expected, req.uri().path()); + #[cfg(test)] + async fn complete_event(req: &Request, id: &str) -> Result, Error> { + assert_eq!(Method::POST, req.method()); + let rsp = Response::builder() + .status(StatusCode::ACCEPTED) + .body(Body::empty()) + .expect("Unable to construct response"); - Ok(rsp) -} + let expected = format!("/2018-06-01/runtime/invocation/{}/response", id); + assert_eq!(expected, req.uri().path()); -async fn event_err(req: &Request, id: &str) -> Result, Error> { - let expected = format!("/2018-06-01/runtime/invocation/{}/error", id); - assert_eq!(expected, req.uri().path()); + Ok(rsp) + } - assert_eq!(req.method(), Method::POST); - let header = "lambda-runtime-function-error-type"; - let expected = "unhandled"; - assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); + #[cfg(test)] + async fn event_err(req: &Request, id: &str) -> Result, Error> { + let expected = format!("/2018-06-01/runtime/invocation/{}/error", id); + assert_eq!(expected, req.uri().path()); - let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; - Ok(rsp) -} + assert_eq!(req.method(), Method::POST); + let header = "lambda-runtime-function-error-type"; + let expected = "unhandled"; + assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); -fn set_origin(base: Uri, req: Request) -> Result, Error> { - let (mut parts, body) = req.into_parts(); - let (scheme, authority) = { - let scheme = base.scheme().unwrap_or(&Scheme::HTTP); - let authority = base.authority().expect("Authority not found"); - (scheme, authority) - }; - let path = parts.uri.path_and_query().expect("PathAndQuery not found"); - - let uri = Uri::builder() - .scheme(scheme.clone()) - .authority(authority.clone()) - .path_and_query(path.clone()) - .build() - .expect("Unable to build URI"); + let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; + Ok(rsp) + } - parts.uri = uri; - Ok(Request::from_parts(parts, body)) -} + #[cfg(test)] + async fn handle_incoming(req: Request) -> Result, Error> { + let path: Vec<&str> = req + .uri() + .path_and_query() + .expect("PathAndQuery not found") + .as_str() + .split('/') + .collect::>(); + match path[1..] { + ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, + ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, + ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, + ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), + _ => unimplemented!(), + } + } -#[cfg(test)] -mod endpoint_tests { - use super::{handle, set_origin}; - use crate::{ - requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, - simulated::SimulatedConnector, - types::Diagnostic, - Error, - }; - use http::{HeaderValue, StatusCode, Uri}; - use std::convert::TryFrom; - use tokio::sync; + #[cfg(test)] + async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> + where + I: AsyncRead + AsyncWrite + Unpin + 'static, + { + let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); + select! { + _ = rx => { + Ok(()) + } + res = conn => { + match res { + Ok(()) => Ok(()), + Err(e) => { + Err(e) + } + } + } + } + } #[tokio::test] - async fn next_event() -> Result<(), Error> { - let (client, server) = crate::simulated::chan(); + async fn test_next_event() -> Result<(), Error> { let base = Uri::from_static("http://localhost:9001"); + let (client, server) = crate::simulated::chan(); let (tx, rx) = sync::oneshot::channel(); let server = tokio::spawn(async { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = SimulatedConnector { inner: client }; - let client = hyper::Client::builder().build(conn); + let conn = simulated::Connector { inner: client }; + let client = Client::with(base, conn); let req = NextEventRequest.into_req()?; - let req = set_origin(base, req)?; - let rsp = client.request(req).await.expect("Unable to send request"); + let rsp = client.call(req).await.expect("Unable to send request"); assert_eq!(rsp.status(), StatusCode::OK); let header = "lambda-runtime-deadline-ms"; @@ -211,7 +184,7 @@ mod endpoint_tests { } #[tokio::test] - async fn ok_response() -> Result<(), Error> { + async fn test_ok_response() -> Result<(), Error> { let (client, server) = crate::simulated::chan(); let (tx, rx) = sync::oneshot::channel(); let base = Uri::from_static("http://localhost:9001"); @@ -220,17 +193,16 @@ mod endpoint_tests { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = SimulatedConnector { inner: client }; - let client = hyper::Client::builder().build(conn); + let conn = simulated::Connector { inner: client }; + let client = Client::with(base, conn); let req = EventCompletionRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", body: "done", }; let req = req.into_req()?; - let req = set_origin(base, req)?; - let rsp = client.request(req).await?; + let rsp = client.call(req).await?; assert_eq!(rsp.status(), StatusCode::ACCEPTED); // shutdown server @@ -243,7 +215,7 @@ mod endpoint_tests { } #[tokio::test] - async fn error_response() -> Result<(), Error> { + async fn test_error_response() -> Result<(), Error> { let (client, server) = crate::simulated::chan(); let (tx, rx) = sync::oneshot::channel(); let base = Uri::from_static("http://localhost:9001"); @@ -252,8 +224,8 @@ mod endpoint_tests { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = SimulatedConnector { inner: client }; - let client = hyper::Client::builder().build(conn); + let conn = simulated::Connector { inner: client }; + let client = Client::with(base, conn); let req = EventErrorRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", @@ -263,8 +235,7 @@ mod endpoint_tests { }, }; let req = req.into_req()?; - let req = set_origin(base, req)?; - let rsp = client.request(req).await?; + let rsp = client.call(req).await?; assert_eq!(rsp.status(), StatusCode::ACCEPTED); // shutdown server @@ -276,54 +247,38 @@ mod endpoint_tests { } } - // #[tokio::test] - // async fn run_end_to_end() -> Result<(), Error> { - // use serde_json::Value; - - // let (client, server) = crate::simulated::chan(); - - // let (tx, rx) = sync::oneshot::channel(); - // let server = tokio::spawn(async move { handle(server, rx) }); - - // async fn handler(s: Value) -> Result { - // INVOCATION_CTX.with(|_ctx| {}); - // Ok(s) - // } - // let handler = handler_fn(handler); - // let client = tokio::spawn(async move { - // run_simulated(handler, &url).await?; - // Ok::<(), Error>(()) - // }); - // race!(client, server); - // Ok(()) - // } - - // #[tokio::test] - // async fn test_stream_handler() -> Result<(), Error> { - // let (client, server) = crate::simulated::chan(); - // let req = Request::builder() - // .method(Method::GET) - // .uri("http://httpbin.org") - // .body(Body::empty()) - // .expect("Can't build request"); - - // let conn = SimulatedConnector { inner: client }; - // let client = hyper::Client::builder().build(conn); - - // let (tx, rx) = sync::oneshot::channel(); - // let server = tokio::spawn(async { - // handle(server, rx).await.expect("Unable to handle request"); - // }); - - // let rsp = client.request(req).await.expect("Unable to send request"); - // assert_eq!(rsp.status(), http::StatusCode::OK); - - // // shutdown server - // tx.send(()).expect("Receiver has been dropped"); - // match server.await { - // Ok(_) => Ok(()), - // Err(e) if e.is_panic() => return Err::<(), anyhow::Error>(e.into()), - // Err(_) => unreachable!("This branch shouldn't be reachable"), - // } - // } + #[tokio::test] + async fn successful_end_to_end_run() -> Result<(), Error> { + let (client, server) = crate::simulated::chan(); + let (tx, rx) = sync::oneshot::channel(); + let base = Uri::from_static("http://localhost:9001"); + + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + let conn = simulated::Connector { inner: client }; + + let runtime = Runtime::builder() + .with_endpoint(base) + .with_connector(conn) + .build() + .expect("Unable to build runtime"); + + async fn func(event: serde_json::Value, _: crate::Context) -> Result { + Ok(event) + } + let f = crate::handler_fn(func); + + let client = &runtime.client; + let incoming = incoming(client).take(1); + runtime.run(incoming, f).await?; + + // shutdown server + tx.send(()).expect("Receiver has been dropped"); + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 6f420f56..44077f0e 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -1,24 +1,28 @@ -#![deny(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] +#![deny(clippy::all, clippy::cargo)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] -//! The official Rust runtime for AWS Lambda. -//! //! The mechanism available for defining a Lambda function is as follows: //! //! Create a type that conforms to the [`Handler`] trait. This type can then be passed //! to the the `lambda::run` function, which launches and runs the Lambda runtime. pub use crate::types::Context; use client::Client; -use futures::stream::{Stream, StreamExt}; +use hyper::client::{connect::Connection, HttpConnector}; use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, env, fmt, future::Future, + sync::Arc, }; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_stream::{Stream, StreamExt}; +use tower_service::Service; +use tracing::{error, trace}; mod client; mod requests; +#[cfg(test)] mod simulated; /// Types available to a Lambda function. mod types; @@ -61,22 +65,26 @@ impl Config { } } -/// A trait describing an asynchronous function `A` to `B. +/// A trait describing an asynchronous function `A` to `B`. pub trait Handler { /// Errors returned by this handler. type Error; - /// The future response value of this handler. + /// Response of this handler. type Fut: Future>; - /// Process the incoming event and `Context` then return the response asynchronously. - fn call(&mut self, event: A, context: Context) -> Self::Fut; + /// Handle the incoming event. + fn call(&self, event: A, context: Context) -> Self::Fut; } -/// Returns a new `HandlerFn` with the given closure. +/// Returns a new [`HandlerFn`] with the given closure. +/// +/// [`HandlerFn`]: struct.HandlerFn.html pub fn handler_fn(f: F) -> HandlerFn { HandlerFn { f } } -/// A `Handler` implemented by a closure. +/// A [`Handler`] implemented by a closure. +/// +/// [`Handler`]: trait.Handler.html #[derive(Clone, Debug)] pub struct HandlerFn { f: F, @@ -86,15 +94,179 @@ impl Handler for HandlerFn where F: Fn(A, Context) -> Fut, Fut: Future> + Send, - Error: Into + fmt::Debug, + Error: Into> + fmt::Display, { type Error = Error; type Fut = Fut; - fn call(&mut self, req: A, ctx: Context) -> Self::Fut { + fn call(&self, req: A, ctx: Context) -> Self::Fut { (self.f)(req, ctx) } } +#[non_exhaustive] +#[derive(Debug, PartialEq)] +enum BuilderError { + UnsetUri, +} + +struct Runtime = HttpConnector> { + client: Client, +} + +impl Runtime { + pub fn builder() -> RuntimeBuilder { + RuntimeBuilder { + connector: HttpConnector::new(), + uri: None, + } + } +} + +impl Runtime +where + C: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + pub async fn run( + &self, + incoming: impl Stream, Error>> + Send, + handler: F, + ) -> Result<(), Error> + where + F: Handler + Send + Sync + 'static, + >::Fut: Future>::Error>> + Send + 'static, + >::Error: fmt::Display + Send + Sync + 'static, + A: for<'de> Deserialize<'de> + Send + Sync + 'static, + B: Serialize + Send + Sync + 'static, + { + let client = &self.client; + let handler = Arc::new(handler); + tokio::pin!(incoming); + while let Some(event) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = event?; + let (parts, body) = event.into_parts(); + + let ctx: Context = Context::try_from(parts.headers)?; + let body = hyper::body::to_bytes(body).await?; + trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose + let body = serde_json::from_slice(&body)?; + + let handler = Arc::clone(&handler); + let request_id = &ctx.request_id.clone(); + let task = tokio::spawn(async move { handler.call(body, ctx) }); + + let req = match task.await { + Ok(response) => match response.await { + Ok(response) => { + trace!("Ok response from handler (run loop)"); + EventCompletionRequest { + request_id, + body: response, + } + .into_req() + } + Err(err) => { + error!("{}", err); // logs the error in CloudWatch + EventErrorRequest { + request_id, + diagnostic: Diagnostic { + error_type: type_name_of_val(&err).to_owned(), + error_message: format!("{}", err), // returns the error to the caller via Lambda API + }, + } + .into_req() + } + }, + Err(err) if err.is_panic() => { + error!("{:?}", err); // inconsistent with other log record formats - to be reviewed + EventErrorRequest { + request_id, + diagnostic: Diagnostic { + error_type: type_name_of_val(&err).to_owned(), + error_message: format!("Lambda panicked: {}", err), + }, + } + .into_req() + } + Err(_) => unreachable!("tokio::task should not be canceled"), + }; + let req = req?; + client.call(req).await.expect("Unable to send response to Runtime APIs"); + } + Ok(()) + } +} + +struct RuntimeBuilder = hyper::client::HttpConnector> { + connector: C, + uri: Option, +} + +impl RuntimeBuilder +where + C: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + pub fn with_connector(self, connector: C2) -> RuntimeBuilder + where + C2: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + { + RuntimeBuilder { + connector, + uri: self.uri, + } + } + + pub fn with_endpoint(self, uri: http::Uri) -> Self { + Self { uri: Some(uri), ..self } + } + + pub fn build(self) -> Result, BuilderError> { + let uri = match self.uri { + Some(uri) => uri, + None => return Err(BuilderError::UnsetUri), + }; + let client = Client::with(uri, self.connector); + + Ok(Runtime { client }) + } +} + +#[test] +fn test_builder() { + let runtime = Runtime::builder() + .with_connector(HttpConnector::new()) + .with_endpoint(http::Uri::from_static("http://nomatter.com")) + .build(); + + runtime.unwrap(); +} + +fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ +where + C: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + async_stream::stream! { + loop { + trace!("Waiting for next event (incoming loop)"); + let req = NextEventRequest.into_req().expect("Unable to construct request"); + let res = client.call(req).await; + yield res; + } + } +} + /// Starts the Lambda Rust runtime and begins polling for events on the [Lambda /// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). /// @@ -118,88 +290,24 @@ where /// ``` pub async fn run(handler: F) -> Result<(), Error> where - F: Handler, - >::Error: fmt::Debug, - A: for<'de> Deserialize<'de>, - B: Serialize, + F: Handler + Send + Sync + 'static, + >::Fut: Future>::Error>> + Send + 'static, + >::Error: fmt::Display + Send + Sync + 'static, + A: for<'de> Deserialize<'de> + Send + Sync + 'static, + B: Serialize + Send + Sync + 'static, { - let mut handler = handler; + trace!("Loading config from env"); let config = Config::from_env()?; let uri = config.endpoint.try_into().expect("Unable to convert to URL"); - let client = Client::with(uri, hyper::Client::new()); - let incoming = incoming(&client); - run_inner(&client, incoming, &mut handler).await?; - - Ok(()) -} - -/// Runs the lambda function almost entirely in-memory. This is meant for testing. -pub async fn run_simulated(handler: F, url: &str) -> Result<(), Error> -where - F: Handler, - >::Error: fmt::Debug, - A: for<'de> Deserialize<'de>, - B: Serialize, -{ - let mut handler = handler; - let uri = url.try_into().expect("Unable to convert to URL"); - let client = Client::with(uri, hyper::Client::new()); - let incoming = incoming(&client).take(1); - run_inner(&client, incoming, &mut handler).await?; - - Ok(()) -} - -fn incoming(client: &Client) -> impl Stream, Error>> + '_ { - async_stream::stream! { - loop { - let req = NextEventRequest.into_req().expect("Unable to construct request"); - let res = client.call(req).await; - yield res; - } - } -} - -async fn run_inner( - client: &Client, - incoming: impl Stream, Error>>, - handler: &mut F, -) -> Result<(), Error> -where - F: Handler, - >::Error: fmt::Debug, - A: for<'de> Deserialize<'de>, - B: Serialize, -{ - tokio::pin!(incoming); - - while let Some(event) = incoming.next().await { - let event = event?; - let (parts, body) = event.into_parts(); - - let mut ctx: Context = Context::try_from(parts.headers)?; - ctx.env_config = Config::from_env()?; - let body = hyper::body::to_bytes(body).await?; - let body = serde_json::from_slice(&body)?; - - let request_id = &ctx.request_id.clone(); - let f = handler.call(body, ctx); - - let req = match f.await { - Ok(res) => EventCompletionRequest { request_id, body: res }.into_req()?, - Err(e) => EventErrorRequest { - request_id, - diagnostic: Diagnostic { - error_message: format!("{:?}", e), - error_type: type_name_of_val(e).to_owned(), - }, - } - .into_req()?, - }; - client.call(req).await?; - } + let runtime = Runtime::builder() + .with_connector(HttpConnector::new()) + .with_endpoint(uri) + .build() + .expect("Unable create runtime"); - Ok(()) + let client = &runtime.client; + let incoming = incoming(client); + runtime.run(incoming, handler).await } fn type_name_of_val(_: T) -> &'static str { diff --git a/lambda/src/requests.rs b/lambda/src/requests.rs index 2d691b94..a89f1689 100644 --- a/lambda/src/requests.rs +++ b/lambda/src/requests.rs @@ -134,7 +134,7 @@ struct InitErrorRequest; impl IntoRequest for InitErrorRequest { fn into_req(self) -> Result, Error> { - let uri = format!("/2018-06-01/runtime/init/error"); + let uri = "/2018-06-01/runtime/init/error".to_string(); let uri = Uri::from_str(&uri)?; let req = Request::builder() diff --git a/lambda/src/simulated.rs b/lambda/src/simulated.rs index 843a2842..7d1931c3 100644 --- a/lambda/src/simulated.rs +++ b/lambda/src/simulated.rs @@ -11,7 +11,7 @@ use std::{ }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -/// Creates a pair of AsyncReadWrite data streams, where the write end of each member of the pair +/// Creates a pair of `AsyncRead`/`AsyncWrite` data streams, where the write end of each member of the pair /// is the read end of the other member of the pair. This allows us to emulate the behavior of a TcpStream /// but in-memory, deterministically, and with full control over failure injection. pub(crate) fn chan() -> (SimStream, SimStream) { @@ -36,11 +36,11 @@ pub(crate) fn chan() -> (SimStream, SimStream) { } #[derive(Clone)] -pub struct SimulatedConnector { +pub struct Connector { pub inner: SimStream, } -impl hyper::service::Service for SimulatedConnector { +impl hyper::service::Service for Connector { type Response = SimStream; type Error = std::io::Error; type Future = Pin> + Send>>; @@ -103,6 +103,7 @@ pub struct BufferState { } impl BufferState { + /// Creates a new `BufferState`. fn new() -> Self { BufferState { buffer: VecDeque::new(), @@ -207,7 +208,7 @@ mod tests { client.write_all(b"Ping").await.expect("Write should succeed"); // Verify we can read it from side 2 - let mut read_on_server = [0u8; 4]; + let mut read_on_server = [0_u8; 4]; server .read_exact(&mut read_on_server) .await @@ -218,7 +219,7 @@ mod tests { server.write_all(b"Pong").await.expect("Write should succeed"); // Verify we can read it from side 1 - let mut read_on_client = [0u8; 4]; + let mut read_on_client = [0_u8; 4]; client .read_exact(&mut read_on_client) .await From 6033ce3b8fdecded509716908199528292925182 Mon Sep 17 00:00:00 2001 From: Richard H Boyd Date: Fri, 5 Feb 2021 21:02:58 -0500 Subject: [PATCH 023/394] point to basic.rs instead of hello (#285) using the parameter `hello` results in the following error. ```text error: no example target named `hello` ``` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32385540..e624a692 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ $ echo $'[target.x86_64-unknown-linux-musl]\nlinker = "x86_64-linux-musl-gcc"' > Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: ```bash -$ cargo build -p lambda --example hello --release --target x86_64-unknown-linux-musl +$ cargo build -p lambda --example basic --release --target x86_64-unknown-linux-musl ``` For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. From 5556ab270e6aa4b063fa3313f3698a6713c69eec Mon Sep 17 00:00:00 2001 From: Leo Toikka Date: Sun, 7 Feb 2021 18:19:27 +0200 Subject: [PATCH 024/394] Lambda-http: vary type of response based on request origin (#269) * Lambda-http: vary type of response based on request origin ApiGatewayV2, ApiGateway and Alb all expect different types of responses to be returned from the invoked lambda function. Thus, it makes sense to pass the request origin to the creation of the response, so that the correct type of LambdaResponse is returned from the function. This commit also adds support for the "cookies" attribute which can be used for returning multiple Set-cookie headers from a lambda invoked via ApiGatewayV2, since ApiGatewayV2 no longer seems to recognize the "multiValueHeaders" attribute. Closes: #267. * Fix Serialize import * Fix missing reference on self * Fix import order * Add missing comma for fmt check Co-authored-by: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> --- lambda-http/src/lib.rs | 18 +-- lambda-http/src/request.rs | 29 +++-- lambda-http/src/response.rs | 212 +++++++++++++++++++++++++++--------- 3 files changed, 193 insertions(+), 66 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 7ee4927e..91c8cd0d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -75,7 +75,10 @@ pub mod request; mod response; mod strmap; pub use crate::{body::Body, ext::RequestExt, response::IntoResponse, strmap::StrMap}; -use crate::{request::LambdaRequest, response::LambdaResponse}; +use crate::{ + request::{LambdaRequest, RequestOrigin}, + response::LambdaResponse, +}; use std::{ future::Future, pin::Pin, @@ -124,7 +127,7 @@ where #[doc(hidden)] pub struct TransformResponse { - is_alb: bool, + request_origin: RequestOrigin, fut: Pin> + Send + Sync>>, } @@ -135,9 +138,9 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll { match self.fut.as_mut().poll(cx) { - Poll::Ready(result) => { - Poll::Ready(result.map(|resp| LambdaResponse::from_response(self.is_alb, resp.into_response()))) - } + Poll::Ready(result) => Poll::Ready( + result.map(|resp| LambdaResponse::from_response(&self.request_origin, resp.into_response())), + ), Poll::Pending => Poll::Pending, } } @@ -166,9 +169,10 @@ impl Handler for Adapter { impl LambdaHandler, LambdaResponse> for Adapter { type Error = H::Error; type Fut = TransformResponse; + fn call(&self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { - let is_alb = event.is_alb(); + let request_origin = event.request_origin(); let fut = Box::pin(self.handler.call(event.into(), context)); - TransformResponse { is_alb, fut } + TransformResponse { request_origin, fut } } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 3444209f..352ba780 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -91,17 +91,30 @@ pub enum LambdaRequest<'a> { } impl LambdaRequest<'_> { - /// Return true if this request represents an ALB event - /// - /// Alb responses have unique requirements for responses that - /// vary only slightly from APIGateway responses. We serialize - /// responses capturing a hint that the request was an alb triggered - /// event. - pub fn is_alb(&self) -> bool { - matches!(self, LambdaRequest::Alb { .. }) + /// Return the `RequestOrigin` of the request to determine where the `LambdaRequest` + /// originated from, so that the appropriate response can be selected based on what + /// type of response the request origin expects. + pub fn request_origin(&self) -> RequestOrigin { + match self { + LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2, + LambdaRequest::Alb { .. } => RequestOrigin::Alb, + LambdaRequest::ApiGateway { .. } => RequestOrigin::ApiGateway, + } } } +/// Represents the origin from which the lambda was requested from. +#[doc(hidden)] +#[derive(Debug)] +pub enum RequestOrigin { + /// API Gateway v2 request origin + ApiGatewayV2, + /// API Gateway request origin + ApiGateway, + /// ALB request origin + Alb, +} + /// See [context-variable-reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for more detail. #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index bdeee5c1..8c3e2a1d 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -1,47 +1,67 @@ //! Response types -use crate::body::Body; +use crate::{body::Body, request::RequestOrigin}; use http::{ - header::{HeaderMap, HeaderValue, CONTENT_TYPE}, + header::{HeaderMap, HeaderValue, CONTENT_TYPE, SET_COOKIE}, Response, }; use serde::{ - ser::{Error as SerError, SerializeMap}, + ser::{Error as SerError, SerializeMap, SerializeSeq}, Serialize, Serializer, }; -/// Representation of API Gateway response +/// Representation of Lambda response +#[doc(hidden)] +#[derive(Serialize, Debug)] +#[serde(untagged)] +pub enum LambdaResponse { + ApiGatewayV2(ApiGatewayV2Response), + Alb(AlbResponse), + ApiGateway(ApiGatewayResponse), +} + +/// Representation of API Gateway v2 lambda response #[doc(hidden)] #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct LambdaResponse { - pub status_code: u16, - // ALB requires a statusDescription i.e. "200 OK" field but API Gateway returns an error - // when one is provided. only populate this for ALB responses +pub struct ApiGatewayV2Response { + status_code: u16, + #[serde(serialize_with = "serialize_headers")] + headers: HeaderMap, + #[serde(serialize_with = "serialize_headers_slice")] + cookies: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub status_description: Option, + body: Option, + is_base64_encoded: bool, +} + +/// Representation of ALB lambda response +#[doc(hidden)] +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AlbResponse { + status_code: u16, + status_description: String, #[serde(serialize_with = "serialize_headers")] - pub headers: HeaderMap, - #[serde(serialize_with = "serialize_multi_value_headers")] - pub multi_value_headers: HeaderMap, + headers: HeaderMap, #[serde(skip_serializing_if = "Option::is_none")] - pub body: Option, - // This field is optional for API Gateway but required for ALB - pub is_base64_encoded: bool, + body: Option, + is_base64_encoded: bool, } -#[cfg(test)] -impl Default for LambdaResponse { - fn default() -> Self { - Self { - status_code: 200, - status_description: Default::default(), - headers: Default::default(), - multi_value_headers: Default::default(), - body: Default::default(), - is_base64_encoded: Default::default(), - } - } +/// Representation of API Gateway lambda response +#[doc(hidden)] +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayResponse { + status_code: u16, + #[serde(serialize_with = "serialize_headers")] + headers: HeaderMap, + #[serde(serialize_with = "serialize_multi_value_headers")] + multi_value_headers: HeaderMap, + #[serde(skip_serializing_if = "Option::is_none")] + body: Option, + is_base64_encoded: bool, } /// Serialize a http::HeaderMap into a serde str => str map @@ -73,9 +93,21 @@ where map.end() } +/// Serialize a &[HeaderValue] into a Vec +fn serialize_headers_slice(headers: &[HeaderValue], serializer: S) -> Result +where + S: Serializer, +{ + let mut seq = serializer.serialize_seq(Some(headers.len()))?; + for header in headers { + seq.serialize_element(header.to_str().map_err(S::Error::custom)?)?; + } + seq.end() +} + /// tranformation from http type to internal type impl LambdaResponse { - pub(crate) fn from_response(is_alb: bool, value: Response) -> Self + pub(crate) fn from_response(request_origin: &RequestOrigin, value: Response) -> Self where T: Into, { @@ -85,21 +117,43 @@ impl LambdaResponse { b @ Body::Text(_) => (false, Some(b)), b @ Body::Binary(_) => (true, Some(b)), }; - Self { - status_code: parts.status.as_u16(), - status_description: if is_alb { - Some(format!( + + let mut headers = parts.headers; + let status_code = parts.status.as_u16(); + + match request_origin { + RequestOrigin::ApiGatewayV2 => { + // ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute, + // so remove them from the headers. + let cookies: Vec = headers.get_all(SET_COOKIE).iter().cloned().collect(); + headers.remove(SET_COOKIE); + + LambdaResponse::ApiGatewayV2(ApiGatewayV2Response { + body, + status_code, + is_base64_encoded, + cookies, + headers, + }) + } + RequestOrigin::ApiGateway => LambdaResponse::ApiGateway(ApiGatewayResponse { + body, + status_code, + is_base64_encoded, + headers: headers.clone(), + multi_value_headers: headers, + }), + RequestOrigin::Alb => LambdaResponse::Alb(AlbResponse { + body, + status_code, + is_base64_encoded, + headers, + status_description: format!( "{} {}", - parts.status.as_u16(), + status_code, parts.status.canonical_reason().unwrap_or_default() - )) - } else { - None - }, - body, - headers: parts.headers.clone(), - multi_value_headers: parts.headers, - is_base64_encoded, + ), + }), } } } @@ -159,10 +213,42 @@ impl IntoResponse for serde_json::Value { #[cfg(test)] mod tests { - use super::{Body, IntoResponse, LambdaResponse}; + use super::{ + AlbResponse, ApiGatewayResponse, ApiGatewayV2Response, Body, IntoResponse, LambdaResponse, RequestOrigin, + }; use http::{header::CONTENT_TYPE, Response}; use serde_json::{self, json}; + fn api_gateway_response() -> ApiGatewayResponse { + ApiGatewayResponse { + status_code: 200, + headers: Default::default(), + multi_value_headers: Default::default(), + body: Default::default(), + is_base64_encoded: Default::default(), + } + } + + fn alb_response() -> AlbResponse { + AlbResponse { + status_code: 200, + status_description: "200 OK".to_string(), + headers: Default::default(), + body: Default::default(), + is_base64_encoded: Default::default(), + } + } + + fn api_gateway_v2_response() -> ApiGatewayV2Response { + ApiGatewayV2Response { + status_code: 200, + headers: Default::default(), + body: Default::default(), + cookies: Default::default(), + is_base64_encoded: Default::default(), + } + } + #[test] fn json_into_response() { let response = json!({ "hello": "lambda"}).into_response(); @@ -189,32 +275,39 @@ mod tests { } #[test] - fn default_response() { - assert_eq!(LambdaResponse::default().status_code, 200) + fn serialize_body_for_api_gateway() { + let mut resp = api_gateway_response(); + resp.body = Some("foo".into()); + assert_eq!( + serde_json::to_string(&resp).expect("failed to serialize response"), + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"foo","isBase64Encoded":false}"# + ); } #[test] - fn serialize_default() { + fn serialize_body_for_alb() { + let mut resp = alb_response(); + resp.body = Some("foo".into()); assert_eq!( - serde_json::to_string(&LambdaResponse::default()).expect("failed to serialize response"), - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"isBase64Encoded":false}"# + serde_json::to_string(&resp).expect("failed to serialize response"), + r#"{"statusCode":200,"statusDescription":"200 OK","headers":{},"body":"foo","isBase64Encoded":false}"# ); } #[test] - fn serialize_body() { - let mut resp = LambdaResponse::default(); + fn serialize_body_for_api_gateway_v2() { + let mut resp = api_gateway_v2_response(); resp.body = Some("foo".into()); assert_eq!( serde_json::to_string(&resp).expect("failed to serialize response"), - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"foo","isBase64Encoded":false}"# + r#"{"statusCode":200,"headers":{},"cookies":[],"body":"foo","isBase64Encoded":false}"# ); } #[test] fn serialize_multi_value_headers() { let res = LambdaResponse::from_response( - false, + &RequestOrigin::ApiGateway, Response::builder() .header("multi", "a") .header("multi", "b") @@ -227,4 +320,21 @@ mod tests { r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# ) } + + #[test] + fn serialize_cookies() { + let res = LambdaResponse::from_response( + &RequestOrigin::ApiGatewayV2, + Response::builder() + .header("set-cookie", "cookie1=a") + .header("set-cookie", "cookie2=b") + .body(Body::from(())) + .expect("failed to create response"), + ); + let json = serde_json::to_string(&res).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{},"cookies":["cookie1=a","cookie2=b"],"isBase64Encoded":false}"# + ) + } } From 9bd45c1c336bfb6b9dd121525c2c5bcf75e9dbde Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Sun, 7 Feb 2021 12:36:08 -0500 Subject: [PATCH 025/394] Fix clippy errors (#286) Fix all clippy errors currently present. Additionally add clippy check to actions. --- .github/workflows/build.yml | 13 ++++++++++++- lambda-http/Cargo.toml | 1 + lambda/Cargo.toml | 7 ++++--- lambda/src/client.rs | 20 ++++++++++++-------- lambda/src/lib.rs | 1 + 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f23ef207..c2b6161e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: Rust -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: @@ -68,6 +68,17 @@ jobs: override: true - name: Run fmt check run: cargo fmt --all -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: clippy + override: true + - name: Run clippy check + run: cargo clippy # publish rustdoc to a gh-pages branch on pushes to master # this can be helpful to those depending on the mainline branch diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 575c2d3e..526f45f3 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -9,6 +9,7 @@ license = "Apache-2.0" homepage = "https://github.com/awslabs/aws-lambda-rust-runtime" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" documentation = "https://docs.rs/lambda_runtime" +categories = ["web-programming::http-server"] readme = "../README.md" [badges] diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index 37a30f26..6ef7e1b0 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -6,6 +6,8 @@ description = "AWS Lambda Runtime" edition = "2018" license = "Apache License 2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +categories = ["web-programming::http-server"] +keywords = ["AWS", "Lambda", "API"] readme = "../README.md" [features] @@ -16,14 +18,13 @@ simulated = [] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } serde = { version = "1", features = ["derive"] } -serde_json = "1.0.39" +serde_json = "^1" bytes = "1.0" http = "0.2" async-stream = "0.3" futures = "0.3" tracing-error = "0.1.2" tracing = { version = "0.1", features = ["log"] } -tracing-futures = "0.2" tower-service = "0.3" tokio-stream = "0.1.2" @@ -31,5 +32,5 @@ tokio-stream = "0.1.2" tracing-subscriber = "0.2" once_cell = "1.4.0" simple_logger = "1.6.0" -log = "0.4" +log = "^0.4" simple-error = "0.2" diff --git a/lambda/src/client.rs b/lambda/src/client.rs index b24b2e78..148ec62c 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -31,11 +31,15 @@ where .scheme(scheme.clone()) .authority(authority.clone()) .path_and_query(path.clone()) - .build() - .expect("Unable to build URI"); + .build(); - parts.uri = uri; - Ok(Request::from_parts(parts, body)) + match uri { + Ok(u) => { + parts.uri = u; + Ok(Request::from_parts(parts, body)) + } + Err(e) => Err(Box::new(e)), + } } pub(crate) async fn call(&self, req: Request) -> Result, Error> { @@ -178,7 +182,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } @@ -209,7 +213,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } @@ -242,7 +246,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } @@ -277,7 +281,7 @@ mod endpoint_tests { tx.send(()).expect("Receiver has been dropped"); match server.await { Ok(_) => Ok(()), - Err(e) if e.is_panic() => return Err::<(), Error>(e.into()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), Err(_) => unreachable!("This branch shouldn't be reachable"), } } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 44077f0e..ac454c33 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -156,6 +156,7 @@ where let handler = Arc::clone(&handler); let request_id = &ctx.request_id.clone(); + #[allow(clippy::async_yields_async)] let task = tokio::spawn(async move { handler.call(body, ctx) }); let req = match task.await { From a5f642d9c1c3a0c8f2a1dc6644dacf22f5276573 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Sat, 13 Feb 2021 16:35:28 -0800 Subject: [PATCH 026/394] Remove sync from lambda-http handler (#289) Sync was added to the lambda http handler preventing handlers that used non-sync traits. --- lambda-http/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 91c8cd0d..7cff2b9a 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -100,7 +100,7 @@ pub trait Handler: Sized { /// The type of Response this Handler will return type Response: IntoResponse; /// The type of Future this Handler will return - type Fut: Future> + Send + Sync + 'static; + type Fut: Future> + Send + 'static; /// Function used to execute handler behavior fn call(&self, event: Request, context: Context) -> Self::Fut; } @@ -115,7 +115,7 @@ impl Handler for F where F: Fn(Request, Context) -> Fut, R: IntoResponse, - Fut: Future> + Send + Sync + 'static, + Fut: Future> + Send + 'static, { type Response = R; type Error = Error; @@ -128,7 +128,7 @@ where #[doc(hidden)] pub struct TransformResponse { request_origin: RequestOrigin, - fut: Pin> + Send + Sync>>, + fut: Pin> + Send>>, } impl Future for TransformResponse From ba696878310347f6610db819e3824be1b798fe63 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 16 Feb 2021 13:45:49 -0800 Subject: [PATCH 027/394] Replace SimStream with DuplexStream (#288) --- lambda/src/client.rs | 19 ++-- lambda/src/simulated.rs | 241 ++++++++++------------------------------ 2 files changed, 66 insertions(+), 194 deletions(-) diff --git a/lambda/src/client.rs b/lambda/src/client.rs index 148ec62c..45693a17 100644 --- a/lambda/src/client.rs +++ b/lambda/src/client.rs @@ -66,9 +66,10 @@ mod endpoint_tests { use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; use hyper::{server::conn::Http, service::service_fn, Body}; use serde_json::json; + use simulated::DuplexStreamWrapper; use std::convert::TryFrom; use tokio::{ - io::{AsyncRead, AsyncWrite}, + io::{self, AsyncRead, AsyncWrite}, select, sync::{self, oneshot}, }; @@ -161,14 +162,14 @@ mod endpoint_tests { #[tokio::test] async fn test_next_event() -> Result<(), Error> { let base = Uri::from_static("http://localhost:9001"); - let (client, server) = crate::simulated::chan(); + let (client, server) = io::duplex(64); let (tx, rx) = sync::oneshot::channel(); let server = tokio::spawn(async { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = simulated::Connector { inner: client }; + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let client = Client::with(base, conn); let req = NextEventRequest.into_req()?; @@ -189,7 +190,7 @@ mod endpoint_tests { #[tokio::test] async fn test_ok_response() -> Result<(), Error> { - let (client, server) = crate::simulated::chan(); + let (client, server) = io::duplex(64); let (tx, rx) = sync::oneshot::channel(); let base = Uri::from_static("http://localhost:9001"); @@ -197,7 +198,7 @@ mod endpoint_tests { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = simulated::Connector { inner: client }; + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let client = Client::with(base, conn); let req = EventCompletionRequest { @@ -220,7 +221,7 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { - let (client, server) = crate::simulated::chan(); + let (client, server) = io::duplex(200); let (tx, rx) = sync::oneshot::channel(); let base = Uri::from_static("http://localhost:9001"); @@ -228,7 +229,7 @@ mod endpoint_tests { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = simulated::Connector { inner: client }; + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let client = Client::with(base, conn); let req = EventErrorRequest { @@ -253,14 +254,14 @@ mod endpoint_tests { #[tokio::test] async fn successful_end_to_end_run() -> Result<(), Error> { - let (client, server) = crate::simulated::chan(); + let (client, server) = io::duplex(64); let (tx, rx) = sync::oneshot::channel(); let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { handle(server, rx).await.expect("Unable to handle request"); }); - let conn = simulated::Connector { inner: client }; + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let runtime = Runtime::builder() .with_endpoint(base) diff --git a/lambda/src/simulated.rs b/lambda/src/simulated.rs index 7d1931c3..4fcc3106 100644 --- a/lambda/src/simulated.rs +++ b/lambda/src/simulated.rs @@ -1,229 +1,100 @@ use http::Uri; use hyper::client::connect::Connection; use std::{ - cmp::min, - collections::VecDeque, + collections::HashMap, future::Future, io::Result as IoResult, pin::Pin, sync::{Arc, Mutex}, - task::{Context, Poll, Waker}, + task::{Context, Poll}, }; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::io::{AsyncRead, AsyncWrite, DuplexStream, ReadBuf}; -/// Creates a pair of `AsyncRead`/`AsyncWrite` data streams, where the write end of each member of the pair -/// is the read end of the other member of the pair. This allows us to emulate the behavior of a TcpStream -/// but in-memory, deterministically, and with full control over failure injection. -pub(crate) fn chan() -> (SimStream, SimStream) { - // Set up two reference-counted, lock-guarded byte VecDeques, one for each direction of the - // connection - let one = Arc::new(Mutex::new(BufferState::new())); - let two = Arc::new(Mutex::new(BufferState::new())); - - // Use buf1 for the read-side of left, use buf2 for the write-side of left - let left = SimStream { - read: ReadHalf { buffer: one.clone() }, - write: WriteHalf { buffer: two.clone() }, - }; - - // Now swap the buffers for right - let right = SimStream { - read: ReadHalf { buffer: two }, - write: WriteHalf { buffer: one }, - }; - - (left, right) -} +use crate::Error; #[derive(Clone)] pub struct Connector { - pub inner: SimStream, -} - -impl hyper::service::Service for Connector { - type Response = SimStream; - type Error = std::io::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: Uri) -> Self::Future { - let inner = self.inner.clone(); - Box::pin(async move { Ok(inner) }) - } + inner: Arc>>, } -impl Connection for SimStream { - fn connected(&self) -> hyper::client::connect::Connected { - hyper::client::connect::Connected::new() - } -} - -/// A struct that implements AsyncRead + AsyncWrite (similarly to TcpStream) using in-memory -/// bytes only. Unfortunately tokio does not provide an operation that is the opposite of -/// `tokio::io::split`, as that would negate the need for this struct. -// TODO: Implement the ability to explicitly close a connection -#[derive(Debug, Clone)] -pub struct SimStream { - read: ReadHalf, - write: WriteHalf, -} - -/// Delegates to the underlying `write` member's methods -impl AsyncWrite for SimStream { - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - Pin::new(&mut self.write).poll_write(cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.write).poll_flush(cx) - } - - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.write).poll_shutdown(cx) - } -} +pub struct DuplexStreamWrapper(DuplexStream); -/// Delegates to the underlying `read` member's methods -impl AsyncRead for SimStream { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - Pin::new(&mut self.read).poll_read(cx, buf) +impl DuplexStreamWrapper { + pub(crate) fn new(stream: DuplexStream) -> DuplexStreamWrapper { + DuplexStreamWrapper(stream) } } -/// A buffer for use with ReadHalf/WriteHalf that allows bytes to be written at one end of a -/// dequeue and read from the other end. If a `read_waker` is provided, the BufferState will call -/// `wake()` when there is new data to be read. -#[derive(Debug, Clone)] -pub struct BufferState { - buffer: VecDeque, - read_waker: Option, -} - -impl BufferState { - /// Creates a new `BufferState`. - fn new() -> Self { - BufferState { - buffer: VecDeque::new(), - read_waker: None, +impl Connector { + pub fn new() -> Self { + #[allow(clippy::mutable_key_type)] + let map = HashMap::new(); + Connector { + inner: Arc::new(Mutex::new(map)), } } - /// Writes data to the front of the deque byte buffer - fn write(&mut self, buf: &[u8]) { - for b in buf { - self.buffer.push_front(*b) - } - // If somebody is waiting on this data, wake them up. - if let Some(waker) = self.read_waker.take() { - waker.wake(); + pub fn insert(&self, uri: Uri, stream: DuplexStreamWrapper) -> Result<(), Error> { + match self.inner.lock() { + Ok(mut map) => { + map.insert(uri, stream); + Ok(()) + } + Err(_) => Err("mutex was poisoned".into()), } } - /// Read data from the end of the deque byte buffer - fn read(&mut self, to_buf: &mut ReadBuf<'_>) -> usize { - // Read no more bytes than we have available, and no more bytes than we were asked for - let bytes_to_read = min(to_buf.remaining(), self.buffer.len()); - for _ in 0..bytes_to_read { - to_buf.put_slice(&[self.buffer.pop_back().unwrap()]); + pub fn with(uri: Uri, stream: DuplexStreamWrapper) -> Result { + let connector = Connector::new(); + match connector.insert(uri, stream) { + Ok(_) => Ok(connector), + Err(e) => Err(e), } - - bytes_to_read } } -/// An AsyncWrite implementation that uses a VecDeque of bytes as a buffer. The WriteHalf will -/// add new bytes to the front of the deque using push_front. -/// -/// Intended for use with ReadHalf to read from the VecDeque -#[derive(Debug, Clone)] -pub struct WriteHalf { - buffer: Arc>, -} - -impl AsyncWrite for WriteHalf { - fn poll_write(self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - // Acquire the lock for the buffer - let mut write_to = self - .buffer - .lock() - .expect("Lock was poisoned when acquiring buffer lock for WriteHalf"); - - // write the bytes - write_to.write(buf); - - // This operation completes immediately - Poll::Ready(Ok(buf.len())) - } +impl hyper::service::Service for Connector { + type Response = DuplexStreamWrapper; + type Error = crate::Error; + #[allow(clippy::type_complexity)] + type Future = Pin> + Send>>; - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) + fn call(&mut self, uri: Uri) -> Self::Future { + let res = match self.inner.lock() { + Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), + Ok(_) => Err(format!("Uri {} is not in map", uri).into()), + Err(_) => Err("mutex was poisoned".into()), + }; + Box::pin(async move { res }) } } -#[derive(Debug, Clone)] -pub struct ReadHalf { - buffer: Arc>, +impl Connection for DuplexStreamWrapper { + fn connected(&self) -> hyper::client::connect::Connected { + hyper::client::connect::Connected::new() + } } -impl AsyncRead for ReadHalf { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - // Acquire the lock for the buffer - let mut read_from = self - .buffer - .lock() - .expect("Lock was poisoned when acquiring buffer lock for ReadHalf"); - - let bytes_read = read_from.read(buf); - - // Returning Poll::Ready(Ok(0)) would indicate that there is nothing more to read, which - // means that someone trying to read from a VecDeque that hasn't been written to yet - // would get an Eof error (as I learned the hard way). Instead we should return Poll:Pending - // to indicate that there could be more to read in the future. - if bytes_read == 0 { - read_from.read_waker = Some(cx.waker().clone()); - Poll::Pending - } else { - //read_from.read_waker = Some(cx.waker().clone()); - Poll::Ready(Ok(())) - } +impl AsyncRead for DuplexStreamWrapper { + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) } } -#[cfg(test)] -mod tests { - use super::chan; - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - - #[tokio::test] - async fn ends_should_talk_to_each_other() { - let (mut client, mut server) = chan(); - // Write ping to the side 1 - client.write_all(b"Ping").await.expect("Write should succeed"); - - // Verify we can read it from side 2 - let mut read_on_server = [0_u8; 4]; - server - .read_exact(&mut read_on_server) - .await - .expect("Read should succeed"); - assert_eq!(&read_on_server, b"Ping"); +impl AsyncWrite for DuplexStreamWrapper { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } - // Write "Pong" to side 2 - server.write_all(b"Pong").await.expect("Write should succeed"); + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } - // Verify we can read it from side 1 - let mut read_on_client = [0_u8; 4]; - client - .read_exact(&mut read_on_client) - .await - .expect("Read should succeed"); - assert_eq!(&read_on_client, b"Pong"); + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) } } From ad0a1d28fb925c8934b094deacb6c6fcf8b32597 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Tue, 9 Mar 2021 17:40:22 -0800 Subject: [PATCH 028/394] Renaming the lambda crate back to lambda_runtime and cleaning up documentation (#296) * Renaming the lambda crate back to lambda_runtime and cleaning up documentation * Fixing failing cargo test --- CHANGELOG.md | 18 ----------------- Cargo.toml | 4 ++-- README.md | 20 +++++++++---------- lambda-http/Cargo.toml | 6 +++--- lambda-http/examples/hello-http.rs | 4 ++-- lambda-http/src/ext.rs | 4 ++-- lambda-http/src/lib.rs | 20 +++++++++---------- {lambda => lambda-runtime}/Cargo.toml | 2 +- {lambda => lambda-runtime}/examples/README.md | 4 ---- {lambda => lambda-runtime}/examples/basic.rs | 4 ++-- .../examples/error-handling.rs | 6 +++--- {lambda => lambda-runtime}/src/client.rs | 0 {lambda => lambda-runtime}/src/lib.rs | 6 +++--- {lambda => lambda-runtime}/src/requests.rs | 0 {lambda => lambda-runtime}/src/simulated.rs | 0 {lambda => lambda-runtime}/src/types.rs | 0 16 files changed, 38 insertions(+), 60 deletions(-) delete mode 100644 CHANGELOG.md rename {lambda => lambda-runtime}/Cargo.toml (97%) rename {lambda => lambda-runtime}/examples/README.md (98%) rename {lambda => lambda-runtime}/examples/basic.rs (95%) rename {lambda => lambda-runtime}/examples/error-handling.rs (95%) rename {lambda => lambda-runtime}/src/client.rs (100%) rename {lambda => lambda-runtime}/src/lib.rs (98%) rename {lambda => lambda-runtime}/src/requests.rs (100%) rename {lambda => lambda-runtime}/src/simulated.rs (100%) rename {lambda => lambda-runtime}/src/types.rs (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 8f914433..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,18 +0,0 @@ -# next (unreleased) - -- **New**: The `lambda_http` crate now exposes mock helper methods for `RequestExt` under `cfg(test)` builds to facilitate straight forward unit testability of handlers. -- **New**: The `lambda_http` crate now exposes two new functions for deserializing requests from text and raw IO: `lambda_http::request::{from_str,from_reader}`. - -# 0.2.0 - -- **New**: We created lambda_runtime_core crate that implements the runtime's main loop and supports handlers that accept and return Vec. ([#53](https://github.com/awslabs/aws-lambda-rust-runtime/issues/53)) -- **New**: The primary lambda_runtime crate is a wrapper on top of the lambda_runtime_core handler ([#53](https://github.com/awslabs/aws-lambda-rust-runtime/issues/53)) -- **New**: The lambda_http crate, which enables support for API Gateway or ALB requests, treating them as Request structs from the http crate ([#18 by @softprops](https://github.com/awslabs/aws-lambda-rust-runtime/issues/18)). -- **New**: The lambda_runtime_errors crate introduces the LambdaErrorExt trait that enables the ability to specify custom errorType values for the Lambda Runtime API. The change also includes a companion derive crate that makes it easy to automatically generate LambdaErrorExt implementations for crate-local error types ([#63](https://github.com/awslabs/aws-lambda-rust-runtime/issues/63)). -- **Fix**: Handlers can now return any error type ([#54](https://github.com/awslabs/aws-lambda-rust-runtime/issues/54)) -- **Fix**: Support for closures as handlers ([#19 by @srijs](https://github.com/awslabs/aws-lambda-rust-runtime/issues/19)). -- **Fix**: Multiple bug fixes and performance improvements (thanks @Sh4rK). - -# 0.1.0 - -- Initial Release diff --git a/Cargo.toml b/Cargo.toml index 4c4ad33d..291345b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] members = [ - "lambda", - "lambda-http" + "lambda-http", + "lambda-runtime" ] \ No newline at end of file diff --git a/README.md b/README.md index e624a692..b748c586 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This workspace includes multiple crates: -- [![Docs](https://docs.rs/lambda/badge.svg)](https://docs.rs/lambda) **`lambda`** is a library that provides a Lambda runtime for applications written in Rust. +- [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. ## Example function @@ -12,7 +12,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. ```rust,no_run -use lambda::{handler_fn, Context}; +use lambda_runtime::{handler_fn, Context}; use serde_json::{json, Value}; type Error = Box; @@ -20,7 +20,7 @@ type Error = Box; #[tokio::main] async fn main() -> Result<(), Error> { let func = handler_fn(func); - lambda::run(func).await?; + lambda_runtime::run(func).await?; Ok(()) } @@ -31,7 +31,7 @@ async fn func(event: Value, _: Context) -> Result { } ``` -The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda/examples/hello-without-macro.rs) in the `lambda` crate. +The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda/examples/hello-without-macro.rs) in the `lambda_runtime` crate. ### Deployment @@ -56,13 +56,13 @@ $ echo $'[target.x86_64-unknown-linux-musl]\nlinker = "x86_64-linux-musl-gcc"' > Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: ```bash -$ cargo build -p lambda --example basic --release --target x86_64-unknown-linux-musl +$ cargo build -p lambda_runtime --example basic --release --target x86_64-unknown-linux-musl ``` For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. ```bash -$ cp ./target/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +$ cp ./target/x86_64-unknown-linux-musl/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap ``` Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! @@ -127,7 +127,7 @@ $ npx serverless invoke -f hello -d '{"foo":"bar"}' Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/softprops/lambda-rust). -Running the following command will start a ephemeral docker container which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda/release/{your-binary-name}.zip`, typically this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`) +Running the following command will start a ephemeral docker container which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`, typically this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`) ```bash # build and package deploy-ready artifact @@ -159,12 +159,12 @@ $ unzip -o \ ## `lambda` -`lambda` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides a few major components: +`lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides a few major components: - `Handler`, a trait that defines interactions between customer-authored code and this library. -- `lambda::run`, function that runs an `Handler`. +- `lambda_runtime::run`, function that runs an `Handler`. -The function `handler_fn` converts a rust function or closure to `Handler`, which can then be run by `lambda::run`. +The function `handler_fn` converts a rust function or closure to `Handler`, which can then be run by `lambda_runtime::run`. ## AWS event objects diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 526f45f3..2c882a1a 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -17,12 +17,12 @@ travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" } maintenance = { status = "actively-developed" } [dependencies] -base64 = "0.12" +base64 = "0.13.0" http = "0.2" -lambda = { path = "../lambda", version = "0.1" } +lambda_runtime = { path = "../lambda-runtime", version = "0.1" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" -serde_urlencoded = "0.6" +serde_urlencoded = "0.7.0" [dev-dependencies] log = "^0.4" diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index 0a609651..3fadb9de 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -1,6 +1,6 @@ use lambda_http::{ handler, - lambda::{self, Context}, + lambda_runtime::{self, Context}, IntoResponse, Request, RequestExt, Response, }; @@ -8,7 +8,7 @@ type Error = Box; #[tokio::main] async fn main() -> Result<(), Error> { - lambda::run(handler(func)).await?; + lambda_runtime::run(handler(func)).await?; Ok(()) } diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 13740f49..13a3be52 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -66,7 +66,7 @@ impl Error for PayloadError { /// as well as `{"x":1, "y":2}` respectively. /// /// ```rust,no_run -/// use lambda_http::{handler, lambda::{self, Context}, Body, IntoResponse, Request, Response, RequestExt}; +/// use lambda_http::{handler, lambda_runtime::{self, Context}, Body, IntoResponse, Request, Response, RequestExt}; /// use serde::Deserialize; /// /// type Error = Box; @@ -81,7 +81,7 @@ impl Error for PayloadError { /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { -/// lambda::run(handler(add)).await?; +/// lambda_runtime::run(handler(add)).await?; /// Ok(()) /// } /// diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 7cff2b9a..2351383e 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -12,12 +12,12 @@ //! ## Hello World //! //! The following example is how you would structure your Lambda such that you have a `main` function where you explicitly invoke -//! `lambda::run` in combination with the [`handler`](fn.handler.html) function. This pattern allows you to utilize global initialization +//! `lambda_runtime::run` in combination with the [`handler`](fn.handler.html) function. This pattern allows you to utilize global initialization //! 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 //! your function's execution path. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda}; +//! use lambda_http::{handler, lambda_runtime}; //! //! type Error = Box; //! @@ -25,7 +25,7 @@ //! async fn main() -> Result<(), Error> { //! // initialize dependencies once here for the lifetime of your //! // lambda task -//! lambda::run(handler(|request, context| async { Ok("👋 world!") })).await?; +//! lambda_runtime::run(handler(|request, context| async { Ok("👋 world!") })).await?; //! Ok(()) //! } //! ``` @@ -36,13 +36,13 @@ //! with the [`RequestExt`](trait.RequestExt.html) trait. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda::{self, Context}, IntoResponse, Request, RequestExt}; +//! use lambda_http::{handler, lambda_runtime::{self, Context}, IntoResponse, Request, RequestExt}; //! //! type Error = Box; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { -//! lambda::run(handler(hello)).await?; +//! lambda_runtime::run(handler(hello)).await?; //! Ok(()) //! } //! @@ -66,8 +66,8 @@ extern crate maplit; pub use http::{self, Response}; -use lambda::Handler as LambdaHandler; -pub use lambda::{self, Context}; +use lambda_runtime::Handler as LambdaHandler; +pub use lambda_runtime::{self, Context}; mod body; pub mod ext; @@ -93,7 +93,7 @@ pub type Request = http::Request; /// Functions serving as ALB and API Gateway REST and HTTP API handlers must conform to this type. /// -/// This can be viewed as a `lambda::Handler` constrained to `http` crate `Request` and `Response` types +/// This can be viewed as a `lambda_runtime::Handler` constrained to `http` crate `Request` and `Response` types pub trait Handler: Sized { /// The type of Error that this Handler will return type Error; @@ -105,7 +105,7 @@ pub trait Handler: Sized { fn call(&self, event: Request, context: Context) -> Self::Fut; } -/// Adapts a [`Handler`](trait.Handler.html) to the `lambda::run` interface +/// Adapts a [`Handler`](trait.Handler.html) to the `lambda_runtime::run` interface pub fn handler(handler: H) -> Adapter { Adapter { handler } } @@ -146,7 +146,7 @@ where } } -/// Exists only to satisfy the trait cover rule for `lambda::Handler` impl +/// Exists only to satisfy the trait cover rule for `lambda_runtime::Handler` impl /// /// User code should never need to interact with this type directly. Since `Adapter` implements `Handler` /// It serves as a opaque trait covering type. diff --git a/lambda/Cargo.toml b/lambda-runtime/Cargo.toml similarity index 97% rename from lambda/Cargo.toml rename to lambda-runtime/Cargo.toml index 6ef7e1b0..c1e4944f 100644 --- a/lambda/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lambda" +name = "lambda_runtime" version = "0.1.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" diff --git a/lambda/examples/README.md b/lambda-runtime/examples/README.md similarity index 98% rename from lambda/examples/README.md rename to lambda-runtime/examples/README.md index 51a5a10b..5e080eee 100644 --- a/lambda/examples/README.md +++ b/lambda-runtime/examples/README.md @@ -63,10 +63,6 @@ Runtime.ExitError See _error-handling.rs_ example for more error handling options. -## macro.rs - -The most basic example using `#[lambda]` macro to reduce the amount of boilerplate code. - **Deployment**: ```bash cp ./target/x86_64-unknown-linux-musl/release/examples/macro ./bootstrap && zip lambda.zip bootstrap && rm bootstrap diff --git a/lambda/examples/basic.rs b/lambda-runtime/examples/basic.rs similarity index 95% rename from lambda/examples/basic.rs rename to lambda-runtime/examples/basic.rs index 7b492a44..999efdcc 100644 --- a/lambda/examples/basic.rs +++ b/lambda-runtime/examples/basic.rs @@ -1,7 +1,7 @@ // This example requires the following input to succeed: // { "command": "do something" } -use lambda::{handler_fn, Context}; +use lambda_runtime::{handler_fn, Context}; use log::LevelFilter; use serde::{Deserialize, Serialize}; use simple_logger::SimpleLogger; @@ -35,7 +35,7 @@ async fn main() -> Result<(), Error> { SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); let func = handler_fn(my_handler); - lambda::run(func).await?; + lambda_runtime::run(func).await?; Ok(()) } diff --git a/lambda/examples/error-handling.rs b/lambda-runtime/examples/error-handling.rs similarity index 95% rename from lambda/examples/error-handling.rs rename to lambda-runtime/examples/error-handling.rs index 1b061254..f5ba2474 100644 --- a/lambda/examples/error-handling.rs +++ b/lambda-runtime/examples/error-handling.rs @@ -1,5 +1,5 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda -use lambda::handler_fn; +use lambda_runtime::handler_fn; use log::LevelFilter; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -73,12 +73,12 @@ async fn main() -> Result<(), Error> { // call the actual handler of the request let func = handler_fn(func); - lambda::run(func).await?; + lambda_runtime::run(func).await?; Ok(()) } /// The actual handler of the Lambda request. -pub(crate) async fn func(event: Value, ctx: lambda::Context) -> Result { +pub(crate) async fn func(event: Value, ctx: lambda_runtime::Context) -> Result { // check what action was requested match serde_json::from_value::(event)?.event_type { EventType::SimpleError => { diff --git a/lambda/src/client.rs b/lambda-runtime/src/client.rs similarity index 100% rename from lambda/src/client.rs rename to lambda-runtime/src/client.rs diff --git a/lambda/src/lib.rs b/lambda-runtime/src/lib.rs similarity index 98% rename from lambda/src/lib.rs rename to lambda-runtime/src/lib.rs index ac454c33..e05edbf4 100644 --- a/lambda/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -4,7 +4,7 @@ //! The mechanism available for defining a Lambda function is as follows: //! //! Create a type that conforms to the [`Handler`] trait. This type can then be passed -//! to the the `lambda::run` function, which launches and runs the Lambda runtime. +//! to the the `lambda_runtime::run` function, which launches and runs the Lambda runtime. pub use crate::types::Context; use client::Client; use hyper::client::{connect::Connection, HttpConnector}; @@ -273,7 +273,7 @@ where /// /// # Example /// ```no_run -/// use lambda::{handler_fn, Context}; +/// use lambda_runtime::{handler_fn, Context}; /// use serde_json::Value; /// /// type Error = Box; @@ -281,7 +281,7 @@ where /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// let func = handler_fn(func); -/// lambda::run(func).await?; +/// lambda_runtime::run(func).await?; /// Ok(()) /// } /// diff --git a/lambda/src/requests.rs b/lambda-runtime/src/requests.rs similarity index 100% rename from lambda/src/requests.rs rename to lambda-runtime/src/requests.rs diff --git a/lambda/src/simulated.rs b/lambda-runtime/src/simulated.rs similarity index 100% rename from lambda/src/simulated.rs rename to lambda-runtime/src/simulated.rs diff --git a/lambda/src/types.rs b/lambda-runtime/src/types.rs similarity index 100% rename from lambda/src/types.rs rename to lambda-runtime/src/types.rs From 96fc5bce8543b42339c22f5c3ed9f83bfabd3991 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Thu, 11 Mar 2021 18:31:25 -0800 Subject: [PATCH 029/394] Making Error type public so that consumers don't have to create their own type alias (#298) * Making Error type public so that consumers don't have to create their own type alias * Missed usage in example * Rust fmt changes * Missed example use. * Changing lambda_runtime::Error references to just Error and including in imports instead. --- lambda-http/src/lib.rs | 13 +++---------- lambda-runtime/examples/basic.rs | 6 +----- lambda-runtime/examples/error-handling.rs | 5 +---- lambda-runtime/src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 2351383e..7fdcca56 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -17,9 +17,7 @@ //! your function's execution path. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda_runtime}; -//! -//! type Error = Box; +//! use lambda_http::{handler, lambda_runtime::{self, Error}}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { @@ -36,9 +34,7 @@ //! with the [`RequestExt`](trait.RequestExt.html) trait. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda_runtime::{self, Context}, IntoResponse, Request, RequestExt}; -//! -//! type Error = Box; +//! use lambda_http::{handler, lambda_runtime::{self, Context, Error}, IntoResponse, Request, RequestExt}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { @@ -66,8 +62,8 @@ extern crate maplit; pub use http::{self, Response}; -use lambda_runtime::Handler as LambdaHandler; pub use lambda_runtime::{self, Context}; +use lambda_runtime::{Error, Handler as LambdaHandler}; mod body; pub mod ext; @@ -85,9 +81,6 @@ use std::{ task::{Context as TaskContext, Poll}, }; -/// Error type that lambdas may result in -pub(crate) type Error = Box; - /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; diff --git a/lambda-runtime/examples/basic.rs b/lambda-runtime/examples/basic.rs index 999efdcc..d74e10de 100644 --- a/lambda-runtime/examples/basic.rs +++ b/lambda-runtime/examples/basic.rs @@ -1,15 +1,11 @@ // This example requires the following input to succeed: // { "command": "do something" } -use lambda_runtime::{handler_fn, Context}; +use lambda_runtime::{handler_fn, Context, Error}; use log::LevelFilter; use serde::{Deserialize, Serialize}; use simple_logger::SimpleLogger; -/// A shorthand for `Box` -/// type required by aws-lambda-rust-runtime. -pub type Error = Box; - /// This is also a made-up example. Requests come into the runtime as unicode /// strings in json format, which can map to any structure that implements `serde::Deserialize` /// The runtime pays no attention to the contents of the request payload. diff --git a/lambda-runtime/examples/error-handling.rs b/lambda-runtime/examples/error-handling.rs index f5ba2474..a0683f21 100644 --- a/lambda-runtime/examples/error-handling.rs +++ b/lambda-runtime/examples/error-handling.rs @@ -1,14 +1,11 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda -use lambda_runtime::handler_fn; +use lambda_runtime::{handler_fn, Error}; use log::LevelFilter; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use simple_logger::SimpleLogger; use std::fs::File; -/// A shorthand for `Box` type required by aws-lambda-rust-runtime. -pub type Error = Box; - /// A simple Lambda request structure with just one field /// that tells the Lambda what is expected of it. #[derive(Deserialize)] diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e05edbf4..a78da826 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -31,7 +31,7 @@ use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEvent use types::Diagnostic; /// Error type that lambdas may result in -pub(crate) type Error = Box; +pub type Error = Box; /// Configuration derived from environment variables. #[derive(Debug, Default, Clone, PartialEq)] From 1d31aad77dfcd59e461603920333a1e89f1130d3 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Thu, 11 Mar 2021 23:57:42 -0500 Subject: [PATCH 030/394] Fix broken link in readme (#299) The link to the basic example was pointing to the old directory structure. Also remove the explicit error definition. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b748c586..e0abe94b 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,9 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. ```rust,no_run -use lambda_runtime::{handler_fn, Context}; +use lambda_runtime::{handler_fn, Context, Error}; use serde_json::{json, Value}; -type Error = Box; - #[tokio::main] async fn main() -> Result<(), Error> { let func = handler_fn(func); @@ -31,7 +29,7 @@ async fn func(event: Value, _: Context) -> Result { } ``` -The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda/examples/hello-without-macro.rs) in the `lambda_runtime` crate. +The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda-runtime/examples/basic.rs) in the `lambda_runtime` crate. ### Deployment From 2f30438c96339babb692b7135f508910cb60b8d3 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:54:25 -0500 Subject: [PATCH 031/394] Bump crate versions for 0.3.0 release (#301) --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 2c882a1a..4cf2ba8a 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.2.0-beta.1" +version = "0.3.0" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -19,7 +19,7 @@ maintenance = { status = "actively-developed" } [dependencies] base64 = "0.13.0" http = "0.2" -lambda_runtime = { path = "../lambda-runtime", version = "0.1" } +lambda_runtime = { path = "../lambda-runtime", version = "0.3" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index c1e4944f..2091c508 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.1.0" +version = "0.3.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" From fd13527a06060e32c786632f21c726e989e45fa9 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Fri, 12 Mar 2021 18:35:49 -0500 Subject: [PATCH 032/394] Fix license field for lambda_runtime (#302) The license field for lambda_runtime was not using the correct identifier preventing publishing to crates.io. --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 2091c508..b2d68246 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" -license = "Apache License 2.0" +license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] From d3ff4352c4b0e64fb46af2ca425db39211237e26 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Fri, 12 Mar 2021 16:55:31 -0800 Subject: [PATCH 033/394] Update build.yml to fix docs generation (#303) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2b6161e..b49ddf81 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,7 @@ jobs: - name: Generate Docs run: | cargo doc --no-deps - echo "" > target/doc/index.html + echo "" > target/doc/index.html - name: Publish uses: peaceiris/actions-gh-pages@v3 with: From c01811a33d33a89d0c55c3c9e90916832f1f7ca8 Mon Sep 17 00:00:00 2001 From: mx Date: Mon, 29 Mar 2021 03:57:08 +1300 Subject: [PATCH 034/394] Set env_config value and add Serialize/Deserialize for Context (#308) * Added changes from 2020 on top of v.0.3 master * Init Lambda env vars for an end to end test * Formatting change for cargo fmt * Bumped the version to 0.3.1 * Added debugging section to ReadMe * ReadMe Debugging section mentioned non-AWS project * Roll back version bump back to 0.3.0 --- README.md | 4 ++++ lambda-runtime/src/client.rs | 25 +++++++++++++++++++++++-- lambda-runtime/src/lib.rs | 25 +++++++++++++++---------- lambda-runtime/src/types.rs | 12 +++++++++++- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e0abe94b..a9891b2b 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,10 @@ $ unzip -o \ # Ctrl-D to yield control back to your function ``` +### Debugging + +Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The lambda handler code does not need to be modified between the local and AWS versions. + ## `lambda` `lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides a few major components: diff --git a/lambda-runtime/src/client.rs b/lambda-runtime/src/client.rs index 45693a17..2b860e87 100644 --- a/lambda-runtime/src/client.rs +++ b/lambda-runtime/src/client.rs @@ -67,7 +67,7 @@ mod endpoint_tests { use hyper::{server::conn::Http, service::service_fn, Body}; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::convert::TryFrom; + use std::{convert::TryFrom, env}; use tokio::{ io::{self, AsyncRead, AsyncWrite}, select, @@ -274,9 +274,30 @@ mod endpoint_tests { } let f = crate::handler_fn(func); + // set env vars needed to init Config if they are not already set in the environment + if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { + env::set_var("AWS_LAMBDA_RUNTIME_API", "http://localhost:9001"); + } + if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); + } + if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); + } + if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); + } + if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); + } + if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); + } + let config = crate::Config::from_env().expect("Failed to read env vars"); + let client = &runtime.client; let incoming = incoming(client).take(1); - runtime.run(incoming, f).await?; + runtime.run(incoming, f, &config).await?; // shutdown server tx.send(()).expect("Receiver has been dropped"); diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index a78da826..c629fd0c 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -34,7 +34,7 @@ use types::Diagnostic; pub type Error = Box; /// Configuration derived from environment variables. -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct Config { /// The host and port of the [runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). pub endpoint: String, @@ -54,12 +54,15 @@ impl Config { /// Attempts to read configuration from environment variables. pub fn from_env() -> Result { let conf = Config { - endpoint: env::var("AWS_LAMBDA_RUNTIME_API")?, - function_name: env::var("AWS_LAMBDA_FUNCTION_NAME")?, - memory: env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")?.parse::()?, - version: env::var("AWS_LAMBDA_FUNCTION_VERSION")?, - log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME")?, - log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME")?, + endpoint: env::var("AWS_LAMBDA_RUNTIME_API").expect("Missing AWS_LAMBDA_RUNTIME_API env var"), + function_name: env::var("AWS_LAMBDA_FUNCTION_NAME").expect("Missing AWS_LAMBDA_FUNCTION_NAME env var"), + memory: env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") + .expect("Missing AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var") + .parse::() + .expect("AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var is not "), + version: env::var("AWS_LAMBDA_FUNCTION_VERSION").expect("Missing AWS_LAMBDA_FUNCTION_VERSION env var"), + log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME").expect("Missing AWS_LAMBDA_LOG_STREAM_NAME env var"), + log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME").expect("Missing AWS_LAMBDA_LOG_GROUP_NAME env var"), }; Ok(conf) } @@ -133,6 +136,7 @@ where &self, incoming: impl Stream, Error>> + Send, handler: F, + config: &Config, ) -> Result<(), Error> where F: Handler + Send + Sync + 'static, @@ -150,6 +154,7 @@ where let (parts, body) = event.into_parts(); let ctx: Context = Context::try_from(parts.headers)?; + let ctx: Context = ctx.with_config(config); let body = hyper::body::to_bytes(body).await?; trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose let body = serde_json::from_slice(&body)?; @@ -299,16 +304,16 @@ where { trace!("Loading config from env"); let config = Config::from_env()?; - let uri = config.endpoint.try_into().expect("Unable to convert to URL"); + let uri = config.endpoint.clone().try_into().expect("Unable to convert to URL"); let runtime = Runtime::builder() .with_connector(HttpConnector::new()) .with_endpoint(uri) .build() - .expect("Unable create runtime"); + .expect("Unable to create a runtime"); let client = &runtime.client; let incoming = incoming(client); - runtime.run(incoming, handler).await + runtime.run(incoming, handler, &config).await } fn type_name_of_val(_: T) -> &'static str { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index c3e11498..c8579250 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -93,7 +93,7 @@ pub struct CognitoIdentity { /// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) /// and the headers returned by the poll request to the Runtime APIs. #[non_exhaustive] -#[derive(Clone, Debug, PartialEq, Default)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct Context { /// The AWS request ID generated by the Lambda service. pub request_id: String, @@ -141,3 +141,13 @@ impl TryFrom for Context { Ok(ctx) } } + +impl Context { + /// Add environment details to the context by setting `env_config`. + pub fn with_config(self, config: &Config) -> Self { + Self { + env_config: config.clone(), + ..self + } + } +} From 53397cd9ad01f769e1c0a88963ab9861af58edfc Mon Sep 17 00:00:00 2001 From: Rasmus Viitanen Date: Sat, 22 May 2021 18:20:50 +0200 Subject: [PATCH 035/394] switch panic handling method (#319) --- lambda-runtime/examples/shared_resource.rs | 63 ++++++++++++++++++++++ lambda-runtime/src/lib.rs | 40 +++++++------- 2 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 lambda-runtime/examples/shared_resource.rs diff --git a/lambda-runtime/examples/shared_resource.rs b/lambda-runtime/examples/shared_resource.rs new file mode 100644 index 00000000..fc7c15f0 --- /dev/null +++ b/lambda-runtime/examples/shared_resource.rs @@ -0,0 +1,63 @@ +// This example demonstrates use of shared resources such as DB connections +// or local caches that can be initialized at the start of the runtime and +// reused by subsequent lambda handler calls. +// Run it with the following input: +// { "command": "do something" } + +use lambda_runtime::{handler_fn, Context, Error}; +use log::LevelFilter; +use serde::{Deserialize, Serialize}; +use simple_logger::SimpleLogger; + +/// This is also a made-up example. Requests come into the runtime as unicode +/// strings in json format, which can map to any structure that implements `serde::Deserialize` +/// The runtime pays no attention to the contents of the request payload. +#[derive(Deserialize)] +struct Request { + command: String, +} + +/// This is a made-up example of what a response structure may look like. +/// There is no restriction on what it can be. The runtime requires responses +/// to be serialized into json. The runtime pays no attention +/// to the contents of the response payload. +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +struct SharedClient { + name: &'static str, +} + +impl SharedClient { + fn new(name: &'static str) -> Self { + Self { + name + } + } + + fn response(&self, req_id: String, command: String) -> Response { + Response { + req_id, + msg: format!("Command {} executed by {}.", command, self.name), + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + // can be replaced with any other method of initializing `log` + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + let client = SharedClient::new("Shared Client 1 (perhaps a database)"); + let client_ref = &client; + lambda_runtime::run(handler_fn(move |event: Request, ctx: Context| async move { + let command = event.command; + Ok::(client_ref.response(ctx.request_id, command)) + })) + .await?; + Ok(()) +} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c629fd0c..4f2497cb 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -13,7 +13,7 @@ use std::{ convert::{TryFrom, TryInto}, env, fmt, future::Future, - sync::Arc, + panic, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; @@ -96,7 +96,7 @@ pub struct HandlerFn { impl Handler for HandlerFn where F: Fn(A, Context) -> Fut, - Fut: Future> + Send, + Fut: Future>, Error: Into> + fmt::Display, { type Error = Error; @@ -139,14 +139,13 @@ where config: &Config, ) -> Result<(), Error> where - F: Handler + Send + Sync + 'static, - >::Fut: Future>::Error>> + Send + 'static, - >::Error: fmt::Display + Send + Sync + 'static, - A: for<'de> Deserialize<'de> + Send + Sync + 'static, - B: Serialize + Send + Sync + 'static, + F: Handler, + >::Fut: Future>::Error>>, + >::Error: fmt::Display, + A: for<'de> Deserialize<'de>, + B: Serialize, { let client = &self.client; - let handler = Arc::new(handler); tokio::pin!(incoming); while let Some(event) = incoming.next().await { trace!("New event arrived (run loop)"); @@ -159,12 +158,10 @@ where trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose let body = serde_json::from_slice(&body)?; - let handler = Arc::clone(&handler); let request_id = &ctx.request_id.clone(); - #[allow(clippy::async_yields_async)] - let task = tokio::spawn(async move { handler.call(body, ctx) }); + let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(body, ctx))); - let req = match task.await { + let req = match task { Ok(response) => match response.await { Ok(response) => { trace!("Ok response from handler (run loop)"); @@ -186,18 +183,21 @@ where .into_req() } }, - Err(err) if err.is_panic() => { + Err(err) => { error!("{:?}", err); // inconsistent with other log record formats - to be reviewed EventErrorRequest { request_id, diagnostic: Diagnostic { error_type: type_name_of_val(&err).to_owned(), - error_message: format!("Lambda panicked: {}", err), + error_message: if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {}", msg) + } else { + "Lambda panicked".to_string() + }, }, } .into_req() } - Err(_) => unreachable!("tokio::task should not be canceled"), }; let req = req?; client.call(req).await.expect("Unable to send response to Runtime APIs"); @@ -296,11 +296,11 @@ where /// ``` pub async fn run(handler: F) -> Result<(), Error> where - F: Handler + Send + Sync + 'static, - >::Fut: Future>::Error>> + Send + 'static, - >::Error: fmt::Display + Send + Sync + 'static, - A: for<'de> Deserialize<'de> + Send + Sync + 'static, - B: Serialize + Send + Sync + 'static, + F: Handler, + >::Fut: Future>::Error>>, + >::Error: fmt::Display, + A: for<'de> Deserialize<'de>, + B: Serialize, { trace!("Loading config from env"); let config = Config::from_env()?; From 0280da0821c4671af1554757bbc7adb59dbfa0d4 Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Sat, 22 May 2021 12:22:12 -0500 Subject: [PATCH 036/394] Fix rust format issue (#330) --- lambda-runtime/examples/shared_resource.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lambda-runtime/examples/shared_resource.rs b/lambda-runtime/examples/shared_resource.rs index fc7c15f0..64fb2e62 100644 --- a/lambda-runtime/examples/shared_resource.rs +++ b/lambda-runtime/examples/shared_resource.rs @@ -33,9 +33,7 @@ struct SharedClient { impl SharedClient { fn new(name: &'static str) -> Self { - Self { - name - } + Self { name } } fn response(&self, req_id: String, command: String) -> Response { From 797f0ab285fbaafe284f7a0df4cceb2ae0e3d3d4 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Wed, 7 Jul 2021 20:00:06 -0700 Subject: [PATCH 037/394] Removed 'static bound requirements for the lambda_http crate and create a shared resources example for the crate (#333) --- lambda-http/examples/hello-http.rs | 4 +- .../examples/shared-resources-example.rs | 39 +++++++++++++++++++ lambda-http/src/lib.rs | 31 ++++++++------- 3 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 lambda-http/examples/shared-resources-example.rs diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index 3fadb9de..0682d028 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -1,11 +1,9 @@ use lambda_http::{ handler, - lambda_runtime::{self, Context}, + lambda_runtime::{self, Context, Error}, IntoResponse, Request, RequestExt, Response, }; -type Error = Box; - #[tokio::main] async fn main() -> Result<(), Error> { lambda_runtime::run(handler(func)).await?; diff --git a/lambda-http/examples/shared-resources-example.rs b/lambda-http/examples/shared-resources-example.rs new file mode 100644 index 00000000..61a30668 --- /dev/null +++ b/lambda-http/examples/shared-resources-example.rs @@ -0,0 +1,39 @@ +use lambda_http::{ + handler, + lambda_runtime::{self, Context, Error}, + IntoResponse, Request, RequestExt, Response, +}; + +struct SharedClient { + name: &'static str, +} + +impl SharedClient { + fn response(&self, req_id: String, first_name: &str) -> String { + format!("{}: Client ({}) invoked by {}.", req_id, self.name, first_name) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // Create the "client" and a reference to it, so that we can pass this into the handler closure below. + let shared_client = SharedClient { + name: "random_client_name_1", + }; + let shared_client_ref = &shared_client; + + // Define a closure here that makes use of the shared client. + let handler_func_closure = move |event: Request, ctx: Context| async move { + Ok(match event.query_string_parameters().get("first_name") { + Some(first_name) => shared_client_ref.response(ctx.request_id, first_name).into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }) + }; + + // Pass the closure to the runtime here. + lambda_runtime::run(handler(handler_func_closure)).await?; + Ok(()) +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 7fdcca56..af49fc08 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -77,6 +77,7 @@ use crate::{ }; use std::{ future::Future, + marker::PhantomData, pin::Pin, task::{Context as TaskContext, Poll}, }; @@ -87,28 +88,31 @@ pub type Request = http::Request; /// Functions serving as ALB and API Gateway REST and HTTP API handlers must conform to this type. /// /// This can be viewed as a `lambda_runtime::Handler` constrained to `http` crate `Request` and `Response` types -pub trait Handler: Sized { +pub trait Handler<'a>: Sized { /// The type of Error that this Handler will return type Error; /// The type of Response this Handler will return type Response: IntoResponse; /// The type of Future this Handler will return - type Fut: Future> + Send + 'static; + type Fut: Future> + Send + 'a; /// Function used to execute handler behavior fn call(&self, event: Request, context: Context) -> Self::Fut; } /// Adapts a [`Handler`](trait.Handler.html) to the `lambda_runtime::run` interface -pub fn handler(handler: H) -> Adapter { - Adapter { handler } +pub fn handler<'a, H: Handler<'a>>(handler: H) -> Adapter<'a, H> { + Adapter { + handler, + _pd: PhantomData, + } } /// An implementation of `Handler` for a given closure return a `Future` representing the computed response -impl Handler for F +impl<'a, F, R, Fut> Handler<'a> for F where F: Fn(Request, Context) -> Fut, R: IntoResponse, - Fut: Future> + Send + 'static, + Fut: Future> + Send + 'a, { type Response = R; type Error = Error; @@ -119,12 +123,12 @@ where } #[doc(hidden)] -pub struct TransformResponse { +pub struct TransformResponse<'a, R, E> { request_origin: RequestOrigin, - fut: Pin> + Send>>, + fut: Pin> + Send + 'a>>, } -impl Future for TransformResponse +impl<'a, R, E> Future for TransformResponse<'a, R, E> where R: IntoResponse, { @@ -146,11 +150,12 @@ where /// /// See [this article](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/) /// for a larger explaination of why this is nessessary -pub struct Adapter { +pub struct Adapter<'a, H: Handler<'a>> { handler: H, + _pd: PhantomData<&'a H>, } -impl Handler for Adapter { +impl<'a, H: Handler<'a>> Handler<'a> for Adapter<'a, H> { type Response = H::Response; type Error = H::Error; type Fut = H::Fut; @@ -159,9 +164,9 @@ impl Handler for Adapter { } } -impl LambdaHandler, LambdaResponse> for Adapter { +impl<'a, H: Handler<'a>> LambdaHandler, LambdaResponse> for Adapter<'a, H> { type Error = H::Error; - type Fut = TransformResponse; + type Fut = TransformResponse<'a, H::Response, Self::Error>; fn call(&self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { let request_origin = event.request_origin(); From 7e2cd97b37436234c621dd6057ea99497ebd5c96 Mon Sep 17 00:00:00 2001 From: kenshih Date: Tue, 27 Jul 2021 12:58:30 -0400 Subject: [PATCH 038/394] Fix sam local support for missing headers (#38) (#332) * Added unit tests for TryFrom for Context --- lambda-runtime/src/types.rs | 105 +++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index c8579250..bee81194 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,5 +1,5 @@ use crate::{Config, Error}; -use http::HeaderMap; +use http::{HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, convert::TryFrom}; @@ -120,21 +120,27 @@ impl TryFrom for Context { type Error = Error; fn try_from(headers: HeaderMap) -> Result { let ctx = Context { - request_id: headers["lambda-runtime-aws-request-id"] - .to_str() - .expect("Missing Request ID") + request_id: headers + .get("lambda-runtime-aws-request-id") + .expect("missing lambda-runtime-aws-request-id header") + .to_str()? .to_owned(), - deadline: headers["lambda-runtime-deadline-ms"] + deadline: headers + .get("lambda-runtime-deadline-ms") + .expect("missing lambda-runtime-deadline-ms header") + .to_str()? + .parse::()?, + invoked_function_arn: headers + .get("lambda-runtime-invoked-function-arn") + .unwrap_or(&HeaderValue::from_static( + "No header lambda-runtime-invoked-function-arn found.", + )) .to_str()? - .parse() - .expect("Missing deadline"), - invoked_function_arn: headers["lambda-runtime-invoked-function-arn"] - .to_str() - .expect("Missing arn; this is a bug") .to_owned(), - xray_trace_id: headers["lambda-runtime-trace-id"] - .to_str() - .expect("Invalid XRayTraceID sent by Lambda; this is a bug") + xray_trace_id: headers + .get("lambda-runtime-trace-id") + .unwrap_or(&HeaderValue::from_static("No header lambda-runtime-trace-id found.")) + .to_str()? .to_owned(), ..Default::default() }; @@ -142,6 +148,79 @@ impl TryFrom for Context { } } +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn context_with_expected_values_and_types_resolves() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + let tried = Context::try_from(headers); + assert!(tried.is_ok()); + } + + #[test] + fn context_with_certain_missing_headers_still_resolves() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + let tried = Context::try_from(headers); + assert!(tried.is_ok()); + } + + #[test] + fn context_with_bad_deadline_type_is_err() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert( + "lambda-runtime-deadline-ms", + HeaderValue::from_static("BAD-Type,not "), + ); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + let tried = Context::try_from(headers); + assert!(tried.is_err()); + } + + #[test] + #[should_panic] + #[allow(unused_must_use)] + fn context_with_missing_request_id_should_panic() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + Context::try_from(headers); + } + + #[test] + #[should_panic] + #[allow(unused_must_use)] + fn context_with_missing_deadline_should_panic() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + Context::try_from(headers); + } +} + impl Context { /// Add environment details to the context by setting `env_config`. pub fn with_config(self, config: &Config) -> Self { From 95468c1057c553e37df1403886198d908f440d20 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Thu, 5 Aug 2021 09:59:58 -0700 Subject: [PATCH 039/394] Increment crate versions to v0.3.1 (#336) --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4cf2ba8a..90d16079 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.3.0" +version = "0.3.1" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index b2d68246..f218f196 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.3.0" +version = "0.3.1" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" From b3d069901eedbe62304a84fc54b9e027edb21326 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Fri, 6 Aug 2021 12:09:29 -0700 Subject: [PATCH 040/394] Incrementing to 0.4.0 (#339) --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 90d16079..e5b544a8 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.3.1" +version = "0.4.0" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -19,7 +19,7 @@ maintenance = { status = "actively-developed" } [dependencies] base64 = "0.13.0" http = "0.2" -lambda_runtime = { path = "../lambda-runtime", version = "0.3" } +lambda_runtime = { path = "../lambda-runtime", version = "0.4" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index f218f196..11d09d3a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.3.1" +version = "0.4.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" From 324c5cec6dd605b01c5b8fd7f1570d3b16ea2f63 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Thu, 26 Aug 2021 10:47:45 +0800 Subject: [PATCH 041/394] set _X_AMZN_TRACE_ID environment variable for x-ray (#341) --- lambda-runtime/src/lib.rs | 3 +++ lambda-runtime/src/types.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 4f2497cb..ec145dcc 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -158,6 +158,9 @@ where trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose let body = serde_json::from_slice(&body)?; + let xray_trace_id = &ctx.xray_trace_id.clone(); + env::set_var("_X_AMZN_TRACE_ID", xray_trace_id); + let request_id = &ctx.request_id.clone(); let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(body, ctx))); diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index bee81194..ad894971 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -139,7 +139,7 @@ impl TryFrom for Context { .to_owned(), xray_trace_id: headers .get("lambda-runtime-trace-id") - .unwrap_or(&HeaderValue::from_static("No header lambda-runtime-trace-id found.")) + .unwrap_or(&HeaderValue::from_static("")) .to_str()? .to_owned(), ..Default::default() From 4dd68177e7ca5701c6963e46afcacc8b24fd6820 Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Wed, 8 Sep 2021 08:54:08 -0700 Subject: [PATCH 042/394] Updated versions to 0.4.1 for release to crates.io (#343) --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index e5b544a8..7ce1fdaf 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.4.0" +version = "0.4.1" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -19,7 +19,7 @@ maintenance = { status = "actively-developed" } [dependencies] base64 = "0.13.0" http = "0.2" -lambda_runtime = { path = "../lambda-runtime", version = "0.4" } +lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 11d09d3a..a499be03 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.4.0" +version = "0.4.1" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" From 3871cb4c8fa0319eb157c15eb9e443b1b7526dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Monniot?= Date: Wed, 8 Sep 2021 13:36:31 -0700 Subject: [PATCH 043/394] Remove Send bound in http's Handler (#344) --- lambda-http/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index af49fc08..1e532b0d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -94,7 +94,7 @@ pub trait Handler<'a>: Sized { /// The type of Response this Handler will return type Response: IntoResponse; /// The type of Future this Handler will return - type Fut: Future> + Send + 'a; + type Fut: Future> + 'a; /// Function used to execute handler behavior fn call(&self, event: Request, context: Context) -> Self::Fut; } @@ -112,7 +112,7 @@ impl<'a, F, R, Fut> Handler<'a> for F where F: Fn(Request, Context) -> Fut, R: IntoResponse, - Fut: Future> + Send + 'a, + Fut: Future> + 'a, { type Response = R; type Error = Error; @@ -125,7 +125,7 @@ where #[doc(hidden)] pub struct TransformResponse<'a, R, E> { request_origin: RequestOrigin, - fut: Pin> + Send + 'a>>, + fut: Pin> + 'a>>, } impl<'a, R, E> Future for TransformResponse<'a, R, E> From f7fb932843c69583172b8b096e43a90e311bbabc Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Thu, 21 Oct 2021 14:19:39 -0500 Subject: [PATCH 044/394] fix: use correct name of lambda_runtime example (#352) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9891b2b..92722c97 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ $ cargo build -p lambda_runtime --example basic --release --target x86_64-unknow For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. ```bash -$ cp ./target/x86_64-unknown-linux-musl/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +$ cp ./target/x86_64-unknown-linux-musl/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap ``` Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! From 40e2658804a75377b96a199a18f195122828c38d Mon Sep 17 00:00:00 2001 From: Simon Vandel Sillesen Date: Thu, 21 Oct 2021 21:23:45 +0200 Subject: [PATCH 045/394] Remove seemingly unnecessary Req -> Parts -> Req conversion (#353) Note: I haven't tested this on AWS Lambda - just noticed it while reading the source. --- lambda-runtime/src/client.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/lambda-runtime/src/client.rs b/lambda-runtime/src/client.rs index 2b860e87..5e39e300 100644 --- a/lambda-runtime/src/client.rs +++ b/lambda-runtime/src/client.rs @@ -44,8 +44,6 @@ where pub(crate) async fn call(&self, req: Request) -> Result, Error> { let req = self.set_origin(req)?; - let (parts, body) = req.into_parts(); - let req = Request::from_parts(parts, body); let response = self.client.request(req).await?; Ok(response) } From ba9040ceec6dd1cd1273cb2d0359f0f504f5417b Mon Sep 17 00:00:00 2001 From: Sean Pianka <15352684+seanpianka@users.noreply.github.com> Date: Sat, 30 Oct 2021 20:01:14 -0400 Subject: [PATCH 046/394] build: BREAKING CHANGE: upgrade tracing-[error|subscriber] (#358) Signed-off-by: Sean Pianka --- lambda-runtime/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index a499be03..4d0de675 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -23,13 +23,13 @@ bytes = "1.0" http = "0.2" async-stream = "0.3" futures = "0.3" -tracing-error = "0.1.2" +tracing-error = "0.2" tracing = { version = "0.1", features = ["log"] } tower-service = "0.3" tokio-stream = "0.1.2" [dev-dependencies] -tracing-subscriber = "0.2" +tracing-subscriber = "0.3" once_cell = "1.4.0" simple_logger = "1.6.0" log = "^0.4" From 146d35cc5044e469f852146092c061a988bb2f9e Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Sat, 30 Oct 2021 19:18:04 -0700 Subject: [PATCH 047/394] Fixed cargo clippy warnings (#359) --- lambda-http/src/request.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 352ba780..5fa5a2b7 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -363,7 +363,7 @@ where T: Default + Deserialize<'de>, { let opt = Option::deserialize(deserializer)?; - Ok(opt.unwrap_or_else(T::default)) + Ok(opt.unwrap_or_default()) } /// Converts LambdaRequest types into `http::Request` types @@ -397,7 +397,7 @@ impl<'a> From> for http::Request { headers .get("X-Forwarded-Proto") .and_then(|val| val.to_str().ok()) - .unwrap_or_else(|| "https"), + .unwrap_or("https"), headers .get(http::header::HOST) .and_then(|val| val.to_str().ok()) @@ -445,7 +445,7 @@ impl<'a> From> for http::Request { headers .get("X-Forwarded-Proto") .and_then(|val| val.to_str().ok()) - .unwrap_or_else(|| "https"), + .unwrap_or("https"), headers .get(http::header::HOST) .and_then(|val| val.to_str().ok()) @@ -508,7 +508,7 @@ impl<'a> From> for http::Request { headers .get("X-Forwarded-Proto") .and_then(|val| val.to_str().ok()) - .unwrap_or_else(|| "https"), + .unwrap_or("https"), headers .get(http::header::HOST) .and_then(|val| val.to_str().ok()) @@ -609,7 +609,7 @@ mod tests { // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request // note: file paths are relative to the directory of the crate at runtime let result = from_reader(File::open("tests/data/apigw_proxy_request.json").expect("expected file")); - assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); + assert!(result.is_ok(), "event was not parsed as expected {:?}", result); } #[test] @@ -620,7 +620,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event was not parsed as expected {:?} given {}", result, input) + "event was not parsed as expected {:?} given {}", + result, + input ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -635,7 +637,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event was not parsed as expected {:?} given {}", result, input) + "event was not parsed as expected {:?} given {}", + result, + input ); let req = result.expect("failed to parse request"); let cookie_header = req @@ -658,7 +662,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event was not parsed as expected {:?} given {}", result, input) + "event was not parsed as expected {:?} given {}", + result, + input ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -676,7 +682,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event was not parsed as expected {:?} given {}", result, input) + "event was not parsed as expected {:?} given {}", + result, + input ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -691,7 +699,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event is was not parsed as expected {:?} given {}", result, input) + "event is was not parsed as expected {:?} given {}", + result, + input ); let request = result.expect("failed to parse request"); @@ -712,7 +722,9 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - format!("event is was not parsed as expected {:?} given {}", result, input) + "event is was not parsed as expected {:?} given {}", + result, + input ); let request = result.expect("failed to parse request"); assert!(!request.query_string_parameters().is_empty()); From 2dca015860baf766c0ea756e710c2818a227194f Mon Sep 17 00:00:00 2001 From: Muhammad Bhatti Date: Wed, 3 Nov 2021 02:43:08 +0100 Subject: [PATCH 048/394] make handler reference mutable (#356) --- lambda-http/src/lib.rs | 8 ++++---- lambda-runtime/src/lib.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 1e532b0d..bfdb8f1d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -96,7 +96,7 @@ pub trait Handler<'a>: Sized { /// The type of Future this Handler will return type Fut: Future> + 'a; /// Function used to execute handler behavior - fn call(&self, event: Request, context: Context) -> Self::Fut; + fn call(&mut self, event: Request, context: Context) -> Self::Fut; } /// Adapts a [`Handler`](trait.Handler.html) to the `lambda_runtime::run` interface @@ -117,7 +117,7 @@ where type Response = R; type Error = Error; type Fut = Fut; - fn call(&self, event: Request, context: Context) -> Self::Fut { + fn call(&mut self, event: Request, context: Context) -> Self::Fut { (self)(event, context) } } @@ -159,7 +159,7 @@ impl<'a, H: Handler<'a>> Handler<'a> for Adapter<'a, H> { type Response = H::Response; type Error = H::Error; type Fut = H::Fut; - fn call(&self, event: Request, context: Context) -> Self::Fut { + fn call(&mut self, event: Request, context: Context) -> Self::Fut { self.handler.call(event, context) } } @@ -168,7 +168,7 @@ impl<'a, H: Handler<'a>> LambdaHandler, LambdaResponse> for Ad type Error = H::Error; type Fut = TransformResponse<'a, H::Response, Self::Error>; - fn call(&self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { + fn call(&mut self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { let request_origin = event.request_origin(); let fut = Box::pin(self.handler.call(event.into(), context)); TransformResponse { request_origin, fut } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index ec145dcc..d7e7f52c 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -75,7 +75,7 @@ pub trait Handler { /// Response of this handler. type Fut: Future>; /// Handle the incoming event. - fn call(&self, event: A, context: Context) -> Self::Fut; + fn call(&mut self, event: A, context: Context) -> Self::Fut; } /// Returns a new [`HandlerFn`] with the given closure. @@ -101,7 +101,7 @@ where { type Error = Error; type Fut = Fut; - fn call(&self, req: A, ctx: Context) -> Self::Fut { + fn call(&mut self, req: A, ctx: Context) -> Self::Fut { (self.f)(req, ctx) } } @@ -135,7 +135,7 @@ where pub async fn run( &self, incoming: impl Stream, Error>> + Send, - handler: F, + mut handler: F, config: &Config, ) -> Result<(), Error> where From dcc33ea9e3fba5750f582ff5307a26ee320d70ed Mon Sep 17 00:00:00 2001 From: Colton Weaver Date: Tue, 2 Nov 2021 18:48:59 -0700 Subject: [PATCH 049/394] =?UTF-8?q?Expanded=20README=20on=20how=20to=20bui?= =?UTF-8?q?ld=20for=20Amazon=20Linux=202=20x86=20or=20ARM,=20includ?= =?UTF-8?q?=E2=80=A6=20(#360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expanded README on how to build for Amazon Linux 2 x86 or ARM, including instructions for compiling on MacOS * Fixed misses in PR * Update README.md --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 92722c97..a9616f1a 100644 --- a/README.md +++ b/README.md @@ -29,23 +29,66 @@ async fn func(event: Value, _: Context) -> Result { } ``` -The code above is the same as the [basic example](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda-runtime/examples/basic.rs) in the `lambda_runtime` crate. - ### Deployment There are currently multiple ways of building this package: manually with the AWS CLI, and with the [Serverless framework](https://serverless.com/framework/). #### AWS CLI -To deploy the basic sample as a Lambda function using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html), we first need to manually build it with [`cargo`](https://doc.rust-lang.org/cargo/). Since Lambda uses Amazon Linux, you'll need to target your executable for an `x86_64-unknown-linux-musl` platform. +To deploy the basic sample as a Lambda function using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html), we first need to manually build it with [`cargo`](https://doc.rust-lang.org/cargo/). Due to a few differences in dependencies, the process for building for Amazon Linux 2 is slightly different than building for Amazon Linux. + +**Building for Amazon Linux 2** + +Decide which target you'd like to use. For ARM Lambda Functions you'll want to use `aarch64-unknown-linux-gnu` and for x86 Lambda Functions you'll want to use `x86_64-unknown-linux-gnu`. + +Run this script once to add your desired target, in this example we'll use x86: + +```bash +$ rustup target add x86_64-unknown-linux-gnu +``` + +Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: + +```bash +$ cargo build -p lambda_runtime --example basic --release --target x86_64-unknown-linux-gnu +``` + +_Building on MacOS Using Docker_ + +At the time of writing, the ability to cross compile to x86 or aarch64 AL2 on MacOS is limited. The most robust way we've found is using Docker to produce the artifacts for you. This guide will work for both Intel and Apple Silicon MacOS and requires that you have set up Docker correctly for either architecture. [See here for a guide on how to do this.](https://docs.docker.com/desktop/mac/install/) + +The following command will pull the [official Rust Docker Image](https://hub.docker.com/_/rust) for a given architecture you plan to use in Lambda and use it to run any cargo commands you need, such as build. + +```bash +$ LAMBDA_ARCH="linux/arm64" # set this to either linux/arm64 for ARM functions, or linux/amd64 for x86 functions. +$ RUST_TARGET="aarch64-unknown-linux-gnu" # corresponding with the above, set this to aarch64 or x86_64 -unknown-linux-gnu for ARM or x86 functions. +$ RUST_VERSION="latest" # Set this to a specific version of rust you want to compile for, or to latest if you want the latest stable version. +$ docker run \ + --platform ${LAMBDA_ARCH} \ + --rm --user "$(id -u)":"$(id -g)" \ + -v "${PWD}":/usr/src/myapp -w /usr/src/myapp rust:${RUST_VERSION} \ + cargo build -p lambda_runtime --example basic --release --target ${RUST_TARGET} # This line can be any cargo command +``` + +In short, the above command does the following: + +1. Gives the current user ownership of the artifacts produced by the cargo run. +2. Mounts the current working directory as a volume within the pulled Docker image. +3. Pulls a given Rust Docker image for a given platform. +4. Executes the command on the line beginning with `cargo` within the project directory within the image. + +It is important to note that build artifacts produced from the above command can be found under the expected `target/` directory in the project after build. + +**Building for Amazon Linux 1** Run this script once to add the new target: ```bash $ rustup target add x86_64-unknown-linux-musl ``` +* **Note:** If you are running on Mac OS you'll need to install the linker for the target platform. You do this using the `musl-cross` tap from [Homebrew](https://brew.sh/) which provides a complete cross-compilation toolchain for Mac OS. Once `musl-cross` is installed we will also need to inform cargo of the newly installed linker when building for the `x86_64-unknown-linux-musl` platform. + - * **Note:** If you are running on Mac OS you'll need to install the linker for the target platform. You do this using the `musl-cross` tap from [Homebrew](https://brew.sh/) which provides a complete cross-compilation toolchain for Mac OS. Once `musl-cross` is installed we will also need to inform cargo of the newly installed linker when building for the `x86_64-unknown-linux-musl` platform. ```bash $ brew install filosottile/musl-cross/musl-cross $ mkdir .cargo @@ -57,10 +100,14 @@ Compile one of the examples as a _release_ with a specific _target_ for deployme $ cargo build -p lambda_runtime --example basic --release --target x86_64-unknown-linux-musl ``` +**Uploading the Resulting Binary to Lambda** + For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. +NOTE: Depending on the target you used above, you'll find the provided basic executable under the corresponding directory. In the following example, we've compiled for x86_64-unknown-linux-gnu. + ```bash -$ cp ./target/x86_64-unknown-linux-musl/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +$ cp ./target/x86_64-unknown-linux-gnu/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap ``` Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! @@ -69,7 +116,7 @@ Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](h $ aws lambda create-function --function-name rustTest \ --handler doesnt.matter \ --zip-file fileb://./lambda.zip \ - --runtime provided \ + --runtime provided.al2 \ # Change this to provided.al if you would like to use Amazon Linux 1. --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ --tracing-config Mode=Active @@ -79,9 +126,9 @@ You can now test the function using the AWS CLI or the AWS Lambda console ```bash $ aws lambda invoke --function-name rustTest \ - --payload '{"firstName": "world"}' \ + --payload '{"command": "Say Hi!"}' \ output.json -$ cat output.json # Prints: {"message": "Hello, world!"} +$ cat output.json # Prints: {"msg": "Command Say Hi! executed."} ``` **Note:** `--cli-binary-format raw-in-base64-out` is a required From c948939caf0ddb0c5e74567f7bddd3c0bb7090c2 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Mon, 15 Nov 2021 16:46:56 +0100 Subject: [PATCH 050/394] feat: inject user agent in Lambda runtime API calls (#363) --- lambda-runtime/src/requests.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index a89f1689..8aa2edbe 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -4,6 +4,8 @@ use hyper::Body; use serde::Serialize; use std::str::FromStr; +const USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); + pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; } @@ -20,6 +22,7 @@ impl IntoRequest for NextEventRequest { fn into_req(self) -> Result, Error> { let req = Request::builder() .method(Method::GET) + .header("User-Agent", USER_AGENT) .uri(Uri::from_static("/2018-06-01/runtime/invocation/next")) .body(Body::empty())?; Ok(req) @@ -57,6 +60,10 @@ fn test_next_event_request() { let req = req.into_req().unwrap(); assert_eq!(req.method(), Method::GET); assert_eq!(req.uri(), &Uri::from_static("/2018-06-01/runtime/invocation/next")); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); } // /runtime/invocation/{AwsRequestId}/response @@ -75,7 +82,11 @@ where let body = serde_json::to_vec(&self.body)?; let body = Body::from(body); - let req = Request::builder().method(Method::POST).uri(uri).body(body)?; + let req = Request::builder() + .header("User-Agent", USER_AGENT) + .method(Method::POST) + .uri(uri) + .body(body)?; Ok(req) } } @@ -90,6 +101,10 @@ fn test_event_completion_request() { let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/response"); assert_eq!(req.method(), Method::POST); assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); } // /runtime/invocation/{AwsRequestId}/error @@ -108,6 +123,7 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { let req = Request::builder() .method(Method::POST) .uri(uri) + .header("User-Agent", USER_AGENT) .header("lambda-runtime-function-error-type", "unhandled") .body(body)?; Ok(req) @@ -127,6 +143,10 @@ fn test_event_error_request() { let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/error"); assert_eq!(req.method(), Method::POST); assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); } // /runtime/init/error @@ -140,6 +160,7 @@ impl IntoRequest for InitErrorRequest { let req = Request::builder() .method(Method::POST) .uri(uri) + .header("User-Agent", USER_AGENT) .header("lambda-runtime-function-error-type", "unhandled") .body(Body::empty())?; Ok(req) @@ -153,4 +174,8 @@ fn test_init_error_request() { let expected = Uri::from_static("/2018-06-01/runtime/init/error"); assert_eq!(req.method(), Method::POST); assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); } From 02a29ff1512fb3899a61704bb7b30ca17ebbfb9a Mon Sep 17 00:00:00 2001 From: Jalal El Mansouri Date: Tue, 16 Nov 2021 16:39:21 -0500 Subject: [PATCH 051/394] Handler lifetime fix (#364) --- lambda-http/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index bfdb8f1d..ec80c30b 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -5,7 +5,7 @@ //! //! This crate abstracts over all of these trigger events using standard [`http`](https://github.com/hyperium/http) types minimizing the mental overhead //! 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 -//! transparently use whichever lambda trigger suits your application and cost optimiztions best. +//! transparently use whichever lambda trigger suits your application and cost optimizations best. //! //! # Examples //! @@ -149,7 +149,7 @@ where /// It serves as a opaque trait covering type. /// /// See [this article](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/) -/// for a larger explaination of why this is nessessary +/// for a larger explanation of why this is necessary pub struct Adapter<'a, H: Handler<'a>> { handler: H, _pd: PhantomData<&'a H>, @@ -164,7 +164,7 @@ impl<'a, H: Handler<'a>> Handler<'a> for Adapter<'a, H> { } } -impl<'a, H: Handler<'a>> LambdaHandler, LambdaResponse> for Adapter<'a, H> { +impl<'a, 'b, H: Handler<'a>> LambdaHandler, LambdaResponse> for Adapter<'a, H> { type Error = H::Error; type Fut = TransformResponse<'a, H::Response, Self::Error>; From 576f6013dd188de08928dd503170b525bc17c26e Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Wed, 17 Nov 2021 18:10:03 +0100 Subject: [PATCH 052/394] fix: null deserialization of stageVariables for sam local (#366) --- lambda-http/src/request.rs | 47 +++++++++++++++++-- .../tests/data/apigw_v2_sam_local.json | 46 ++++++++++++++++++ 2 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 lambda-http/tests/data/apigw_v2_sam_local.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 5fa5a2b7..6bef39ce 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -33,11 +33,11 @@ pub enum LambdaRequest<'a> { cookies: Option>>, #[serde(deserialize_with = "deserialize_headers")] headers: http::HeaderMap, - #[serde(default)] + #[serde(default, deserialize_with = "nullable_default")] query_string_parameters: StrMap, - #[serde(default)] + #[serde(default, deserialize_with = "nullable_default")] path_parameters: StrMap, - #[serde(default)] + #[serde(default, deserialize_with = "nullable_default")] stage_variables: StrMap, body: Option>, #[serde(default)] @@ -55,7 +55,7 @@ pub enum LambdaRequest<'a> { /// the `lambda.multi_value_headers.enabled` target group setting turned on #[serde(default, deserialize_with = "deserialize_multi_value_headers")] multi_value_headers: http::HeaderMap, - #[serde(deserialize_with = "nullable_default")] + #[serde(default, deserialize_with = "nullable_default")] query_string_parameters: StrMap, /// For alb events these are only present when /// the `lambda.multi_value_headers.enabled` target group setting turned on @@ -75,7 +75,7 @@ pub enum LambdaRequest<'a> { headers: http::HeaderMap, #[serde(default, deserialize_with = "deserialize_multi_value_headers")] multi_value_headers: http::HeaderMap, - #[serde(deserialize_with = "nullable_default")] + #[serde(default, deserialize_with = "nullable_default")] query_string_parameters: StrMap, #[serde(default, deserialize_with = "nullable_default")] multi_value_query_string_parameters: StrMap, @@ -736,6 +736,29 @@ mod tests { ); } + #[test] + fn deserialize_apigw_v2_sam_local() { + // manually generated from AWS SAM CLI + // Steps to recreate: + // * sam init + // * Use, Zip Python 3.9, and Hello World example + // * Change the template to use HttpApi instead of Api + // * Change the function code to return the Lambda event serialized + // * sam local start-api + // * Invoke the API + let input = include_str!("../tests/data/apigw_v2_sam_local.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {:?} given {}", + result, + input + ); + let req = result.expect("failed to parse request"); + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), "http://127.0.0.1:3000/hello"); + } + #[test] fn deserialize_with_null() { #[derive(Debug, PartialEq, Deserialize)] @@ -749,4 +772,18 @@ mod tests { Test { foo: HashMap::new() } ) } + + #[test] + fn deserialize_with_missing() { + #[derive(Debug, PartialEq, Deserialize)] + struct Test { + #[serde(default, deserialize_with = "nullable_default")] + foo: HashMap, + } + + assert_eq!( + serde_json::from_str::(r#"{}"#).expect("failed to deserialize"), + Test { foo: HashMap::new() } + ) + } } diff --git a/lambda-http/tests/data/apigw_v2_sam_local.json b/lambda-http/tests/data/apigw_v2_sam_local.json new file mode 100644 index 00000000..4043b7a1 --- /dev/null +++ b/lambda-http/tests/data/apigw_v2_sam_local.json @@ -0,0 +1,46 @@ +{ + "version": "2.0", + "routeKey": "GET /hello", + "rawPath": "/hello", + "rawQueryString": "", + "cookies": [], + "headers": { + "Host": "127.0.0.1:3000", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate", + "Connection": "keep-alive", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Cache-Control": "max-age=0", + "X-Forwarded-Proto": "http", + "X-Forwarded-Port": "3000" + }, + "queryStringParameters": {}, + "requestContext": { + "accountId": "123456789012", + "apiId": "1234567890", + "http": { + "method": "GET", + "path": "/hello", + "protocol": "HTTP/1.1", + "sourceIp": "127.0.0.1", + "userAgent": "Custom User Agent String" + }, + "requestId": "1ac06eee-f687-44ec-9036-dfd49d0be0a3", + "routeKey": "GET /hello", + "stage": "$default", + "time": "16/Nov/2021:11:54:33 +0000", + "timeEpoch": 1637063673, + "domainName": "localhost", + "domainPrefix": "localhost" + }, + "body": "", + "pathParameters": {}, + "stageVariables": null, + "isBase64Encoded": false +} \ No newline at end of file From eeb849be6b8ea676fdd6aa0815c310ca8736f35e Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sat, 20 Nov 2021 22:15:24 +0100 Subject: [PATCH 053/394] =?UTF-8?q?fix:=20create=20a=20valid=20URL=20for?= =?UTF-8?q?=20REST=20APIs=20and=20ALBs=20when=20the=20host=20header=20i?= =?UTF-8?q?=E2=80=A6=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: create a valid URL for REST APIs and ALBs when the host header is missing * chore: cargo fmt --- lambda-http/src/request.rs | 70 +++++++++++++++-------- lambda-http/tests/data/apigw_no_host.json | 54 +++++++++++++++++ 2 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 lambda-http/tests/data/apigw_no_host.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 6bef39ce..e5e3cd8d 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -440,18 +440,21 @@ impl<'a> From> for http::Request { let builder = http::Request::builder() .method(http_method) .uri({ - format!( - "{}://{}{}", - headers - .get("X-Forwarded-Proto") - .and_then(|val| val.to_str().ok()) - .unwrap_or("https"), - headers - .get(http::header::HOST) - .and_then(|val| val.to_str().ok()) - .unwrap_or_default(), - path - ) + let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); + match host { + Some(host) => { + format!( + "{}://{}{}", + headers + .get("X-Forwarded-Proto") + .and_then(|val| val.to_str().ok()) + .unwrap_or("https"), + host, + path + ) + } + None => path.to_string(), + } }) // multi-valued query string parameters are always a super // set of singly valued query string parameters, @@ -503,18 +506,21 @@ impl<'a> From> for http::Request { let builder = http::Request::builder() .method(http_method) .uri({ - format!( - "{}://{}{}", - headers - .get("X-Forwarded-Proto") - .and_then(|val| val.to_str().ok()) - .unwrap_or("https"), - headers - .get(http::header::HOST) - .and_then(|val| val.to_str().ok()) - .unwrap_or_default(), - path - ) + let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); + match host { + Some(host) => { + format!( + "{}://{}{}", + headers + .get("X-Forwarded-Proto") + .and_then(|val| val.to_str().ok()) + .unwrap_or("https"), + host, + path + ) + } + None => path.to_string(), + } }) // multi valued query string parameters are always a super // set of singly valued query string parameters, @@ -759,6 +765,22 @@ mod tests { assert_eq!(req.uri(), "http://127.0.0.1:3000/hello"); } + #[test] + fn deserialize_apigw_no_host() { + // generated from the 'apigateway-aws-proxy' test event template in the Lambda console + let input = include_str!("../tests/data/apigw_no_host.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {:?} given {}", + result, + input + ); + let req = result.expect("failed to parse request"); + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), "/test/hello"); + } + #[test] fn deserialize_with_null() { #[derive(Debug, PartialEq, Deserialize)] diff --git a/lambda-http/tests/data/apigw_no_host.json b/lambda-http/tests/data/apigw_no_host.json new file mode 100644 index 00000000..369c4870 --- /dev/null +++ b/lambda-http/tests/data/apigw_no_host.json @@ -0,0 +1,54 @@ +{ + "path": "/test/hello", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, lzma, sdch, br", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", + "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==", + "X-Forwarded-For": "192.168.100.1, 192.168.1.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "pathParameters": { + "proxy": "hello" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "us4z18", + "stage": "test", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9", + "identity": { + "cognitoIdentityPoolId": "", + "accountId": "", + "cognitoIdentityId": "", + "caller": "", + "apiKey": "", + "sourceIp": "192.168.100.1", + "cognitoAuthenticationType": "", + "cognitoAuthenticationProvider": "", + "userArn": "", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", + "user": "" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "apiId": "wt6mne2s9k" + }, + "resource": "/{proxy+}", + "httpMethod": "GET", + "queryStringParameters": { + "name": "me" + }, + "stageVariables": { + "stageVarName": "stageVarValue" + } +} \ No newline at end of file From 154cf312a0b4d7ffec2fa035695151106a44f286 Mon Sep 17 00:00:00 2001 From: mx Date: Mon, 22 Nov 2021 06:38:53 +1300 Subject: [PATCH 054/394] Removed references to type Error aliases in docs (#370) --- lambda-http/src/ext.rs | 4 +--- lambda-runtime/src/lib.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 13a3be52..be094c5a 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -66,11 +66,9 @@ impl Error for PayloadError { /// as well as `{"x":1, "y":2}` respectively. /// /// ```rust,no_run -/// use lambda_http::{handler, lambda_runtime::{self, Context}, Body, IntoResponse, Request, Response, RequestExt}; +/// use lambda_http::{handler, lambda_runtime::{self, Error, Context}, Body, IntoResponse, Request, Response, RequestExt}; /// use serde::Deserialize; /// -/// type Error = Box; -/// /// #[derive(Debug,Deserialize,Default)] /// struct Args { /// #[serde(default)] diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index d7e7f52c..30220e45 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -281,11 +281,9 @@ where /// /// # Example /// ```no_run -/// use lambda_runtime::{handler_fn, Context}; +/// use lambda_runtime::{Error, handler_fn, Context}; /// use serde_json::Value; /// -/// type Error = Box; -/// /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// let func = handler_fn(func); From 6b1b3f0c348386a91fe9129a1b79f3e1d15ae1a7 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sun, 28 Nov 2021 11:00:14 +0100 Subject: [PATCH 055/394] fix: add support for null/invalid type for headers (#371) --- lambda-http/src/request.rs | 62 +++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index e5e3cd8d..88740dd1 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -352,7 +352,7 @@ where } } - deserializer.deserialize_map(HeaderVisitor) + Ok(deserializer.deserialize_map(HeaderVisitor).unwrap_or_default()) } /// deserializes (json) null values to their default values @@ -633,6 +633,17 @@ mod tests { let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), "https://xxx.execute-api.us-east-1.amazonaws.com/"); + + // Ensure this is an APIGWv2 request + let req_context = req.request_context(); + assert!( + match req_context { + RequestContext::ApiGatewayV2(_) => true, + _ => false, + }, + "expected ApiGatewayV2 context, got {:?}", + req_context + ); } #[test] @@ -657,6 +668,17 @@ mod tests { assert_eq!(req.method(), "POST"); assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value"); assert_eq!(cookie_header, Ok("cookie1=value1;cookie2=value2")); + + // Ensure this is an APIGWv2 request + let req_context = req.request_context(); + assert!( + match req_context { + RequestContext::ApiGatewayV2(_) => true, + _ => false, + }, + "expected ApiGatewayV2 context, got {:?}", + req_context + ); } #[test] @@ -678,6 +700,17 @@ mod tests { req.uri(), "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello" ); + + // Ensure this is an APIGW request + let req_context = req.request_context(); + assert!( + match req_context { + RequestContext::ApiGateway(_) => true, + _ => false, + }, + "expected ApiGateway context, got {:?}", + req_context + ); } #[test] @@ -695,6 +728,17 @@ mod tests { let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), "https://lambda-846800462-us-east-2.elb.amazonaws.com/"); + + // Ensure this is an ALB request + let req_context = req.request_context(); + assert!( + match req_context { + RequestContext::Alb(_) => true, + _ => false, + }, + "expected Alb context, got {:?}", + req_context + ); } #[test] @@ -808,4 +852,20 @@ mod tests { Test { foo: HashMap::new() } ) } + + #[test] + fn deserialize_null_headers() { + #[derive(Debug, PartialEq, Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers")] + headers: http::HeaderMap, + } + + assert_eq!( + serde_json::from_str::(r#"{"headers":null}"#).expect("failed to deserialize"), + Test { + headers: http::HeaderMap::new() + } + ) + } } From 5092de1b0c0bba9357fe1a306909e31e1b01bd96 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 11 Dec 2021 02:31:50 -0800 Subject: [PATCH 056/394] Lambda Extensions (#376) * Create new Runtime Client crate Extract the HTTP client logic into a new crate. This allows other crates to share the same logic to interact with the Runtime API. Signed-off-by: David Calavera * Add Lambda Extension crate This new crate encapsulates the logic to create Lambda Extensions. It includes reference examples. Signed-off-by: David Calavera * Rename client crate to avoid confusion. Once upon a time, there was a crate called `lambda-runtime-client`. We don't want people to mistake this new crate with the old one. Signed-off-by: David Calavera * Modify user API. - Remove async_trait dependency. - Use a similar handler api than the runtime. - Allow to register the extension for only certain events. Signed-off-by: David Calavera * Cleanup user API. - Remove extension ID from call signature. - Make extension mutable. - Add example showing how to implement an extension with a struct. Signed-off-by: David Calavera * Add documentation and cleanup code. Signed-off-by: David Calavera * Make custom trait example more useful. Signed-off-by: David Calavera * Fix formatting. Signed-off-by: David Calavera * Remove unused dependencies. Signed-off-by: David Calavera * Add README files for the new crates. Signed-off-by: David Calavera * Update readme files. Signed-off-by: David Calavera * Fix extension name Cleanup the path from the executable when it takes the name from arg[0]. Signed-off-by: David Calavera --- Cargo.toml | 4 +- README.md | 3 + lambda-extension/Cargo.toml | 31 ++ lambda-extension/README.md | 58 +++ lambda-extension/examples/basic.rs | 25 ++ lambda-extension/examples/custom_events.rs | 30 ++ .../examples/custom_trait_implementation.rs | 36 ++ lambda-extension/src/lib.rs | 257 +++++++++++++ lambda-extension/src/requests.rs | 82 +++++ lambda-runtime-api-client/Cargo.toml | 22 ++ lambda-runtime-api-client/README.md | 35 ++ lambda-runtime-api-client/src/lib.rs | 134 +++++++ lambda-runtime/Cargo.toml | 1 + lambda-runtime/src/client.rs | 308 ---------------- lambda-runtime/src/lib.rs | 347 +++++++++++++----- lambda-runtime/src/requests.rs | 18 +- 16 files changed, 986 insertions(+), 405 deletions(-) create mode 100644 lambda-extension/Cargo.toml create mode 100644 lambda-extension/README.md create mode 100644 lambda-extension/examples/basic.rs create mode 100644 lambda-extension/examples/custom_events.rs create mode 100644 lambda-extension/examples/custom_trait_implementation.rs create mode 100644 lambda-extension/src/lib.rs create mode 100644 lambda-extension/src/requests.rs create mode 100644 lambda-runtime-api-client/Cargo.toml create mode 100644 lambda-runtime-api-client/README.md create mode 100644 lambda-runtime-api-client/src/lib.rs delete mode 100644 lambda-runtime/src/client.rs diff --git a/Cargo.toml b/Cargo.toml index 291345b5..01bc46b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ "lambda-http", - "lambda-runtime" + "lambda-runtime-api-client", + "lambda-runtime", + "lambda-extension" ] \ No newline at end of file diff --git a/README.md b/README.md index a9616f1a..96f24476 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. +- [![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. +- [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. + ## Example function diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml new file mode 100644 index 00000000..05f1d6f7 --- /dev/null +++ b/lambda-extension/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "lambda_extension" +version = "0.1.0" +edition = "2021" +authors = ["David Calavera "] +description = "AWS Lambda Extension API" +license = "Apache-2.0" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +categories = ["web-programming::http-server"] +keywords = ["AWS", "Lambda", "API"] +readme = "README.md" + +[dependencies] +tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +serde = { version = "1", features = ["derive"] } +serde_json = "^1" +bytes = "1.0" +http = "0.2" +async-stream = "0.3" +tracing = { version = "0.1", features = ["log"] } +tower-service = "0.3" +tokio-stream = "0.1.2" +lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } + +[dev-dependencies] +tracing-subscriber = "0.3" +once_cell = "1.4.0" +simple_logger = "1.6.0" +log = "^0.4" +simple-error = "0.2" diff --git a/lambda-extension/README.md b/lambda-extension/README.md new file mode 100644 index 00000000..4982779f --- /dev/null +++ b/lambda-extension/README.md @@ -0,0 +1,58 @@ +# Runtime Extensions for AWS Lambda in Rust + +[![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension) + +**`lambda-extension`** is a library that makes it easy to write [AWS Lambda Runtime Extensions](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html) in Rust. + +## Example extension + +The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch. + +```rust,no_run +use lambda_extension::{extension_fn, Error, NextEvent}; +use log::LevelFilter; +use simple_logger::SimpleLogger; +use tracing::info; + +async fn log_extension(event: NextEvent) -> Result<(), Error> { + match event { + NextEvent::Shutdown(event) => { + info!("{}", event); + } + NextEvent::Invoke(event) => { + info!("{}", event); + } + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + let func = extension_fn(log_extension); + lambda_extension::run(func).await +} +``` + +## Deployment + +Lambda extensions can be added to your functions either using [Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#using-extensions-config), or adding them to [containers images](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#invocation-extensions-images). + +Regardless of how you deploy them, the extensions MUST be compiled against the same architecture that your lambda functions runs on. + +### Building extensions + +Once you've decided which target you'll use, you can install it by running the next `rustup` command: + +```bash +$ rustup target add x86_64-unknown-linux-musl +``` + +Then, you can compile the extension against that target: + +```bash +$ cargo build -p lambda_extension --example basic --release --target x86_64-unknown-linux-musl +``` + +This previous command will generate a binary file in `target/x86_64-unknown-linux-musl/release/examples` called `basic`. When the extension is registered with the [Runtime Extensions API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-extensions-api-reg), that's the name that the extension will be registered with. If you want to register the extension with a different name, you only have to rename this binary file and deploy it with the new name. \ No newline at end of file diff --git a/lambda-extension/examples/basic.rs b/lambda-extension/examples/basic.rs new file mode 100644 index 00000000..573b3281 --- /dev/null +++ b/lambda-extension/examples/basic.rs @@ -0,0 +1,25 @@ +use lambda_extension::{extension_fn, Error, NextEvent}; +use log::LevelFilter; +use simple_logger::SimpleLogger; + +async fn my_extension(event: NextEvent) -> Result<(), Error> { + match event { + NextEvent::Shutdown(_e) => { + // do something with the shutdown event + } + NextEvent::Invoke(_e) => { + // do something with the invoke event + } + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + // can be replaced with any other method of initializing `log` + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + let func = extension_fn(my_extension); + lambda_extension::run(func).await +} diff --git a/lambda-extension/examples/custom_events.rs b/lambda-extension/examples/custom_events.rs new file mode 100644 index 00000000..88f040aa --- /dev/null +++ b/lambda-extension/examples/custom_events.rs @@ -0,0 +1,30 @@ +use lambda_extension::{extension_fn, Error, NextEvent, Runtime}; +use log::LevelFilter; +use simple_logger::SimpleLogger; + +async fn my_extension(event: NextEvent) -> Result<(), Error> { + match event { + NextEvent::Shutdown(_e) => { + // do something with the shutdown event + } + _ => { + // ignore any other event + // because we've registered the extension + // only to receive SHUTDOWN events + } + } + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + // can be replaced with any other method of initializing `log` + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + let func = extension_fn(my_extension); + + let runtime = Runtime::builder().with_events(&["SHUTDOWN"]).register().await?; + + runtime.run(func).await +} diff --git a/lambda-extension/examples/custom_trait_implementation.rs b/lambda-extension/examples/custom_trait_implementation.rs new file mode 100644 index 00000000..caef7730 --- /dev/null +++ b/lambda-extension/examples/custom_trait_implementation.rs @@ -0,0 +1,36 @@ +use lambda_extension::{run, Error, Extension, InvokeEvent, NextEvent}; +use log::LevelFilter; +use simple_logger::SimpleLogger; +use std::{ + future::{ready, Future}, + pin::Pin, +}; + +#[derive(Default)] +struct MyExtension { + data: Vec, +} + +impl Extension for MyExtension { + type Fut = Pin>>>; + fn call(&mut self, event: NextEvent) -> Self::Fut { + match event { + NextEvent::Shutdown(_e) => { + self.data.clear(); + } + NextEvent::Invoke(e) => { + self.data.push(e); + } + } + Box::pin(ready(Ok(()))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + // can be replaced with any other method of initializing `log` + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + run(MyExtension::default()).await +} diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs new file mode 100644 index 00000000..41f56890 --- /dev/null +++ b/lambda-extension/src/lib.rs @@ -0,0 +1,257 @@ +#![deny(clippy::all, clippy::cargo)] +#![warn(missing_docs, nonstandard_style, rust_2018_idioms)] + +//! This module includes utilities to create Lambda Runtime Extensions. +//! +//! Create a type that conforms to the [`Extension`] trait. This type can then be passed +//! to the the `lambda_extension::run` function, which launches and runs the Lambda runtime extension. +use hyper::client::{connect::Connection, HttpConnector}; +use lambda_runtime_api_client::Client; +use serde::Deserialize; +use std::future::Future; +use std::path::PathBuf; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_stream::StreamExt; +use tower_service::Service; +use tracing::trace; + +/// Include several request builders to interact with the Extension API. +pub mod requests; + +/// Error type that extensions may result in +pub type Error = lambda_runtime_api_client::Error; + +/// Simple error that encapsulates human readable descriptions +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionError { + err: String, +} + +impl ExtensionError { + fn boxed>(str: T) -> Box { + Box::new(ExtensionError { err: str.into() }) + } +} + +impl std::fmt::Display for ExtensionError { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.err.fmt(f) + } +} + +impl std::error::Error for ExtensionError {} + +/// Request tracing information +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Tracing { + /// The type of tracing exposed to the extension + pub r#type: String, + /// The span value + pub value: String, +} + +/// Event received when there is a new Lambda invocation. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InvokeEvent { + /// The time that the function times out + pub deadline_ms: u64, + /// The ID assigned to the Lambda request + pub request_id: String, + /// The function's Amazon Resource Name + pub invoked_function_arn: String, + /// The request tracing information + pub tracing: Tracing, +} + +/// Event received when a Lambda function shuts down. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShutdownEvent { + /// The reason why the function terminates + /// It can be SPINDOWN, TIMEOUT, or FAILURE + pub shutdown_reason: String, + /// The time that the function times out + pub deadline_ms: u64, +} + +/// Event that the extension receives in +/// either the INVOKE or SHUTDOWN phase +#[derive(Debug, Deserialize)] +#[serde(rename_all = "UPPERCASE", tag = "eventType")] +pub enum NextEvent { + /// Payload when the event happens in the INVOKE phase + Invoke(InvokeEvent), + /// Payload when the event happens in the SHUTDOWN phase + Shutdown(ShutdownEvent), +} + +impl NextEvent { + fn is_invoke(&self) -> bool { + matches!(self, NextEvent::Invoke(_)) + } +} + +/// A trait describing an asynchronous extension. +pub trait Extension { + /// Response of this Extension. + type Fut: Future>; + /// Handle the incoming event. + fn call(&mut self, event: NextEvent) -> Self::Fut; +} + +/// Returns a new [`ExtensionFn`] with the given closure. +/// +/// [`ExtensionFn`]: struct.ExtensionFn.html +pub fn extension_fn(f: F) -> ExtensionFn { + ExtensionFn { f } +} + +/// An [`Extension`] implemented by a closure. +/// +/// [`Extension`]: trait.Extension.html +#[derive(Clone, Debug)] +pub struct ExtensionFn { + f: F, +} + +impl Extension for ExtensionFn +where + F: Fn(NextEvent) -> Fut, + Fut: Future>, +{ + type Fut = Fut; + fn call(&mut self, event: NextEvent) -> Self::Fut { + (self.f)(event) + } +} + +/// The Runtime handles all the incoming extension requests +pub struct Runtime = HttpConnector> { + extension_id: String, + client: Client, +} + +impl Runtime { + /// Create a [`RuntimeBuilder`] to initialize the [`Runtime`] + pub fn builder<'a>() -> RuntimeBuilder<'a> { + RuntimeBuilder::default() + } +} + +impl Runtime +where + C: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + /// Execute the given extension. + /// Register the extension with the Extensions API and wait for incoming events. + pub async fn run(&self, mut extension: impl Extension) -> Result<(), Error> { + let client = &self.client; + + let incoming = async_stream::stream! { + loop { + trace!("Waiting for next event (incoming loop)"); + let req = requests::next_event_request(&self.extension_id)?; + let res = client.call(req).await; + yield res; + } + }; + + tokio::pin!(incoming); + while let Some(event) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = event?; + let (_parts, body) = event.into_parts(); + + let body = hyper::body::to_bytes(body).await?; + trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose + let event: NextEvent = serde_json::from_slice(&body)?; + let is_invoke = event.is_invoke(); + + let res = extension.call(event).await; + if let Err(error) = res { + let req = if is_invoke { + requests::init_error(&self.extension_id, &error.to_string(), None)? + } else { + requests::exit_error(&self.extension_id, &error.to_string(), None)? + }; + + self.client.call(req).await?; + return Err(error); + } + } + + Ok(()) + } +} + +/// Builder to construct a new extension [`Runtime`] +#[derive(Default)] +pub struct RuntimeBuilder<'a> { + extension_name: Option<&'a str>, + events: Option<&'a [&'a str]>, +} + +impl<'a> RuntimeBuilder<'a> { + /// Create a new [`RuntimeBuilder`] with a given extension name + pub fn with_extension_name(self, extension_name: &'a str) -> Self { + RuntimeBuilder { + extension_name: Some(extension_name), + ..self + } + } + + /// Create a new [`RuntimeBuilder`] with a list of given events. + /// The only accepted events are `INVOKE` and `SHUTDOWN`. + pub fn with_events(self, events: &'a [&'a str]) -> Self { + RuntimeBuilder { + events: Some(events), + ..self + } + } + + /// Initialize and register the extension in the Extensions API + pub async fn register(&self) -> Result { + let name = match self.extension_name { + Some(name) => name.into(), + None => { + let args: Vec = std::env::args().collect(); + PathBuf::from(args[0].clone()) + .file_name() + .expect("unexpected executable name") + .to_str() + .expect("unexpect executable name") + .to_string() + } + }; + + let events = self.events.unwrap_or(&["INVOKE", "SHUTDOWN"]); + + let client = Client::builder().build()?; + + let req = requests::register_request(&name, events)?; + let res = client.call(req).await?; + if res.status() != http::StatusCode::OK { + return Err(ExtensionError::boxed("unable to register the extension")); + } + + let extension_id = res.headers().get(requests::EXTENSION_ID_HEADER).unwrap().to_str()?; + Ok(Runtime { + extension_id: extension_id.into(), + client, + }) + } +} + +/// Execute the given extension +pub async fn run(extension: Ex) -> Result<(), Error> +where + Ex: Extension, +{ + Runtime::builder().register().await?.run(extension).await +} diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs new file mode 100644 index 00000000..2fdbf2a6 --- /dev/null +++ b/lambda-extension/src/requests.rs @@ -0,0 +1,82 @@ +use crate::Error; +use http::{Method, Request}; +use hyper::Body; +use lambda_runtime_api_client::build_request; +use serde::Serialize; + +const EXTENSION_NAME_HEADER: &str = "Lambda-Extension-Name"; +pub(crate) const EXTENSION_ID_HEADER: &str = "Lambda-Extension-Identifier"; +const EXTENSION_ERROR_TYPE_HEADER: &str = "Lambda-Extension-Function-Error-Type"; + +pub(crate) fn next_event_request(extension_id: &str) -> Result, Error> { + let req = build_request() + .method(Method::GET) + .header(EXTENSION_ID_HEADER, extension_id) + .uri("/2020-01-01/extension/event/next") + .body(Body::empty())?; + Ok(req) +} + +pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result, Error> { + let events = serde_json::json!({ "events": events }); + + let req = build_request() + .method(Method::POST) + .uri("/2020-01-01/extension/register") + .header(EXTENSION_NAME_HEADER, extension_name) + .body(Body::from(serde_json::to_string(&events)?))?; + + Ok(req) +} + +/// Payload to send error information to the Extensions API. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorRequest<'a> { + /// Human readable error description + pub error_message: &'a str, + /// The type of error to categorize + pub error_type: &'a str, + /// The error backtrace + pub stack_trace: Vec<&'a str>, +} + +/// Create a new init error request to send to the Extensions API +pub fn init_error<'a>( + extension_id: &str, + error_type: &str, + request: Option>, +) -> Result, Error> { + error_request("init", extension_id, error_type, request) +} + +/// Create a new exit error request to send to the Extensions API +pub fn exit_error<'a>( + extension_id: &str, + error_type: &str, + request: Option>, +) -> Result, Error> { + error_request("exit", extension_id, error_type, request) +} + +fn error_request<'a>( + error_type: &str, + extension_id: &str, + error_str: &str, + request: Option>, +) -> Result, Error> { + let uri = format!("/2020-01-01/extension/{}/error", error_type); + + let body = match request { + None => Body::empty(), + Some(err) => Body::from(serde_json::to_string(&err)?), + }; + + let req = build_request() + .method(Method::POST) + .uri(uri) + .header(EXTENSION_ID_HEADER, extension_id) + .header(EXTENSION_ERROR_TYPE_HEADER, error_str) + .body(body)?; + Ok(req) +} diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml new file mode 100644 index 00000000..48188a91 --- /dev/null +++ b/lambda-runtime-api-client/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "lambda_runtime_api_client" +version = "0.4.1" +edition = "2021" +authors = ["David Calavera "] +description = "AWS Lambda Runtime interaction API" +license = "Apache-2.0" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +categories = ["web-programming::http-server"] +keywords = ["AWS", "Lambda", "API"] +readme = "README.md" + +[dependencies] +http = "0.2" +hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +tower-service = "0.3" +tokio = { version = "1.0", features = ["io-util"] } + +[dev-dependencies] +serde_json = "^1" +async-stream = "0.3" +tokio-stream = "0.1.2" \ No newline at end of file diff --git a/lambda-runtime-api-client/README.md b/lambda-runtime-api-client/README.md new file mode 100644 index 00000000..530fefdd --- /dev/null +++ b/lambda-runtime-api-client/README.md @@ -0,0 +1,35 @@ +# AWS Lambda Runtime API Client + +[![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) + +**`lambda-runtime-api-client`** is a library to interact with the AWS Lambda Runtime API. + +This crate provides simple building blocks to send REST request to this API. You probably don't need to use this crate directly, look at [lambda_runtime](https://docs.rs/lambda_runtime) and [lambda_extension](https://docs.rs/lambda_extension) instead. + +## Example + +```rust,no_run +use http::{Method, Request}; +use hyper::Body; +use lambda_runtime_api_client::{build_request, Client, Error}; + +fn register_request(extension_name: &str, events: &[&str]) -> Result, Error> { + let events = serde_json::json!({ "events": events }); + + let req = build_request() + .method(Method::POST) + .uri("/2020-01-01/extension/register") + .header("Lambda-Extension-Name", extension_name) + .body(Body::from(serde_json::to_string(&events)?))?; + + Ok(req) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let client = Client::builder().build()?; + let request = register_request("my_extension", &["INVOKE"])?; + + client.call(request).await +} +``` diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs new file mode 100644 index 00000000..e585944e --- /dev/null +++ b/lambda-runtime-api-client/src/lib.rs @@ -0,0 +1,134 @@ +#![deny(clippy::all, clippy::cargo)] +#![warn(missing_docs, nonstandard_style, rust_2018_idioms)] + +//! This crate includes a base HTTP client to interact with +//! the AWS Lambda Runtime API. +use http::{uri::Scheme, Request, Response, Uri}; +use hyper::{ + client::{connect::Connection, HttpConnector}, + Body, +}; +use std::fmt::Debug; +use tokio::io::{AsyncRead, AsyncWrite}; +use tower_service::Service; + +const USER_AGENT_HEADER: &str = "User-Agent"; +const USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); + +/// Error type that lambdas may result in +pub type Error = Box; + +/// API client to interact with the AWS Lambda Runtime API. +#[derive(Debug)] +pub struct Client { + /// The runtime API URI + pub base: Uri, + /// The client that manages the API connections + pub client: hyper::Client, +} + +impl Client { + /// Create a builder struct to configure the client. + pub fn builder() -> ClientBuilder { + ClientBuilder { + connector: HttpConnector::new(), + uri: None, + } + } +} + +impl Client +where + C: hyper::client::connect::Connect + Sync + Send + Clone + 'static, +{ + /// Send a given request to the Runtime API. + /// Use the client's base URI to ensure the API endpoint is correct. + pub async fn call(&self, req: Request) -> Result, Error> { + let req = self.set_origin(req)?; + let response = self.client.request(req).await?; + Ok(response) + } + + /// Create a new client with a given base URI and HTTP connector. + pub fn with(base: Uri, connector: C) -> Self { + let client = hyper::Client::builder().build(connector); + Self { base, client } + } + + fn set_origin(&self, req: Request) -> Result, Error> { + let (mut parts, body) = req.into_parts(); + let (scheme, authority) = { + let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP); + let authority = self.base.authority().expect("Authority not found"); + (scheme, authority) + }; + let path = parts.uri.path_and_query().expect("PathAndQuery not found"); + + let uri = Uri::builder() + .scheme(scheme.clone()) + .authority(authority.clone()) + .path_and_query(path.clone()) + .build(); + + match uri { + Ok(u) => { + parts.uri = u; + Ok(Request::from_parts(parts, body)) + } + Err(e) => Err(Box::new(e)), + } + } +} + +/// Builder implementation to construct any Runtime API clients. +pub struct ClientBuilder = hyper::client::HttpConnector> { + connector: C, + uri: Option, +} + +impl ClientBuilder +where + C: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + /// Create a new builder with a given HTTP connector. + pub fn with_connector(self, connector: C2) -> ClientBuilder + where + C2: Service + Clone + Send + Sync + Unpin + 'static, + >::Future: Unpin + Send, + >::Error: Into>, + >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + { + ClientBuilder { + connector, + uri: self.uri, + } + } + + /// Create a new builder with a given base URI. + /// Inherits all other attributes from the existent builder. + pub fn with_endpoint(self, uri: http::Uri) -> Self { + Self { uri: Some(uri), ..self } + } + + /// Create the new client to interact with the Runtime API. + pub fn build(self) -> Result, Error> { + let uri = match self.uri { + Some(uri) => uri, + None => { + let uri = std::env::var("AWS_LAMBDA_RUNTIME_API").expect("Missing AWS_LAMBDA_RUNTIME_API env var"); + uri.try_into().expect("Unable to convert to URL") + } + }; + Ok(Client::with(uri, self.connector)) + } +} + +/// Create a request builder. +/// This builder uses `aws-lambda-rust/CRATE_VERSION` as +/// the default User-Agent. +pub fn build_request() -> http::request::Builder { + http::Request::builder().header(USER_AGENT_HEADER, USER_AGENT) +} diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 4d0de675..25bc26ec 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -27,6 +27,7 @@ tracing-error = "0.2" tracing = { version = "0.1", features = ["log"] } tower-service = "0.3" tokio-stream = "0.1.2" +lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } [dev-dependencies] tracing-subscriber = "0.3" diff --git a/lambda-runtime/src/client.rs b/lambda-runtime/src/client.rs deleted file mode 100644 index 5e39e300..00000000 --- a/lambda-runtime/src/client.rs +++ /dev/null @@ -1,308 +0,0 @@ -use crate::Error; -use http::{uri::Scheme, Request, Response, Uri}; -use hyper::{client::HttpConnector, Body}; -use std::fmt::Debug; - -#[derive(Debug)] -pub(crate) struct Client { - pub(crate) base: Uri, - pub(crate) client: hyper::Client, -} - -impl Client -where - C: hyper::client::connect::Connect + Sync + Send + Clone + 'static, -{ - pub fn with(base: Uri, connector: C) -> Self { - let client = hyper::Client::builder().build(connector); - Self { base, client } - } - - fn set_origin(&self, req: Request) -> Result, Error> { - let (mut parts, body) = req.into_parts(); - let (scheme, authority) = { - let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP); - let authority = self.base.authority().expect("Authority not found"); - (scheme, authority) - }; - let path = parts.uri.path_and_query().expect("PathAndQuery not found"); - - let uri = Uri::builder() - .scheme(scheme.clone()) - .authority(authority.clone()) - .path_and_query(path.clone()) - .build(); - - match uri { - Ok(u) => { - parts.uri = u; - Ok(Request::from_parts(parts, body)) - } - Err(e) => Err(Box::new(e)), - } - } - - pub(crate) async fn call(&self, req: Request) -> Result, Error> { - let req = self.set_origin(req)?; - let response = self.client.request(req).await?; - Ok(response) - } -} - -#[cfg(test)] -mod endpoint_tests { - use crate::{ - client::Client, - incoming, - requests::{ - EventCompletionRequest, EventErrorRequest, IntoRequest, IntoResponse, NextEventRequest, NextEventResponse, - }, - simulated, - types::Diagnostic, - Error, Runtime, - }; - use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; - use hyper::{server::conn::Http, service::service_fn, Body}; - use serde_json::json; - use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env}; - use tokio::{ - io::{self, AsyncRead, AsyncWrite}, - select, - sync::{self, oneshot}, - }; - use tokio_stream::StreamExt; - - #[cfg(test)] - async fn next_event(req: &Request) -> Result, Error> { - let path = "/2018-06-01/runtime/invocation/next"; - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); - let body = json!({"message": "hello"}); - - let rsp = NextEventResponse { - request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", - deadline: 1_542_409_706_888, - arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", - trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", - body: serde_json::to_vec(&body)?, - }; - rsp.into_rsp() - } - - #[cfg(test)] - async fn complete_event(req: &Request, id: &str) -> Result, Error> { - assert_eq!(Method::POST, req.method()); - let rsp = Response::builder() - .status(StatusCode::ACCEPTED) - .body(Body::empty()) - .expect("Unable to construct response"); - - let expected = format!("/2018-06-01/runtime/invocation/{}/response", id); - assert_eq!(expected, req.uri().path()); - - Ok(rsp) - } - - #[cfg(test)] - async fn event_err(req: &Request, id: &str) -> Result, Error> { - let expected = format!("/2018-06-01/runtime/invocation/{}/error", id); - assert_eq!(expected, req.uri().path()); - - assert_eq!(req.method(), Method::POST); - let header = "lambda-runtime-function-error-type"; - let expected = "unhandled"; - assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); - - let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; - Ok(rsp) - } - - #[cfg(test)] - async fn handle_incoming(req: Request) -> Result, Error> { - let path: Vec<&str> = req - .uri() - .path_and_query() - .expect("PathAndQuery not found") - .as_str() - .split('/') - .collect::>(); - match path[1..] { - ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, - ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, - ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, - ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), - _ => unimplemented!(), - } - } - - #[cfg(test)] - async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> - where - I: AsyncRead + AsyncWrite + Unpin + 'static, - { - let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); - select! { - _ = rx => { - Ok(()) - } - res = conn => { - match res { - Ok(()) => Ok(()), - Err(e) => { - Err(e) - } - } - } - } - } - - #[tokio::test] - async fn test_next_event() -> Result<(), Error> { - let base = Uri::from_static("http://localhost:9001"); - let (client, server) = io::duplex(64); - - let (tx, rx) = sync::oneshot::channel(); - let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); - }); - - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); - - let req = NextEventRequest.into_req()?; - let rsp = client.call(req).await.expect("Unable to send request"); - - assert_eq!(rsp.status(), StatusCode::OK); - let header = "lambda-runtime-deadline-ms"; - assert_eq!(rsp.headers()[header], &HeaderValue::try_from("1542409706888")?); - - // shutdown server... - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } - } - - #[tokio::test] - async fn test_ok_response() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); - }); - - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); - - let req = EventCompletionRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - body: "done", - }; - let req = req.into_req()?; - - let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } - } - - #[tokio::test] - async fn test_error_response() -> Result<(), Error> { - let (client, server) = io::duplex(200); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); - }); - - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); - - let req = EventErrorRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - diagnostic: Diagnostic { - error_type: "InvalidEventDataError".to_string(), - error_message: "Error parsing event data".to_string(), - }, - }; - let req = req.into_req()?; - let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } - } - - #[tokio::test] - async fn successful_end_to_end_run() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); - }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - - let runtime = Runtime::builder() - .with_endpoint(base) - .with_connector(conn) - .build() - .expect("Unable to build runtime"); - - async fn func(event: serde_json::Value, _: crate::Context) -> Result { - Ok(event) - } - let f = crate::handler_fn(func); - - // set env vars needed to init Config if they are not already set in the environment - if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { - env::set_var("AWS_LAMBDA_RUNTIME_API", "http://localhost:9001"); - } - if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); - } - if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); - } - if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); - } - if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); - } - if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); - } - let config = crate::Config::from_env().expect("Failed to read env vars"); - - let client = &runtime.client; - let incoming = incoming(client).take(1); - runtime.run(incoming, f, &config).await?; - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } - } -} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 30220e45..85403ea3 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -6,21 +6,15 @@ //! Create a type that conforms to the [`Handler`] trait. This type can then be passed //! to the the `lambda_runtime::run` function, which launches and runs the Lambda runtime. pub use crate::types::Context; -use client::Client; use hyper::client::{connect::Connection, HttpConnector}; +use lambda_runtime_api_client::Client; use serde::{Deserialize, Serialize}; -use std::{ - convert::{TryFrom, TryInto}, - env, fmt, - future::Future, - panic, -}; +use std::{convert::TryFrom, env, fmt, future::Future, panic}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; use tower_service::Service; use tracing::{error, trace}; -mod client; mod requests; #[cfg(test)] mod simulated; @@ -31,13 +25,11 @@ use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEvent use types::Diagnostic; /// Error type that lambdas may result in -pub type Error = Box; +pub type Error = lambda_runtime_api_client::Error; /// Configuration derived from environment variables. #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct Config { - /// The host and port of the [runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). - pub endpoint: String, /// The name of the function. pub function_name: String, /// The amount of memory available to the function in MB. @@ -54,7 +46,6 @@ impl Config { /// Attempts to read configuration from environment variables. pub fn from_env() -> Result { let conf = Config { - endpoint: env::var("AWS_LAMBDA_RUNTIME_API").expect("Missing AWS_LAMBDA_RUNTIME_API env var"), function_name: env::var("AWS_LAMBDA_FUNCTION_NAME").expect("Missing AWS_LAMBDA_FUNCTION_NAME env var"), memory: env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") .expect("Missing AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var") @@ -106,25 +97,10 @@ where } } -#[non_exhaustive] -#[derive(Debug, PartialEq)] -enum BuilderError { - UnsetUri, -} - struct Runtime = HttpConnector> { client: Client, } -impl Runtime { - pub fn builder() -> RuntimeBuilder { - RuntimeBuilder { - connector: HttpConnector::new(), - uri: None, - } - } -} - impl Runtime where C: Service + Clone + Send + Sync + Unpin + 'static, @@ -209,56 +185,6 @@ where } } -struct RuntimeBuilder = hyper::client::HttpConnector> { - connector: C, - uri: Option, -} - -impl RuntimeBuilder -where - C: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, -{ - pub fn with_connector(self, connector: C2) -> RuntimeBuilder - where - C2: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, - { - RuntimeBuilder { - connector, - uri: self.uri, - } - } - - pub fn with_endpoint(self, uri: http::Uri) -> Self { - Self { uri: Some(uri), ..self } - } - - pub fn build(self) -> Result, BuilderError> { - let uri = match self.uri { - Some(uri) => uri, - None => return Err(BuilderError::UnsetUri), - }; - let client = Client::with(uri, self.connector); - - Ok(Runtime { client }) - } -} - -#[test] -fn test_builder() { - let runtime = Runtime::builder() - .with_connector(HttpConnector::new()) - .with_endpoint(http::Uri::from_static("http://nomatter.com")) - .build(); - - runtime.unwrap(); -} - fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ where C: Service + Clone + Send + Sync + Unpin + 'static, @@ -305,12 +231,8 @@ where { trace!("Loading config from env"); let config = Config::from_env()?; - let uri = config.endpoint.clone().try_into().expect("Unable to convert to URL"); - let runtime = Runtime::builder() - .with_connector(HttpConnector::new()) - .with_endpoint(uri) - .build() - .expect("Unable to create a runtime"); + let client = Client::builder().build().expect("Unable to create a runtime client"); + let runtime = Runtime { client }; let client = &runtime.client; let incoming = incoming(client); @@ -320,3 +242,262 @@ where fn type_name_of_val(_: T) -> &'static str { std::any::type_name::() } + +#[cfg(test)] +mod endpoint_tests { + use crate::{ + incoming, + requests::{ + EventCompletionRequest, EventErrorRequest, IntoRequest, IntoResponse, NextEventRequest, NextEventResponse, + }, + simulated, + types::Diagnostic, + Error, Runtime, + }; + use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; + use hyper::{server::conn::Http, service::service_fn, Body}; + use lambda_runtime_api_client::Client; + use serde_json::json; + use simulated::DuplexStreamWrapper; + use std::{convert::TryFrom, env}; + use tokio::{ + io::{self, AsyncRead, AsyncWrite}, + select, + sync::{self, oneshot}, + }; + use tokio_stream::StreamExt; + + #[cfg(test)] + async fn next_event(req: &Request) -> Result, Error> { + let path = "/2018-06-01/runtime/invocation/next"; + assert_eq!(req.method(), Method::GET); + assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); + let body = json!({"message": "hello"}); + + let rsp = NextEventResponse { + request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", + deadline: 1_542_409_706_888, + arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", + trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", + body: serde_json::to_vec(&body)?, + }; + rsp.into_rsp() + } + + #[cfg(test)] + async fn complete_event(req: &Request, id: &str) -> Result, Error> { + assert_eq!(Method::POST, req.method()); + let rsp = Response::builder() + .status(StatusCode::ACCEPTED) + .body(Body::empty()) + .expect("Unable to construct response"); + + let expected = format!("/2018-06-01/runtime/invocation/{}/response", id); + assert_eq!(expected, req.uri().path()); + + Ok(rsp) + } + + #[cfg(test)] + async fn event_err(req: &Request, id: &str) -> Result, Error> { + let expected = format!("/2018-06-01/runtime/invocation/{}/error", id); + assert_eq!(expected, req.uri().path()); + + assert_eq!(req.method(), Method::POST); + let header = "lambda-runtime-function-error-type"; + let expected = "unhandled"; + assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); + + let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; + Ok(rsp) + } + + #[cfg(test)] + async fn handle_incoming(req: Request) -> Result, Error> { + let path: Vec<&str> = req + .uri() + .path_and_query() + .expect("PathAndQuery not found") + .as_str() + .split('/') + .collect::>(); + match path[1..] { + ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, + ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, + ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, + ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), + _ => unimplemented!(), + } + } + + #[cfg(test)] + async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> + where + I: AsyncRead + AsyncWrite + Unpin + 'static, + { + let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); + select! { + _ = rx => { + Ok(()) + } + res = conn => { + match res { + Ok(()) => Ok(()), + Err(e) => { + Err(e) + } + } + } + } + } + + #[tokio::test] + async fn test_next_event() -> Result<(), Error> { + let base = Uri::from_static("http://localhost:9001"); + let (client, server) = io::duplex(64); + + let (tx, rx) = sync::oneshot::channel(); + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; + let client = Client::with(base, conn); + + let req = NextEventRequest.into_req()?; + let rsp = client.call(req).await.expect("Unable to send request"); + + assert_eq!(rsp.status(), StatusCode::OK); + let header = "lambda-runtime-deadline-ms"; + assert_eq!(rsp.headers()[header], &HeaderValue::try_from("1542409706888")?); + + // shutdown server... + tx.send(()).expect("Receiver has been dropped"); + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } + + #[tokio::test] + async fn test_ok_response() -> Result<(), Error> { + let (client, server) = io::duplex(64); + let (tx, rx) = sync::oneshot::channel(); + let base = Uri::from_static("http://localhost:9001"); + + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; + let client = Client::with(base, conn); + + let req = EventCompletionRequest { + request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", + body: "done", + }; + let req = req.into_req()?; + + let rsp = client.call(req).await?; + assert_eq!(rsp.status(), StatusCode::ACCEPTED); + + // shutdown server + tx.send(()).expect("Receiver has been dropped"); + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } + + #[tokio::test] + async fn test_error_response() -> Result<(), Error> { + let (client, server) = io::duplex(200); + let (tx, rx) = sync::oneshot::channel(); + let base = Uri::from_static("http://localhost:9001"); + + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; + let client = Client::with(base, conn); + + let req = EventErrorRequest { + request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", + diagnostic: Diagnostic { + error_type: "InvalidEventDataError".to_string(), + error_message: "Error parsing event data".to_string(), + }, + }; + let req = req.into_req()?; + let rsp = client.call(req).await?; + assert_eq!(rsp.status(), StatusCode::ACCEPTED); + + // shutdown server + tx.send(()).expect("Receiver has been dropped"); + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } + + #[tokio::test] + async fn successful_end_to_end_run() -> Result<(), Error> { + let (client, server) = io::duplex(64); + let (tx, rx) = sync::oneshot::channel(); + let base = Uri::from_static("http://localhost:9001"); + + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; + + let client = Client::builder() + .with_endpoint(base) + .with_connector(conn) + .build() + .expect("Unable to build client"); + + async fn func(event: serde_json::Value, _: crate::Context) -> Result { + Ok(event) + } + let f = crate::handler_fn(func); + + // set env vars needed to init Config if they are not already set in the environment + if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { + env::set_var("AWS_LAMBDA_RUNTIME_API", "http://localhost:9001"); + } + if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); + } + if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); + } + if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); + } + if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); + } + if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); + } + let config = crate::Config::from_env().expect("Failed to read env vars"); + + let runtime = Runtime { client }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + runtime.run(incoming, f, &config).await?; + + // shutdown server + tx.send(()).expect("Receiver has been dropped"); + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } +} diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 8aa2edbe..4d033614 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,11 +1,10 @@ use crate::{types::Diagnostic, Error}; use http::{Method, Request, Response, Uri}; use hyper::Body; +use lambda_runtime_api_client::build_request; use serde::Serialize; use std::str::FromStr; -const USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); - pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; } @@ -20,9 +19,8 @@ pub(crate) struct NextEventRequest; impl IntoRequest for NextEventRequest { fn into_req(self) -> Result, Error> { - let req = Request::builder() + let req = build_request() .method(Method::GET) - .header("User-Agent", USER_AGENT) .uri(Uri::from_static("/2018-06-01/runtime/invocation/next")) .body(Body::empty())?; Ok(req) @@ -82,11 +80,7 @@ where let body = serde_json::to_vec(&self.body)?; let body = Body::from(body); - let req = Request::builder() - .header("User-Agent", USER_AGENT) - .method(Method::POST) - .uri(uri) - .body(body)?; + let req = build_request().method(Method::POST).uri(uri).body(body)?; Ok(req) } } @@ -120,10 +114,9 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { let body = serde_json::to_vec(&self.diagnostic)?; let body = Body::from(body); - let req = Request::builder() + let req = build_request() .method(Method::POST) .uri(uri) - .header("User-Agent", USER_AGENT) .header("lambda-runtime-function-error-type", "unhandled") .body(body)?; Ok(req) @@ -157,10 +150,9 @@ impl IntoRequest for InitErrorRequest { let uri = "/2018-06-01/runtime/init/error".to_string(); let uri = Uri::from_str(&uri)?; - let req = Request::builder() + let req = build_request() .method(Method::POST) .uri(uri) - .header("User-Agent", USER_AGENT) .header("lambda-runtime-function-error-type", "unhandled") .body(Body::empty())?; Ok(req) From b245de8aabc1967674bdb9348ff72563ee80cac8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 12 Dec 2021 23:31:11 -0800 Subject: [PATCH 057/394] Remove unused dependencies (#381) These crates are not used anywhere in the repository, as far as I can tell. Everything seems to be working as expected without them. Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 2 -- lambda-runtime-api-client/Cargo.toml | 5 ----- lambda-runtime/Cargo.toml | 4 ---- 3 files changed, 11 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 05f1d6f7..f5b101c1 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -24,8 +24,6 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } [dev-dependencies] -tracing-subscriber = "0.3" -once_cell = "1.4.0" simple_logger = "1.6.0" log = "^0.4" simple-error = "0.2" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 48188a91..36aa0781 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -15,8 +15,3 @@ http = "0.2" hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } tower-service = "0.3" tokio = { version = "1.0", features = ["io-util"] } - -[dev-dependencies] -serde_json = "^1" -async-stream = "0.3" -tokio-stream = "0.1.2" \ No newline at end of file diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 25bc26ec..3e567ed9 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -22,16 +22,12 @@ serde_json = "^1" bytes = "1.0" http = "0.2" async-stream = "0.3" -futures = "0.3" -tracing-error = "0.2" tracing = { version = "0.1", features = ["log"] } tower-service = "0.3" tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } [dev-dependencies] -tracing-subscriber = "0.3" -once_cell = "1.4.0" simple_logger = "1.6.0" log = "^0.4" simple-error = "0.2" From 17425711812e93bff5f7ba3f562721a99f186cad Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Thu, 16 Dec 2021 20:37:13 +0100 Subject: [PATCH 058/394] fix: cargo fmt fix (#378) --- lambda-extension/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 41f56890..7b462190 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -8,8 +8,7 @@ use hyper::client::{connect::Connection, HttpConnector}; use lambda_runtime_api_client::Client; use serde::Deserialize; -use std::future::Future; -use std::path::PathBuf; +use std::{future::Future, path::PathBuf}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::StreamExt; use tower_service::Service; From a7e17ee4021ed02b04c365fe1de8e68dfe1ee67d Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Fri, 17 Dec 2021 13:44:13 +0100 Subject: [PATCH 059/394] feat: add integration tests stack (#379) * feat: add integration tests stack * fix: fmt and clippy * feat: add integration tests for lambda_http * fix: disable multiple_crate_versions --- .gitignore | 4 + Cargo.toml | 1 + Makefile | 48 ++++++ lambda-extension/src/lib.rs | 1 + lambda-integration-tests/Cargo.toml | 21 +++ lambda-integration-tests/python/main.py | 4 + .../src/bin/extension-fn.rs | 23 +++ .../src/bin/extension-trait.rs | 37 ++++ lambda-integration-tests/src/bin/http-fn.rs | 19 ++ .../src/bin/runtime-fn.rs | 29 ++++ .../src/bin/runtime-trait.rs | 43 +++++ lambda-integration-tests/template.yaml | 162 ++++++++++++++++++ lambda-runtime/src/lib.rs | 1 + 13 files changed, 393 insertions(+) create mode 100644 Makefile create mode 100644 lambda-integration-tests/Cargo.toml create mode 100644 lambda-integration-tests/python/main.py create mode 100644 lambda-integration-tests/src/bin/extension-fn.rs create mode 100644 lambda-integration-tests/src/bin/extension-trait.rs create mode 100644 lambda-integration-tests/src/bin/http-fn.rs create mode 100644 lambda-integration-tests/src/bin/runtime-fn.rs create mode 100644 lambda-integration-tests/src/bin/runtime-trait.rs create mode 100644 lambda-integration-tests/template.yaml diff --git a/.gitignore b/.gitignore index 94f3d847..bb7ec9b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target /.cargo lambda-runtime/libtest.rmeta +lambda-integration-tests/target Cargo.lock # Built AWS Lambda zipfile @@ -8,3 +9,6 @@ lambda.zip # output.json from example docs output.json + +.aws-sam +build \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 01bc46b3..4ef3789d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "lambda-http", + "lambda-integration-tests", "lambda-runtime-api-client", "lambda-runtime", "lambda-extension" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..62cb2171 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +INTEG_STACK_NAME ?= rust-lambda-integration-tests +INTEG_FUNCTIONS_BUILD := runtime-fn runtime-trait http-fn +INTEG_FUNCTIONS_INVOKE := RuntimeFn RuntimeFnAl2 RuntimeTrait RuntimeTraitAl2 Python PythonAl2 +INTEG_API_INVOKE := RestApiUrl HttpApiUrl +INTEG_EXTENSIONS := extension-fn extension-trait +# Using musl to run extensions on both AL1 and AL2 +INTEG_ARCH := x86_64-unknown-linux-musl + +integration-tests: +# Build Integration functions + cross build --release --target $(INTEG_ARCH) -p lambda_integration_tests + rm -rf ./build + mkdir -p ./build + ${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_BUILD}, build-integration-function-${function}) + ${MAKE} ${MAKEOPTS} $(foreach extension,${INTEG_EXTENSIONS}, build-integration-extension-${extension}) +# Deploy to AWS + sam deploy \ + --template lambda-integration-tests/template.yaml \ + --stack-name ${INTEG_STACK_NAME} \ + --capabilities CAPABILITY_IAM \ + --resolve-s3 \ + --no-fail-on-empty-changeset +# Invoke functions + ${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_INVOKE}, invoke-integration-function-${function}) + ${MAKE} ${MAKEOPTS} $(foreach api,${INTEG_API_INVOKE}, invoke-integration-api-${api}) + +build-integration-function-%: + mkdir -p ./build/$* + cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/bootstrap + +build-integration-extension-%: + mkdir -p ./build/$*/extensions + cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$* + +invoke-integration-function-%: + aws lambda invoke --function-name $$(aws cloudformation describe-stacks --stack-name $(INTEG_STACK_NAME) \ + --query 'Stacks[0].Outputs[?OutputKey==`$*`].OutputValue' \ + --output text) --payload '{"command": "hello"}' --cli-binary-format raw-in-base64-out /dev/stdout + +invoke-integration-api-%: + $(eval API_URL := $(shell aws cloudformation describe-stacks --stack-name $(INTEG_STACK_NAME) \ + --query 'Stacks[0].Outputs[?OutputKey==`$*`].OutputValue' \ + --output text)) + curl $(API_URL)/get + curl $(API_URL)/al2/get + curl -X POST -d '{"command": "hello"}' $(API_URL)/post + curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post + \ No newline at end of file diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 7b462190..f1d34a18 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::all, clippy::cargo)] +#![allow(clippy::multiple_crate_versions)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] //! This module includes utilities to create Lambda Runtime Extensions. diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml new file mode 100644 index 00000000..b250e911 --- /dev/null +++ b/lambda-integration-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "lambda_integration_tests" +version = "0.4.1" +edition = "2018" +description = "AWS Lambda Runtime integration tests" +license = "Apache-2.0" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +categories = ["web-programming::http-server"] +keywords = ["AWS", "Lambda", "API"] +readme = "../README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lambda_http = { path = "../lambda-http", version = "0.4.1" } +lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } +lambda_extension = { path = "../lambda-extension", version = "0.1.0" } +log = "0.4" +serde = { version = "1", features = ["derive"] } +simple_logger = { version = "1.15", default-features = false } +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/lambda-integration-tests/python/main.py b/lambda-integration-tests/python/main.py new file mode 100644 index 00000000..e7e2114b --- /dev/null +++ b/lambda-integration-tests/python/main.py @@ -0,0 +1,4 @@ +def handler(event, context): + return { + "message": event["command"].upper() + } \ No newline at end of file diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs new file mode 100644 index 00000000..ad641d6c --- /dev/null +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -0,0 +1,23 @@ +use lambda_extension::{extension_fn, Error, NextEvent}; +use log::{info, LevelFilter}; +use simple_logger::SimpleLogger; + +async fn my_extension(event: NextEvent) -> Result<(), Error> { + match event { + NextEvent::Shutdown(e) => { + info!("[extension-fn] Shutdown event received: {:?}", e); + } + NextEvent::Invoke(e) => { + info!("[extension-fn] Request event received: {:?}", e); + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + lambda_extension::run(extension_fn(my_extension)).await +} diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs new file mode 100644 index 00000000..de9031e6 --- /dev/null +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -0,0 +1,37 @@ +use lambda_extension::{Error, Extension, NextEvent}; +use log::{info, LevelFilter}; +use simple_logger::SimpleLogger; +use std::{ + future::{ready, Future}, + pin::Pin, +}; + +#[derive(Default)] +struct MyExtension { + invoke_count: usize, +} + +impl Extension for MyExtension { + type Fut = Pin>>>; + + fn call(&mut self, event: NextEvent) -> Self::Fut { + match event { + NextEvent::Shutdown(e) => { + info!("[extension] Shutdown event received: {:?}", e); + } + NextEvent::Invoke(e) => { + self.invoke_count += 1; + info!("[extension] Request event {} received: {:?}", self.invoke_count, e); + } + } + + Box::pin(ready(Ok(()))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + lambda_extension::run(MyExtension::default()).await +} diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs new file mode 100644 index 00000000..26be7535 --- /dev/null +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -0,0 +1,19 @@ +use lambda_http::{ + lambda_runtime::{self, Context, Error}, + IntoResponse, Request, Response, +}; +use log::{info, LevelFilter}; +use simple_logger::SimpleLogger; + +async fn handler(event: Request, _context: Context) -> Result { + info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); + + Ok(Response::builder().status(200).body("Hello, world!").unwrap()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + lambda_runtime::run(lambda_http::handler(handler)).await +} diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs new file mode 100644 index 00000000..37057aef --- /dev/null +++ b/lambda-integration-tests/src/bin/runtime-fn.rs @@ -0,0 +1,29 @@ +use lambda_runtime::{handler_fn, Context, Error}; +use log::{info, LevelFilter}; +use serde::{Deserialize, Serialize}; +use simple_logger::SimpleLogger; + +#[derive(Deserialize, Debug)] +struct Request { + command: String, +} + +#[derive(Serialize, Debug)] +struct Response { + message: String, +} + +async fn handler(event: Request, _context: Context) -> Result { + info!("[handler-fn] Received event: {:?}", event); + + Ok(Response { + message: event.command.to_uppercase(), + }) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + lambda_runtime::run(handler_fn(handler)).await +} diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs new file mode 100644 index 00000000..e1de6bb1 --- /dev/null +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -0,0 +1,43 @@ +use lambda_runtime::{Context, Error, Handler}; +use log::{info, LevelFilter}; +use serde::{Deserialize, Serialize}; +use simple_logger::SimpleLogger; +use std::{ + future::{ready, Future}, + pin::Pin, +}; + +#[derive(Deserialize, Debug)] +struct Request { + command: String, +} + +#[derive(Serialize, Debug)] +struct Response { + message: String, +} + +#[derive(Default)] +struct MyHandler { + invoke_count: usize, +} + +impl Handler for MyHandler { + type Error = Error; + type Fut = Pin>>>; + + fn call(&mut self, event: Request, _context: Context) -> Self::Fut { + self.invoke_count += 1; + info!("[handler] Received event {}: {:?}", self.invoke_count, event); + Box::pin(ready(Ok(Response { + message: event.command.to_uppercase(), + }))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + + lambda_runtime::run(MyHandler::default()).await +} diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml new file mode 100644 index 00000000..7f408d2c --- /dev/null +++ b/lambda-integration-tests/template.yaml @@ -0,0 +1,162 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + MemorySize: 128 + Handler: bootstrap + Timeout: 5 + +Resources: + # Rust function using runtime_fn running on AL2 + RuntimeFnAl2: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-fn/ + Runtime: provided.al2 + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Rust function using runtime_fn running on AL1 + RuntimeFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-fn/ + Runtime: provided + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Rust function using a Handler implementation running on AL2 + RuntimeTraitAl2: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-trait/ + Runtime: provided.al2 + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Rust function using a Handler implementation running on AL1 + RuntimeTrait: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-trait/ + Runtime: provided + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Rust function using lambda_http::runtime running on AL2 + HttpFnAl2: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-fn/ + Runtime: provided.al2 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2/post + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Rust function using lambda_http::runtime running on AL1 + HttpFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-fn/ + Runtime: provided + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /post + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Python function running on AL2 + PythonAl2: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./python/ + Handler: main.handler + Runtime: python3.9 + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + # Python function running on AL1 + Python: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./python/ + Handler: main.handler + Runtime: python3.7 + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + + ExtensionFn: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ../build/extension-fn/ + + ExtensionTrait: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ../build/extension-trait/ + +Outputs: + RuntimeFnAl2: + Value: !GetAtt RuntimeFnAl2.Arn + RuntimeFn: + Value: !GetAtt RuntimeFn.Arn + RuntimeTraitAl2: + Value: !GetAtt RuntimeTraitAl2.Arn + RuntimeTrait: + Value: !GetAtt RuntimeTrait.Arn + PythonAl2: + Value: !GetAtt PythonAl2.Arn + Python: + Value: !GetAtt Python.Arn + + RestApiUrl: + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod" + HttpApiUrl: + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" \ No newline at end of file diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 85403ea3..ad6fecf1 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::all, clippy::cargo)] +#![allow(clippy::multiple_crate_versions)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] //! The mechanism available for defining a Lambda function is as follows: From 3c3a5672799ee4c92f97e30a5f3ec8a70d91c5df Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Fri, 24 Dec 2021 03:14:16 +0100 Subject: [PATCH 060/394] chore: stable fmt and clippy, set MSRV (#383) --- .github/workflows/build.yml | 41 ++++++++++++++-------------- .rustfmt.toml | 5 ++-- Makefile | 5 ++++ README.md | 12 ++++++++ lambda-extension/Cargo.toml | 2 +- lambda-http/src/lib.rs | 4 +-- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime-api-client/src/lib.rs | 2 +- 8 files changed, 46 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b49ddf81..fb191e04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ jobs: - ubuntu-latest - macOS-latest rust: + - "1.54.0" # Current MSRV - stable - beta - nightly @@ -21,24 +22,24 @@ jobs: include: - rust: nightly allow_failure: true - exclude: - - os: macOS-latest - target: x86_64-unknown-linux-musl - - os: ubuntu-latest - rust: 1.40.0 - target: x86_64-unknown-linux-musl - - os: ubuntu-latest - rust: beta - target: x86_64-unknown-linux-musl - - os: ubuntu-latest - rust: nightly - target: x86_64-unknown-linux-musl - - os: macOS-latest - rust: 1.40.0 - - os: macOS-latest - rust: beta - - os: macOS-latest - rust: nightly + # exclude: + # - os: macOS-latest + # target: x86_64-unknown-linux-musl + # - os: ubuntu-latest + # rust: 1.40.0 + # target: x86_64-unknown-linux-musl + # - os: ubuntu-latest + # rust: beta + # target: x86_64-unknown-linux-musl + # - os: ubuntu-latest + # rust: nightly + # target: x86_64-unknown-linux-musl + # - os: macOS-latest + # rust: 1.40.0 + # - os: macOS-latest + # rust: beta + # - os: macOS-latest + # rust: nightly env: RUST_BACKTRACE: 1 steps: @@ -63,7 +64,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable components: rustfmt override: true - name: Run fmt check @@ -74,7 +75,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable components: clippy override: true - name: Run clippy check diff --git a/.rustfmt.toml b/.rustfmt.toml index 02f25178..a40f0edc 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,6 @@ edition = "2018" -# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports -merge_imports = true +# imports_granularity is unstable +# # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports +# imports_granularity = "Crate" # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width max_width = 120 \ No newline at end of file diff --git a/Makefile b/Makefile index 62cb2171..209208f5 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,11 @@ INTEG_EXTENSIONS := extension-fn extension-trait # Using musl to run extensions on both AL1 and AL2 INTEG_ARCH := x86_64-unknown-linux-musl +pr-check: + cargo +1.54.0 check --all + cargo +stable fmt --all -- --check + cargo +stable clippy + integration-tests: # Build Integration functions cross build --release --target $(INTEG_ARCH) -p lambda_integration_tests diff --git a/README.md b/README.md index 96f24476..5d636b87 100644 --- a/README.md +++ b/README.md @@ -259,3 +259,15 @@ fn main() -> Result<(), Box> { Ok(()) } ``` + +## Supported Rust Versions (MSRV) + +The AWS Lambda Rust Runtime requires a minimum of Rust 1.54, and is not guaranteed to build on compiler versions earlier than that. + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## License + +This project is licensed under the Apache-2.0 License. \ No newline at end of file diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index f5b101c1..ac155397 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lambda_extension" version = "0.1.0" -edition = "2021" +edition = "2018" authors = ["David Calavera "] description = "AWS Lambda Extension API" license = "Apache-2.0" diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index ec80c30b..50e567fa 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -103,7 +103,7 @@ pub trait Handler<'a>: Sized { pub fn handler<'a, H: Handler<'a>>(handler: H) -> Adapter<'a, H> { Adapter { handler, - _pd: PhantomData, + _phantom_data: PhantomData, } } @@ -152,7 +152,7 @@ where /// for a larger explanation of why this is necessary pub struct Adapter<'a, H: Handler<'a>> { handler: H, - _pd: PhantomData<&'a H>, + _phantom_data: PhantomData<&'a H>, } impl<'a, H: Handler<'a>> Handler<'a> for Adapter<'a, H> { diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 36aa0781..0e005802 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lambda_runtime_api_client" version = "0.4.1" -edition = "2021" +edition = "2018" authors = ["David Calavera "] description = "AWS Lambda Runtime interaction API" license = "Apache-2.0" diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index e585944e..3753dedf 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -8,7 +8,7 @@ use hyper::{ client::{connect::Connection, HttpConnector}, Body, }; -use std::fmt::Debug; +use std::{convert::TryInto, fmt::Debug}; use tokio::io::{AsyncRead, AsyncWrite}; use tower_service::Service; From 5c15750c61ed02b5120d481f6146880d68789c7a Mon Sep 17 00:00:00 2001 From: Filip Kieres Date: Tue, 28 Dec 2021 05:07:36 +0100 Subject: [PATCH 061/394] fix: add client context and icognito identity to runtime context (#382) --- lambda-runtime/src/types.rs | 120 +++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index ad894971..8c9e5321 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -57,15 +57,18 @@ struct MobileClientIdentity(String); #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct ClientContext { /// Information about the mobile application invoking the function. + #[serde(default)] pub client: ClientApplication, /// Custom properties attached to the mobile event context. + #[serde(default)] pub custom: HashMap, /// Environment settings from the mobile client. + #[serde(default)] pub environment: HashMap, } /// AWS Mobile SDK client fields. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ClientApplication { /// The mobile app installation id @@ -119,6 +122,18 @@ pub struct Context { impl TryFrom for Context { type Error = Error; fn try_from(headers: HeaderMap) -> Result { + let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { + serde_json::from_str(value.to_str()?)? + } else { + None + }; + + let identity: Option = if let Some(value) = headers.get("lambda-runtime-cognito-identity") { + serde_json::from_str(value.to_str()?)? + } else { + None + }; + let ctx = Context { request_id: headers .get("lambda-runtime-aws-request-id") @@ -142,8 +157,11 @@ impl TryFrom for Context { .unwrap_or(&HeaderValue::from_static("")) .to_str()? .to_owned(), + client_context, + identity, ..Default::default() }; + Ok(ctx) } } @@ -175,6 +193,70 @@ mod test { assert!(tried.is_ok()); } + #[test] + fn context_with_client_context_resolves() { + let mut custom = HashMap::new(); + custom.insert("key".to_string(), "value".to_string()); + let mut environment = HashMap::new(); + environment.insert("key".to_string(), "value".to_string()); + let client_context = ClientContext { + client: ClientApplication { + installation_id: String::new(), + app_title: String::new(), + app_version_name: String::new(), + app_version_code: String::new(), + app_package_name: String::new(), + }, + custom, + environment, + }; + let client_context_str = serde_json::to_string(&client_context).unwrap(); + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-client-context", + HeaderValue::from_str(&client_context_str).unwrap(), + ); + let tried = Context::try_from(headers); + assert!(tried.is_ok()); + let tried = tried.unwrap(); + assert!(tried.client_context.is_some()); + assert_eq!(tried.client_context.unwrap(), client_context); + } + + #[test] + fn context_with_empty_client_context_resolves() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}")); + let tried = Context::try_from(headers); + assert!(tried.is_ok()); + assert!(tried.unwrap().client_context.is_some()); + } + + #[test] + fn context_with_identity_resolves() { + let cognito_identity = CognitoIdentity { + identity_id: String::new(), + identity_pool_id: String::new(), + }; + let cognito_identity_str = serde_json::to_string(&cognito_identity).unwrap(); + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-cognito-identity", + HeaderValue::from_str(&cognito_identity_str).unwrap(), + ); + let tried = Context::try_from(headers); + assert!(tried.is_ok()); + let tried = tried.unwrap(); + assert!(tried.identity.is_some()); + assert_eq!(tried.identity.unwrap(), cognito_identity); + } + #[test] fn context_with_bad_deadline_type_is_err() { let mut headers = HeaderMap::new(); @@ -192,6 +274,42 @@ mod test { assert!(tried.is_err()); } + #[test] + fn context_with_bad_client_context_is_err() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-client-context", + HeaderValue::from_static("BAD-Type,not JSON"), + ); + let tried = Context::try_from(headers); + assert!(tried.is_err()); + } + + #[test] + fn context_with_empty_identity_is_err() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}")); + let tried = Context::try_from(headers); + assert!(tried.is_err()); + } + + #[test] + fn context_with_bad_identity_is_err() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-cognito-identity", + HeaderValue::from_static("BAD-Type,not JSON"), + ); + let tried = Context::try_from(headers); + assert!(tried.is_err()); + } + #[test] #[should_panic] #[allow(unused_must_use)] From 4bf7b07d4c4a87a900181c59dc6386070d33f00a Mon Sep 17 00:00:00 2001 From: david-perez Date: Fri, 31 Dec 2021 21:53:53 +0100 Subject: [PATCH 062/394] `README.md`: invoke lambda function with `raw-in-base64-out` (#385) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d636b87..6c416544 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,9 @@ $ aws lambda create-function --function-name rustTest \ You can now test the function using the AWS CLI or the AWS Lambda console ```bash -$ aws lambda invoke --function-name rustTest \ +$ aws lambda invoke + --cli-binary-format raw-in-base64-out \ + --function-name rustTest \ --payload '{"command": "Say Hi!"}' \ output.json $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} From 6ca9f5ecbbffd7877be7866e361e6745bf934745 Mon Sep 17 00:00:00 2001 From: david-perez Date: Fri, 31 Dec 2021 21:54:15 +0100 Subject: [PATCH 063/394] `README.md`: add newline to end of file (#386) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c416544..ff72aac8 100644 --- a/README.md +++ b/README.md @@ -272,4 +272,4 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform ## License -This project is licensed under the Apache-2.0 License. \ No newline at end of file +This project is licensed under the Apache-2.0 License. From 3a43ed9d53c6f7a8b92b265e17793767e5546f45 Mon Sep 17 00:00:00 2001 From: Markus Bergkvist Date: Sat, 1 Jan 2022 00:11:59 +0100 Subject: [PATCH 064/394] fix: add support for null/invalid type for multi-value-headers (#387) --- lambda-http/src/request.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 88740dd1..de61f726 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -317,7 +317,7 @@ where } } - deserializer.deserialize_map(HeaderVisitor) + Ok(deserializer.deserialize_map(HeaderVisitor).unwrap_or_default()) } /// Deserialize a map of Cow<'_, str> => Cow<'_, str> into an http::HeaderMap @@ -868,4 +868,20 @@ mod tests { } ) } + + #[test] + fn deserialize_null_multi_value_headers() { + #[derive(Debug, PartialEq, Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_multi_value_headers")] + multi_value_headers: http::HeaderMap, + } + + assert_eq!( + serde_json::from_str::(r#"{"multi_value_headers":null}"#).expect("failed to deserialize"), + Test { + multi_value_headers: http::HeaderMap::new() + } + ) + } } From 6da8fe9d08e5f24b1790efeb45100b0cd1b6caed Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Sat, 1 Jan 2022 00:37:25 -0500 Subject: [PATCH 065/394] fix: Allow multiple versions of crates (#388) Downstream dependencies may have different versions of crates, and this could change at any time. Disable this lint so that clippy builds are not at the mercy of donwstream dependencies. --- lambda-runtime-api-client/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 3753dedf..9703ecd2 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::all, clippy::cargo)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] +#![warn(clippy::multiple_crate_versions)] //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. From 5e324aeaab07e27bef3c9471066be9836ebb7f8e Mon Sep 17 00:00:00 2001 From: Blake Hildebrand <38637276+bahildebrand@users.noreply.github.com> Date: Wed, 5 Jan 2022 22:01:31 -0500 Subject: [PATCH 066/394] Change examples to use tracing-subscriber for logging (#389) --- lambda-extension/Cargo.toml | 3 +-- lambda-extension/examples/basic.rs | 15 ++++++++++----- lambda-extension/examples/custom_events.rs | 15 ++++++++++----- .../examples/custom_trait_implementation.rs | 15 ++++++++++----- lambda-integration-tests/Cargo.toml | 6 +++--- lambda-integration-tests/src/bin/extension-fn.rs | 14 +++++++++++--- .../src/bin/extension-trait.rs | 14 +++++++++++--- lambda-integration-tests/src/bin/http-fn.rs | 14 +++++++++++--- lambda-integration-tests/src/bin/runtime-fn.rs | 14 +++++++++++--- lambda-integration-tests/src/bin/runtime-trait.rs | 12 +++++++++--- lambda-runtime/Cargo.toml | 3 +-- lambda-runtime/examples/basic.rs | 13 ++++++++----- lambda-runtime/examples/error-handling.rs | 12 ++---------- lambda-runtime/examples/shared_resource.rs | 12 ++++++++---- 14 files changed, 106 insertions(+), 56 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index ac155397..34cd9cf2 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -24,6 +24,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } [dev-dependencies] -simple_logger = "1.6.0" -log = "^0.4" simple-error = "0.2" +tracing-subscriber = "0.3" diff --git a/lambda-extension/examples/basic.rs b/lambda-extension/examples/basic.rs index 573b3281..6ed92652 100644 --- a/lambda-extension/examples/basic.rs +++ b/lambda-extension/examples/basic.rs @@ -1,6 +1,4 @@ use lambda_extension::{extension_fn, Error, NextEvent}; -use log::LevelFilter; -use simple_logger::SimpleLogger; async fn my_extension(event: NextEvent) -> Result<(), Error> { match event { @@ -16,9 +14,16 @@ async fn my_extension(event: NextEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // required to enable CloudWatch error logging by the runtime - // can be replaced with any other method of initializing `log` - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); let func = extension_fn(my_extension); lambda_extension::run(func).await diff --git a/lambda-extension/examples/custom_events.rs b/lambda-extension/examples/custom_events.rs index 88f040aa..cd3dae46 100644 --- a/lambda-extension/examples/custom_events.rs +++ b/lambda-extension/examples/custom_events.rs @@ -1,6 +1,4 @@ use lambda_extension::{extension_fn, Error, NextEvent, Runtime}; -use log::LevelFilter; -use simple_logger::SimpleLogger; async fn my_extension(event: NextEvent) -> Result<(), Error> { match event { @@ -18,9 +16,16 @@ async fn my_extension(event: NextEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // required to enable CloudWatch error logging by the runtime - // can be replaced with any other method of initializing `log` - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); let func = extension_fn(my_extension); diff --git a/lambda-extension/examples/custom_trait_implementation.rs b/lambda-extension/examples/custom_trait_implementation.rs index caef7730..910a7e07 100644 --- a/lambda-extension/examples/custom_trait_implementation.rs +++ b/lambda-extension/examples/custom_trait_implementation.rs @@ -1,6 +1,4 @@ use lambda_extension::{run, Error, Extension, InvokeEvent, NextEvent}; -use log::LevelFilter; -use simple_logger::SimpleLogger; use std::{ future::{ready, Future}, pin::Pin, @@ -28,9 +26,16 @@ impl Extension for MyExtension { #[tokio::main] async fn main() -> Result<(), Error> { - // required to enable CloudWatch error logging by the runtime - // can be replaced with any other method of initializing `log` - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); run(MyExtension::default()).await } diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index b250e911..eafea4d2 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -15,7 +15,7 @@ readme = "../README.md" lambda_http = { path = "../lambda-http", version = "0.4.1" } lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } lambda_extension = { path = "../lambda-extension", version = "0.1.0" } -log = "0.4" serde = { version = "1", features = ["derive"] } -simple_logger = { version = "1.15", default-features = false } -tokio = { version = "1", features = ["full"] } \ No newline at end of file +tokio = { version = "1", features = ["full"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = "0.3" \ No newline at end of file diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs index ad641d6c..fabdb4cd 100644 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -1,6 +1,5 @@ use lambda_extension::{extension_fn, Error, NextEvent}; -use log::{info, LevelFilter}; -use simple_logger::SimpleLogger; +use tracing::info; async fn my_extension(event: NextEvent) -> Result<(), Error> { match event { @@ -17,7 +16,16 @@ async fn my_extension(event: NextEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); lambda_extension::run(extension_fn(my_extension)).await } diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index de9031e6..62ab2dc7 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -1,10 +1,9 @@ use lambda_extension::{Error, Extension, NextEvent}; -use log::{info, LevelFilter}; -use simple_logger::SimpleLogger; use std::{ future::{ready, Future}, pin::Pin, }; +use tracing::info; #[derive(Default)] struct MyExtension { @@ -31,7 +30,16 @@ impl Extension for MyExtension { #[tokio::main] async fn main() -> Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); lambda_extension::run(MyExtension::default()).await } diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index 26be7535..23b4961c 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -2,8 +2,7 @@ use lambda_http::{ lambda_runtime::{self, Context, Error}, IntoResponse, Request, Response, }; -use log::{info, LevelFilter}; -use simple_logger::SimpleLogger; +use tracing::info; async fn handler(event: Request, _context: Context) -> Result { info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); @@ -13,7 +12,16 @@ async fn handler(event: Request, _context: Context) -> Result Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); lambda_runtime::run(lambda_http::handler(handler)).await } diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs index 37057aef..327316c3 100644 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ b/lambda-integration-tests/src/bin/runtime-fn.rs @@ -1,7 +1,6 @@ use lambda_runtime::{handler_fn, Context, Error}; -use log::{info, LevelFilter}; use serde::{Deserialize, Serialize}; -use simple_logger::SimpleLogger; +use tracing::info; #[derive(Deserialize, Debug)] struct Request { @@ -23,7 +22,16 @@ async fn handler(event: Request, _context: Context) -> Result { #[tokio::main] async fn main() -> Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); lambda_runtime::run(handler_fn(handler)).await } diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs index e1de6bb1..f7175d09 100644 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -1,11 +1,10 @@ use lambda_runtime::{Context, Error, Handler}; -use log::{info, LevelFilter}; use serde::{Deserialize, Serialize}; -use simple_logger::SimpleLogger; use std::{ future::{ready, Future}, pin::Pin, }; +use tracing::info; #[derive(Deserialize, Debug)] struct Request { @@ -37,7 +36,14 @@ impl Handler for MyHandler { #[tokio::main] async fn main() -> Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); lambda_runtime::run(MyHandler::default()).await } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 3e567ed9..5bf36f13 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -28,6 +28,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } [dev-dependencies] -simple_logger = "1.6.0" -log = "^0.4" +tracing-subscriber = "0.3" simple-error = "0.2" diff --git a/lambda-runtime/examples/basic.rs b/lambda-runtime/examples/basic.rs index d74e10de..d4c962a7 100644 --- a/lambda-runtime/examples/basic.rs +++ b/lambda-runtime/examples/basic.rs @@ -2,9 +2,7 @@ // { "command": "do something" } use lambda_runtime::{handler_fn, Context, Error}; -use log::LevelFilter; use serde::{Deserialize, Serialize}; -use simple_logger::SimpleLogger; /// This is also a made-up example. Requests come into the runtime as unicode /// strings in json format, which can map to any structure that implements `serde::Deserialize` @@ -26,9 +24,14 @@ struct Response { #[tokio::main] async fn main() -> Result<(), Error> { - // required to enable CloudWatch error logging by the runtime - // can be replaced with any other method of initializing `log` - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); let func = handler_fn(my_handler); lambda_runtime::run(func).await?; diff --git a/lambda-runtime/examples/error-handling.rs b/lambda-runtime/examples/error-handling.rs index a0683f21..0957f4ce 100644 --- a/lambda-runtime/examples/error-handling.rs +++ b/lambda-runtime/examples/error-handling.rs @@ -1,9 +1,7 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda use lambda_runtime::{handler_fn, Error}; -use log::LevelFilter; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use simple_logger::SimpleLogger; use std::fs::File; /// A simple Lambda request structure with just one field @@ -51,13 +49,8 @@ impl std::fmt::Display for CustomError { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `log` with `simple_logger` - // or another compatible crate. The runtime is using `tracing` internally. - // You can comment out the `simple_logger` init line and uncomment the following block to - // use `tracing` in the handler function. - // - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); - /* + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) // this needs to be set to false, otherwise ANSI color codes will @@ -66,7 +59,6 @@ async fn main() -> Result<(), Error> { // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); - */ // call the actual handler of the request let func = handler_fn(func); diff --git a/lambda-runtime/examples/shared_resource.rs b/lambda-runtime/examples/shared_resource.rs index 64fb2e62..8110c15c 100644 --- a/lambda-runtime/examples/shared_resource.rs +++ b/lambda-runtime/examples/shared_resource.rs @@ -5,9 +5,7 @@ // { "command": "do something" } use lambda_runtime::{handler_fn, Context, Error}; -use log::LevelFilter; use serde::{Deserialize, Serialize}; -use simple_logger::SimpleLogger; /// This is also a made-up example. Requests come into the runtime as unicode /// strings in json format, which can map to any structure that implements `serde::Deserialize` @@ -47,8 +45,14 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - // can be replaced with any other method of initializing `log` - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); let client = SharedClient::new("Shared Client 1 (perhaps a database)"); let client_ref = &client; From 7b2c48a6a6e02ccf40c2e9906c4e2ab860f60e28 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 7 Jan 2022 00:31:08 -0800 Subject: [PATCH 067/394] Allow base paths in the runtime api uri (#393) This change allows to set the runtime api location with a base path. This helps tools outside production to set their own full locations that might not be in the root of the uri. Signed-off-by: David Calavera --- lambda-runtime-api-client/src/lib.rs | 75 ++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 9703ecd2..83d789cc 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -4,7 +4,7 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. -use http::{uri::Scheme, Request, Response, Uri}; +use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; use hyper::{ client::{connect::Connection, HttpConnector}, Body, @@ -58,26 +58,24 @@ where fn set_origin(&self, req: Request) -> Result, Error> { let (mut parts, body) = req.into_parts(); - let (scheme, authority) = { + let (scheme, authority, base_path) = { let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP); let authority = self.base.authority().expect("Authority not found"); - (scheme, authority) + let base_path = self.base.path().trim_end_matches('/'); + (scheme, authority, base_path) }; let path = parts.uri.path_and_query().expect("PathAndQuery not found"); + let pq: PathAndQuery = format!("{}{}", base_path, path).parse().expect("PathAndQuery invalid"); let uri = Uri::builder() - .scheme(scheme.clone()) - .authority(authority.clone()) - .path_and_query(path.clone()) - .build(); - - match uri { - Ok(u) => { - parts.uri = u; - Ok(Request::from_parts(parts, body)) - } - Err(e) => Err(Box::new(e)), - } + .scheme(scheme.as_ref()) + .authority(authority.as_ref()) + .path_and_query(pq) + .build() + .map_err(Box::new)?; + + parts.uri = uri; + Ok(Request::from_parts(parts, body)) } } @@ -133,3 +131,50 @@ where pub fn build_request() -> http::request::Builder { http::Request::builder().header(USER_AGENT_HEADER, USER_AGENT) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_set_origin() { + let base = "http://localhost:9001"; + let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap(); + let req = build_request() + .uri("/2018-06-01/runtime/invocation/next") + .body(()) + .unwrap(); + let req = client.set_origin(req).unwrap(); + assert_eq!( + "http://localhost:9001/2018-06-01/runtime/invocation/next", + &req.uri().to_string() + ); + } + + #[test] + fn test_set_origin_with_base_path() { + let base = "http://localhost:9001/foo"; + let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap(); + let req = build_request() + .uri("/2018-06-01/runtime/invocation/next") + .body(()) + .unwrap(); + let req = client.set_origin(req).unwrap(); + assert_eq!( + "http://localhost:9001/foo/2018-06-01/runtime/invocation/next", + &req.uri().to_string() + ); + + let base = "http://localhost:9001/foo/"; + let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap(); + let req = build_request() + .uri("/2018-06-01/runtime/invocation/next") + .body(()) + .unwrap(); + let req = client.set_origin(req).unwrap(); + assert_eq!( + "http://localhost:9001/foo/2018-06-01/runtime/invocation/next", + &req.uri().to_string() + ); + } +} From d7b7aa7effc84f575a706bb789051f5a71b31801 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 7 Jan 2022 18:57:18 -0800 Subject: [PATCH 068/394] Wrap incoming extension events in a struct (#394) This new structure can be used to pass additional information to the extension in each invocation, like the extension id that the Runtime assigns to the extension when it's initialized. Signed-off-by: David Calavera --- lambda-extension/examples/basic.rs | 6 +++--- lambda-extension/examples/custom_events.rs | 6 +++--- .../examples/custom_trait_implementation.rs | 6 +++--- lambda-extension/src/lib.rs | 20 ++++++++++++++++--- .../src/bin/extension-fn.rs | 6 +++--- .../src/bin/extension-trait.rs | 6 +++--- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lambda-extension/examples/basic.rs b/lambda-extension/examples/basic.rs index 6ed92652..aedc046b 100644 --- a/lambda-extension/examples/basic.rs +++ b/lambda-extension/examples/basic.rs @@ -1,7 +1,7 @@ -use lambda_extension::{extension_fn, Error, NextEvent}; +use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent}; -async fn my_extension(event: NextEvent) -> Result<(), Error> { - match event { +async fn my_extension(event: LambdaEvent) -> Result<(), Error> { + match event.next { NextEvent::Shutdown(_e) => { // do something with the shutdown event } diff --git a/lambda-extension/examples/custom_events.rs b/lambda-extension/examples/custom_events.rs index cd3dae46..560fa295 100644 --- a/lambda-extension/examples/custom_events.rs +++ b/lambda-extension/examples/custom_events.rs @@ -1,7 +1,7 @@ -use lambda_extension::{extension_fn, Error, NextEvent, Runtime}; +use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent, Runtime}; -async fn my_extension(event: NextEvent) -> Result<(), Error> { - match event { +async fn my_extension(event: LambdaEvent) -> Result<(), Error> { + match event.next { NextEvent::Shutdown(_e) => { // do something with the shutdown event } diff --git a/lambda-extension/examples/custom_trait_implementation.rs b/lambda-extension/examples/custom_trait_implementation.rs index 910a7e07..7f056957 100644 --- a/lambda-extension/examples/custom_trait_implementation.rs +++ b/lambda-extension/examples/custom_trait_implementation.rs @@ -1,4 +1,4 @@ -use lambda_extension::{run, Error, Extension, InvokeEvent, NextEvent}; +use lambda_extension::{run, Error, Extension, InvokeEvent, LambdaEvent, NextEvent}; use std::{ future::{ready, Future}, pin::Pin, @@ -11,8 +11,8 @@ struct MyExtension { impl Extension for MyExtension { type Fut = Pin>>>; - fn call(&mut self, event: NextEvent) -> Self::Fut { - match event { + fn call(&mut self, event: LambdaEvent) -> Self::Fut { + match event.next { NextEvent::Shutdown(_e) => { self.data.clear(); } diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index f1d34a18..c38947e5 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -94,12 +94,21 @@ impl NextEvent { } } +/// Wrapper with information about the next +/// event that the Lambda Runtime is going to process +pub struct LambdaEvent { + /// ID assigned to this extension by the Lambda Runtime + pub extension_id: String, + /// Next incoming event + pub next: NextEvent, +} + /// A trait describing an asynchronous extension. pub trait Extension { /// Response of this Extension. type Fut: Future>; /// Handle the incoming event. - fn call(&mut self, event: NextEvent) -> Self::Fut; + fn call(&mut self, event: LambdaEvent) -> Self::Fut; } /// Returns a new [`ExtensionFn`] with the given closure. @@ -119,11 +128,11 @@ pub struct ExtensionFn { impl Extension for ExtensionFn where - F: Fn(NextEvent) -> Fut, + F: Fn(LambdaEvent) -> Fut, Fut: Future>, { type Fut = Fut; - fn call(&mut self, event: NextEvent) -> Self::Fut { + fn call(&mut self, event: LambdaEvent) -> Self::Fut { (self.f)(event) } } @@ -173,6 +182,11 @@ where let event: NextEvent = serde_json::from_slice(&body)?; let is_invoke = event.is_invoke(); + let event = LambdaEvent { + extension_id: self.extension_id.clone(), + next: event, + }; + let res = extension.call(event).await; if let Err(error) = res { let req = if is_invoke { diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs index fabdb4cd..5835b0f7 100644 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -1,8 +1,8 @@ -use lambda_extension::{extension_fn, Error, NextEvent}; +use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent}; use tracing::info; -async fn my_extension(event: NextEvent) -> Result<(), Error> { - match event { +async fn my_extension(event: LambdaEvent) -> Result<(), Error> { + match event.next { NextEvent::Shutdown(e) => { info!("[extension-fn] Shutdown event received: {:?}", e); } diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index 62ab2dc7..bc4b3b32 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -1,4 +1,4 @@ -use lambda_extension::{Error, Extension, NextEvent}; +use lambda_extension::{Error, Extension, LambdaEvent, NextEvent}; use std::{ future::{ready, Future}, pin::Pin, @@ -13,8 +13,8 @@ struct MyExtension { impl Extension for MyExtension { type Fut = Pin>>>; - fn call(&mut self, event: NextEvent) -> Self::Fut { - match event { + fn call(&mut self, event: LambdaEvent) -> Self::Fut { + match event.next { NextEvent::Shutdown(e) => { info!("[extension] Shutdown event received: {:?}", e); } From 917ed63c89a50674397f2fc8be356dedcf168b8b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 12 Jan 2022 19:07:22 -0800 Subject: [PATCH 069/394] Fix request query strings (#395) - Add query strings to the request uri. This ensures that we don't mangle information coming into lambda. - Fix bug in StrMapIter that ignored multi-value query parameters. Until now, this iterator only looped over the first element of each key, even if the key had multiple values assigned. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 33 +++++++++++++++++--- lambda-http/src/strmap.rs | 62 +++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index de61f726..4b230e51 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -441,7 +441,7 @@ impl<'a> From> for http::Request { .method(http_method) .uri({ let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); - match host { + let mut uri = match host { Some(host) => { format!( "{}://{}{}", @@ -454,7 +454,17 @@ impl<'a> From> for http::Request { ) } None => path.to_string(), + }; + + if !multi_value_query_string_parameters.is_empty() { + uri.push('?'); + uri.push_str(multi_value_query_string_parameters.to_query_string().as_str()); + } else if !query_string_parameters.is_empty() { + uri.push('?'); + uri.push_str(query_string_parameters.to_query_string().as_str()); } + + uri }) // multi-valued query string parameters are always a super // set of singly valued query string parameters, @@ -507,7 +517,7 @@ impl<'a> From> for http::Request { .method(http_method) .uri({ let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); - match host { + let mut uri = match host { Some(host) => { format!( "{}://{}{}", @@ -520,7 +530,17 @@ impl<'a> From> for http::Request { ) } None => path.to_string(), + }; + + if !multi_value_query_string_parameters.is_empty() { + uri.push('?'); + uri.push_str(multi_value_query_string_parameters.to_query_string().as_str()); + } else if !query_string_parameters.is_empty() { + uri.push('?'); + uri.push_str(query_string_parameters.to_query_string().as_str()); } + + uri }) // multi valued query string parameters are always a super // set of singly valued query string parameters, @@ -698,7 +718,7 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!( req.uri(), - "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello" + "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me" ); // Ensure this is an APIGW request @@ -727,7 +747,10 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "https://lambda-846800462-us-east-2.elb.amazonaws.com/"); + assert_eq!( + req.uri(), + "https://lambda-846800462-us-east-2.elb.amazonaws.com/?myKey=val2" + ); // Ensure this is an ALB request let req_context = req.request_context(); @@ -822,7 +845,7 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "/test/hello"); + assert_eq!(req.uri(), "/test/hello?name=me"); } #[test] diff --git a/lambda-http/src/strmap.rs b/lambda-http/src/strmap.rs index 281a1101..066c575a 100644 --- a/lambda-http/src/strmap.rs +++ b/lambda-http/src/strmap.rs @@ -41,6 +41,20 @@ impl StrMap { StrMapIter { data: self, keys: self.0.keys(), + current: None, + next_idx: 0, + } + } + + /// Return the URI query representation for this map + pub fn to_query_string(&self) -> String { + if self.is_empty() { + "".into() + } else { + self.iter() + .map(|(k, v)| format!("{}={}", k, v)) + .collect::>() + .join("&") } } } @@ -62,6 +76,8 @@ impl From>> for StrMap { pub struct StrMapIter<'a> { data: &'a StrMap, keys: Keys<'a, String, Vec>, + current: Option<(&'a String, Vec<&'a str>)>, + next_idx: usize, } impl<'a> Iterator for StrMapIter<'a> { @@ -69,7 +85,31 @@ impl<'a> Iterator for StrMapIter<'a> { #[inline] fn next(&mut self) -> Option<(&'a str, &'a str)> { - self.keys.next().and_then(|k| self.data.get(k).map(|v| (k.as_str(), v))) + if self.current.is_none() { + self.current = self.keys.next().map(|k| (k, self.data.get_all(k).unwrap_or_default())); + }; + + let mut reset = false; + let ret = if let Some((key, values)) = &self.current { + let value = values[self.next_idx]; + + if self.next_idx + 1 < values.len() { + self.next_idx += 1; + } else { + reset = true; + } + + Some((key.as_str(), value)) + } else { + None + }; + + if reset { + self.current = None; + self.next_idx = 0; + } + + ret } } @@ -158,4 +198,24 @@ mod tests { values.sort(); assert_eq!(values, vec!["bar", "boom"]); } + + #[test] + fn test_empty_str_map_to_query_string() { + let data = HashMap::new(); + let strmap = StrMap(data.into()); + let query = strmap.to_query_string(); + assert_eq!("", &query); + } + + #[test] + fn test_str_map_to_query_string() { + let mut data = HashMap::new(); + data.insert("foo".into(), vec!["bar".into(), "qux".into()]); + data.insert("baz".into(), vec!["quux".into()]); + + let strmap = StrMap(data.into()); + let query = strmap.to_query_string(); + assert!(query.contains("foo=bar&foo=qux")); + assert!(query.contains("baz=quux")); + } } From 70adf9f82138b2cd61f9752675233b3f36a5b736 Mon Sep 17 00:00:00 2001 From: "Vassil \"Vasco\" Kolarov" Date: Wed, 19 Jan 2022 18:38:15 +0000 Subject: [PATCH 070/394] Added some missing fields to ApiGateway and ApiGatewayRequestContext (#403) * added resource field to ApiGateway enum * updated ApiGatewayContext * updated ApiGatewayContext * run fmt * reverted .gitignore Co-authored-by: Vassil Kolarov --- .gitignore | 2 +- lambda-http/src/request.rs | 16 ++++++++++++++++ .../data/apigw_multi_value_proxy_request.json | 1 + lambda-http/tests/data/apigw_no_host.json | 1 + lambda-http/tests/data/apigw_proxy_request.json | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bb7ec9b9..266ec088 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ lambda.zip output.json .aws-sam -build \ No newline at end of file +build diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 4b230e51..fd509412 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -87,6 +87,8 @@ pub enum LambdaRequest<'a> { #[serde(default)] is_base64_encoded: bool, request_context: ApiGatewayRequestContext, + #[serde(default, deserialize_with = "nullable_default")] + resource: Option, }, } @@ -154,10 +156,22 @@ pub struct ApiGatewayRequestContext { pub resource_id: String, /// The deployment stage of the API request (for example, Beta or Prod). pub stage: String, + /// The full domain name used to invoke the API. This should be the same as the incoming Host header. + pub domain_name: Option, + /// The first label of the $context.domainName. This is often used as a caller/customer identifier. + pub domain_prefix: Option, /// The ID that API Gateway assigns to the API request. pub request_id: String, /// The path to your resource. For example, for the non-proxy request URI of `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, The $context.resourcePath value is /root/child. pub resource_path: String, + /// The request protocol, for example, HTTP/1.1. + pub protocol: Option, + /// The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm). + pub request_time: Option, + /// The Epoch-formatted request time, in milliseconds. + pub request_time_epoch: i64, + /// The identifier API Gateway assigns to your API. + pub apiid: Option, /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. pub http_method: String, /// The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. @@ -166,6 +180,7 @@ pub struct ApiGatewayRequestContext { /// The identifier API Gateway assigns to your API. pub api_id: String, /// Cofnito identity information + #[serde(default)] pub identity: Identity, } @@ -436,6 +451,7 @@ impl<'a> From> for http::Request { body, is_base64_encoded, request_context, + resource: _, } => { let builder = http::Request::builder() .method(http_method) diff --git a/lambda-http/tests/data/apigw_multi_value_proxy_request.json b/lambda-http/tests/data/apigw_multi_value_proxy_request.json index 90f784e2..5b254c8b 100644 --- a/lambda-http/tests/data/apigw_multi_value_proxy_request.json +++ b/lambda-http/tests/data/apigw_multi_value_proxy_request.json @@ -109,6 +109,7 @@ "resourceId": "roq9wj", "stage": "testStage", "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "requestTimeEpoch": 1583798639428, "identity": { "cognitoIdentityPoolId": null, "accountId": null, diff --git a/lambda-http/tests/data/apigw_no_host.json b/lambda-http/tests/data/apigw_no_host.json index 369c4870..3143c81b 100644 --- a/lambda-http/tests/data/apigw_no_host.json +++ b/lambda-http/tests/data/apigw_no_host.json @@ -26,6 +26,7 @@ "resourceId": "us4z18", "stage": "test", "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9", + "requestTimeEpoch": 1583798639428, "identity": { "cognitoIdentityPoolId": "", "accountId": "", diff --git a/lambda-http/tests/data/apigw_proxy_request.json b/lambda-http/tests/data/apigw_proxy_request.json index f76d2b8a..3b7cc9d2 100644 --- a/lambda-http/tests/data/apigw_proxy_request.json +++ b/lambda-http/tests/data/apigw_proxy_request.json @@ -27,6 +27,7 @@ "resourceId": "us4z18", "stage": "test", "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9", + "requestTimeEpoch": 1583798639428, "identity": { "cognitoIdentityPoolId": "", "accountId": "", From f33fa059a88869b38646d875688fab58885208de Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Fri, 21 Jan 2022 09:01:53 +0100 Subject: [PATCH 071/394] Implement tower::Service trait (#401) * feat(lambda_runtime): switch to tower::Service * feat(lambda-http): implement tower::Service * feat(lambda-runtime): rename LambdaRequest to LambdaEvent * feat(lambda-extension): implement tower::Service * feat(lambda-http): implement tower::Service (part 1) * feat(lambda-integration-tests): switch to tower::Service * chore: cargo fmt * feat(lambda-http): implement tower::Service (part 2) * feat(lambda-http): implement tower::Service (part 3) * feat(lambda-integration-tests): add lambda_http trait * fix(lambda-http): fix ext example * feat: cleanup + doc --- Makefile | 8 +- README.md | 14 +- lambda-extension/Cargo.toml | 2 +- lambda-extension/examples/basic.rs | 4 +- lambda-extension/examples/custom_events.rs | 4 +- .../examples/custom_trait_implementation.rs | 15 +- lambda-extension/src/lib.rs | 59 +++----- lambda-http/examples/hello-http.rs | 10 +- .../examples/shared-resources-example.rs | 14 +- lambda-http/src/ext.rs | 27 +++- lambda-http/src/lib.rs | 131 +++++++++--------- .../src/bin/extension-fn.rs | 4 +- .../src/bin/extension-trait.rs | 14 +- lambda-integration-tests/src/bin/http-fn.rs | 11 +- .../src/bin/http-trait.rs | 45 ++++++ .../src/bin/runtime-fn.rs | 8 +- .../src/bin/runtime-trait.rs | 17 ++- lambda-integration-tests/template.yaml | 70 +++++++++- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/examples/basic.rs | 10 +- lambda-runtime/examples/error-handling.rs | 8 +- lambda-runtime/examples/shared_resource.rs | 8 +- lambda-runtime/src/lib.rs | 80 ++++------- lambda-runtime/src/types.rs | 21 +++ 24 files changed, 347 insertions(+), 239 deletions(-) create mode 100644 lambda-integration-tests/src/bin/http-trait.rs diff --git a/Makefile b/Makefile index 209208f5..0890173a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ INTEG_STACK_NAME ?= rust-lambda-integration-tests -INTEG_FUNCTIONS_BUILD := runtime-fn runtime-trait http-fn +INTEG_FUNCTIONS_BUILD := runtime-fn runtime-trait http-fn http-trait INTEG_FUNCTIONS_INVOKE := RuntimeFn RuntimeFnAl2 RuntimeTrait RuntimeTraitAl2 Python PythonAl2 INTEG_API_INVOKE := RestApiUrl HttpApiUrl INTEG_EXTENSIONS := extension-fn extension-trait @@ -10,6 +10,8 @@ pr-check: cargo +1.54.0 check --all cargo +stable fmt --all -- --check cargo +stable clippy + cargo +1.54.0 test + cargo +stable test integration-tests: # Build Integration functions @@ -47,7 +49,11 @@ invoke-integration-api-%: --query 'Stacks[0].Outputs[?OutputKey==`$*`].OutputValue' \ --output text)) curl $(API_URL)/get + curl $(API_URL)/trait/get curl $(API_URL)/al2/get + curl $(API_URL)/al2-trait/get curl -X POST -d '{"command": "hello"}' $(API_URL)/post + curl -X POST -d '{"command": "hello"}' $(API_URL)/trait/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post + curl -X POST -d '{"command": "hello"}' $(API_URL)/al2-trait/post \ No newline at end of file diff --git a/README.md b/README.md index ff72aac8..285a97b4 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,18 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. ```rust,no_run -use lambda_runtime::{handler_fn, Context, Error}; +use lambda_runtime::{service_fn, LambdaEvent, Error}; use serde_json::{json, Value}; #[tokio::main] async fn main() -> Result<(), Error> { - let func = handler_fn(func); + let func = service_fn(func); lambda_runtime::run(func).await?; Ok(()) } -async fn func(event: Value, _: Context) -> Result { +async fn func(event: LambdaEvent) -> Result { + let (event, _context) = event.into_parts(); let first_name = event["firstName"].as_str().unwrap_or("world"); Ok(json!({ "message": format!("Hello, {}!", first_name) })) @@ -213,12 +214,9 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt ## `lambda` -`lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides a few major components: +`lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides `lambda_runtime::run`, a function that runs a `tower::Service`. -- `Handler`, a trait that defines interactions between customer-authored code and this library. -- `lambda_runtime::run`, function that runs an `Handler`. - -The function `handler_fn` converts a rust function or closure to `Handler`, which can then be run by `lambda_runtime::run`. +To write a function that will handle request, you need to pass it through `service_fn`, which will convert your function into a `tower::Service`, which can then be run by `lambda_runtime::run`. ## AWS event objects diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 34cd9cf2..7ad1b491 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -19,7 +19,7 @@ bytes = "1.0" http = "0.2" async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } -tower-service = "0.3" +tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } diff --git a/lambda-extension/examples/basic.rs b/lambda-extension/examples/basic.rs index aedc046b..7a722e72 100644 --- a/lambda-extension/examples/basic.rs +++ b/lambda-extension/examples/basic.rs @@ -1,4 +1,4 @@ -use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -25,6 +25,6 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - let func = extension_fn(my_extension); + let func = service_fn(my_extension); lambda_extension::run(func).await } diff --git a/lambda-extension/examples/custom_events.rs b/lambda-extension/examples/custom_events.rs index 560fa295..d2756c23 100644 --- a/lambda-extension/examples/custom_events.rs +++ b/lambda-extension/examples/custom_events.rs @@ -1,4 +1,4 @@ -use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent, Runtime}; +use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent, Runtime}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -27,7 +27,7 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - let func = extension_fn(my_extension); + let func = service_fn(my_extension); let runtime = Runtime::builder().with_events(&["SHUTDOWN"]).register().await?; diff --git a/lambda-extension/examples/custom_trait_implementation.rs b/lambda-extension/examples/custom_trait_implementation.rs index 7f056957..c9dff5c3 100644 --- a/lambda-extension/examples/custom_trait_implementation.rs +++ b/lambda-extension/examples/custom_trait_implementation.rs @@ -1,4 +1,4 @@ -use lambda_extension::{run, Error, Extension, InvokeEvent, LambdaEvent, NextEvent}; +use lambda_extension::{run, Error, InvokeEvent, LambdaEvent, NextEvent, Service}; use std::{ future::{ready, Future}, pin::Pin, @@ -9,9 +9,16 @@ struct MyExtension { data: Vec, } -impl Extension for MyExtension { - type Fut = Pin>>>; - fn call(&mut self, event: LambdaEvent) -> Self::Fut { +impl Service for MyExtension { + type Error = Error; + type Future = Pin>>>; + type Response = (); + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, event: LambdaEvent) -> Self::Future { match event.next { NextEvent::Shutdown(_e) => { self.data.clear(); diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index c38947e5..130aae50 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -9,10 +9,10 @@ use hyper::client::{connect::Connection, HttpConnector}; use lambda_runtime_api_client::Client; use serde::Deserialize; -use std::{future::Future, path::PathBuf}; +use std::{fmt, future::Future, path::PathBuf}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::StreamExt; -use tower_service::Service; +pub use tower::{self, service_fn, Service}; use tracing::trace; /// Include several request builders to interact with the Extension API. @@ -103,40 +103,6 @@ pub struct LambdaEvent { pub next: NextEvent, } -/// A trait describing an asynchronous extension. -pub trait Extension { - /// Response of this Extension. - type Fut: Future>; - /// Handle the incoming event. - fn call(&mut self, event: LambdaEvent) -> Self::Fut; -} - -/// Returns a new [`ExtensionFn`] with the given closure. -/// -/// [`ExtensionFn`]: struct.ExtensionFn.html -pub fn extension_fn(f: F) -> ExtensionFn { - ExtensionFn { f } -} - -/// An [`Extension`] implemented by a closure. -/// -/// [`Extension`]: trait.Extension.html -#[derive(Clone, Debug)] -pub struct ExtensionFn { - f: F, -} - -impl Extension for ExtensionFn -where - F: Fn(LambdaEvent) -> Fut, - Fut: Future>, -{ - type Fut = Fut; - fn call(&mut self, event: LambdaEvent) -> Self::Fut { - (self.f)(event) - } -} - /// The Runtime handles all the incoming extension requests pub struct Runtime = HttpConnector> { extension_id: String, @@ -153,13 +119,18 @@ impl Runtime { impl Runtime where C: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + C::Future: Unpin + Send, + C::Error: Into>, + C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { /// Execute the given extension. /// Register the extension with the Extensions API and wait for incoming events. - pub async fn run(&self, mut extension: impl Extension) -> Result<(), Error> { + pub async fn run(&self, mut extension: E) -> Result<(), Error> + where + E: Service, + E::Future: Future>, + E::Error: Into> + fmt::Display, + { let client = &self.client; let incoming = async_stream::stream! { @@ -196,7 +167,7 @@ where }; self.client.call(req).await?; - return Err(error); + return Err(error.into()); } } @@ -263,9 +234,11 @@ impl<'a> RuntimeBuilder<'a> { } /// Execute the given extension -pub async fn run(extension: Ex) -> Result<(), Error> +pub async fn run(extension: E) -> Result<(), Error> where - Ex: Extension, + E: Service, + E::Future: Future>, + E::Error: Into> + fmt::Display, { Runtime::builder().register().await?.run(extension).await } diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index 0682d028..40352dab 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -1,16 +1,12 @@ -use lambda_http::{ - handler, - lambda_runtime::{self, Context, Error}, - IntoResponse, Request, RequestExt, Response, -}; +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; #[tokio::main] async fn main() -> Result<(), Error> { - lambda_runtime::run(handler(func)).await?; + lambda_http::run(service_fn(func)).await?; Ok(()) } -async fn func(event: Request, _: Context) -> Result { +async fn func(event: Request) -> Result { Ok(match event.query_string_parameters().get("first_name") { Some(first_name) => format!("Hello, {}!", first_name).into_response(), _ => Response::builder() diff --git a/lambda-http/examples/shared-resources-example.rs b/lambda-http/examples/shared-resources-example.rs index 61a30668..24e56f97 100644 --- a/lambda-http/examples/shared-resources-example.rs +++ b/lambda-http/examples/shared-resources-example.rs @@ -1,8 +1,4 @@ -use lambda_http::{ - handler, - lambda_runtime::{self, Context, Error}, - IntoResponse, Request, RequestExt, Response, -}; +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; struct SharedClient { name: &'static str, @@ -23,9 +19,11 @@ async fn main() -> Result<(), Error> { let shared_client_ref = &shared_client; // Define a closure here that makes use of the shared client. - let handler_func_closure = move |event: Request, ctx: Context| async move { + let handler_func_closure = move |event: Request| async move { Ok(match event.query_string_parameters().get("first_name") { - Some(first_name) => shared_client_ref.response(ctx.request_id, first_name).into_response(), + Some(first_name) => shared_client_ref + .response(event.lambda_context().request_id, first_name) + .into_response(), _ => Response::builder() .status(400) .body("Empty first name".into()) @@ -34,6 +32,6 @@ async fn main() -> Result<(), Error> { }; // Pass the closure to the runtime here. - lambda_runtime::run(handler(handler_func_closure)).await?; + lambda_http::run(service_fn(handler_func_closure)).await?; Ok(()) } diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index be094c5a..2f56d78c 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -1,6 +1,7 @@ //! Extension methods for `http::Request` types use crate::{request::RequestContext, strmap::StrMap, Body}; +use lambda_runtime::Context; use serde::{de::value::Error as SerdeError, Deserialize}; use std::{error::Error, fmt}; @@ -66,7 +67,7 @@ impl Error for PayloadError { /// as well as `{"x":1, "y":2}` respectively. /// /// ```rust,no_run -/// use lambda_http::{handler, lambda_runtime::{self, Error, Context}, Body, IntoResponse, Request, Response, RequestExt}; +/// use lambda_http::{service_fn, Error, Context, Body, IntoResponse, Request, Response, RequestExt}; /// use serde::Deserialize; /// /// #[derive(Debug,Deserialize,Default)] @@ -79,13 +80,12 @@ impl Error for PayloadError { /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { -/// lambda_runtime::run(handler(add)).await?; +/// lambda_http::run(service_fn(add)).await?; /// Ok(()) /// } /// /// async fn add( -/// request: Request, -/// _: Context +/// request: Request /// ) -> Result, Error> { /// let args: Args = request.payload() /// .unwrap_or_else(|_parse_err| None) @@ -167,6 +167,12 @@ pub trait RequestExt { fn payload(&self) -> Result, PayloadError> where for<'de> D: Deserialize<'de>; + + /// Return the Lambda function context associated with the request + fn lambda_context(&self) -> Context; + + /// Configures instance with lambda context + fn with_lambda_context(self, context: Context) -> Self; } impl RequestExt for http::Request { @@ -226,6 +232,19 @@ impl RequestExt for http::Request { .expect("Request did not contain a request context") } + fn lambda_context(&self) -> Context { + self.extensions() + .get::() + .cloned() + .expect("Request did not contain a lambda context") + } + + fn with_lambda_context(self, context: Context) -> Self { + let mut s = self; + s.extensions_mut().insert(context); + s + } + fn payload(&self) -> Result, PayloadError> where for<'de> D: Deserialize<'de>, diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 50e567fa..e1119dd0 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -12,18 +12,18 @@ //! ## Hello World //! //! The following example is how you would structure your Lambda such that you have a `main` function where you explicitly invoke -//! `lambda_runtime::run` in combination with the [`handler`](fn.handler.html) function. This pattern allows you to utilize global initialization +//! `lambda_http::run` in combination with the [`service_fn`](fn.service_fn.html) function. This pattern allows you to utilize global initialization //! 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 //! your function's execution path. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda_runtime::{self, Error}}; +//! use lambda_http::{service_fn, Error}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { //! // initialize dependencies once here for the lifetime of your //! // lambda task -//! lambda_runtime::run(handler(|request, context| async { Ok("👋 world!") })).await?; +//! lambda_http::run(service_fn(|request| async { Ok("👋 world!") })).await?; //! Ok(()) //! } //! ``` @@ -31,21 +31,22 @@ //! ## Leveraging trigger provided data //! //! You can also access information provided directly from the underlying trigger events, like query string parameters, -//! with the [`RequestExt`](trait.RequestExt.html) trait. +//! or Lambda function context, with the [`RequestExt`](trait.RequestExt.html) trait. //! //! ```rust,no_run -//! use lambda_http::{handler, lambda_runtime::{self, Context, Error}, IntoResponse, Request, RequestExt}; +//! use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { -//! lambda_runtime::run(handler(hello)).await?; +//! lambda_http::run(service_fn(hello)).await?; //! Ok(()) //! } //! //! async fn hello( -//! request: Request, -//! _: Context +//! request: Request //! ) -> Result { +//! let _context = request.lambda_context(); +//! //! Ok(format!( //! "hello {}", //! request @@ -62,8 +63,8 @@ extern crate maplit; pub use http::{self, Response}; -pub use lambda_runtime::{self, Context}; -use lambda_runtime::{Error, Handler as LambdaHandler}; +use lambda_runtime::LambdaEvent; +pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; mod body; pub mod ext; @@ -85,47 +86,13 @@ use std::{ /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; -/// Functions serving as ALB and API Gateway REST and HTTP API handlers must conform to this type. +/// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] /// -/// This can be viewed as a `lambda_runtime::Handler` constrained to `http` crate `Request` and `Response` types -pub trait Handler<'a>: Sized { - /// The type of Error that this Handler will return - type Error; - /// The type of Response this Handler will return - type Response: IntoResponse; - /// The type of Future this Handler will return - type Fut: Future> + 'a; - /// Function used to execute handler behavior - fn call(&mut self, event: Request, context: Context) -> Self::Fut; -} - -/// Adapts a [`Handler`](trait.Handler.html) to the `lambda_runtime::run` interface -pub fn handler<'a, H: Handler<'a>>(handler: H) -> Adapter<'a, H> { - Adapter { - handler, - _phantom_data: PhantomData, - } -} - -/// An implementation of `Handler` for a given closure return a `Future` representing the computed response -impl<'a, F, R, Fut> Handler<'a> for F -where - F: Fn(Request, Context) -> Fut, - R: IntoResponse, - Fut: Future> + 'a, -{ - type Response = R; - type Error = Error; - type Fut = Fut; - fn call(&mut self, event: Request, context: Context) -> Self::Fut { - (self)(event, context) - } -} - +/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function. #[doc(hidden)] pub struct TransformResponse<'a, R, E> { request_origin: RequestOrigin, - fut: Pin> + 'a>>, + fut: Pin> + Send + 'a>>, } impl<'a, R, E> Future for TransformResponse<'a, R, E> @@ -133,6 +100,7 @@ where R: IntoResponse, { type Output = Result; + fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll { match self.fut.as_mut().poll(cx) { Poll::Ready(result) => Poll::Ready( @@ -143,34 +111,61 @@ where } } -/// Exists only to satisfy the trait cover rule for `lambda_runtime::Handler` impl +/// Wraps a `Service` in a `Service>` /// -/// User code should never need to interact with this type directly. Since `Adapter` implements `Handler` -/// It serves as a opaque trait covering type. -/// -/// See [this article](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/) -/// for a larger explanation of why this is necessary -pub struct Adapter<'a, H: Handler<'a>> { - handler: H, - _phantom_data: PhantomData<&'a H>, +/// This is completely internal to the `lambda_http::run` function. +#[doc(hidden)] +pub struct Adapter<'a, R, S> { + service: S, + _phantom_data: PhantomData<&'a R>, } -impl<'a, H: Handler<'a>> Handler<'a> for Adapter<'a, H> { - type Response = H::Response; - type Error = H::Error; - type Fut = H::Fut; - fn call(&mut self, event: Request, context: Context) -> Self::Fut { - self.handler.call(event, context) +impl<'a, R, S> From for Adapter<'a, R, S> +where + S: Service + Send, + S::Future: Send + 'a, + R: IntoResponse, +{ + fn from(service: S) -> Self { + Adapter { + service, + _phantom_data: PhantomData, + } } } -impl<'a, 'b, H: Handler<'a>> LambdaHandler, LambdaResponse> for Adapter<'a, H> { - type Error = H::Error; - type Fut = TransformResponse<'a, H::Response, Self::Error>; +impl<'a, R, S> Service>> for Adapter<'a, R, S> +where + S: Service + Send, + S::Future: Send + 'a, + R: IntoResponse, +{ + type Response = LambdaResponse; + type Error = Error; + type Future = TransformResponse<'a, R, Self::Error>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } - fn call(&mut self, event: LambdaRequest<'_>, context: Context) -> Self::Fut { - let request_origin = event.request_origin(); - let fut = Box::pin(self.handler.call(event.into(), context)); + fn call(&mut self, req: LambdaEvent>) -> 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_origin, fut } } } + +/// Starts the Lambda Rust runtime and begins polling for events on the [Lambda +/// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). +/// +/// This takes care of transforming the LambdaEvent into a [`Request`] and then +/// converting the result into a [`LambdaResponse`]. +pub async fn run<'a, R, S>(handler: S) -> Result<(), Error> +where + S: Service + Send, + S::Future: Send + 'a, + R: IntoResponse, +{ + lambda_runtime::run(Adapter::from(handler)).await +} diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs index 5835b0f7..ea5fc26c 100644 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -1,4 +1,4 @@ -use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; use tracing::info; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { @@ -27,5 +27,5 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - lambda_extension::run(extension_fn(my_extension)).await + lambda_extension::run(service_fn(my_extension)).await } diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index bc4b3b32..1dc73c75 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -1,4 +1,4 @@ -use lambda_extension::{Error, Extension, LambdaEvent, NextEvent}; +use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; use std::{ future::{ready, Future}, pin::Pin, @@ -10,10 +10,16 @@ struct MyExtension { invoke_count: usize, } -impl Extension for MyExtension { - type Fut = Pin>>>; +impl Service for MyExtension { + type Error = Error; + type Future = Pin>>>; + type Response = (); - fn call(&mut self, event: LambdaEvent) -> Self::Fut { + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, event: LambdaEvent) -> Self::Future { match event.next { NextEvent::Shutdown(e) => { info!("[extension] Shutdown event received: {:?}", e); diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index 23b4961c..b411b77f 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -1,10 +1,8 @@ -use lambda_http::{ - lambda_runtime::{self, Context, Error}, - IntoResponse, Request, Response, -}; +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; use tracing::info; -async fn handler(event: Request, _context: Context) -> Result { +async fn handler(event: Request) -> Result { + let _context = event.lambda_context(); info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); Ok(Response::builder().status(200).body("Hello, world!").unwrap()) @@ -23,5 +21,6 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - lambda_runtime::run(lambda_http::handler(handler)).await + let handler = service_fn(handler); + lambda_http::run(handler).await } diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs new file mode 100644 index 00000000..091aec8e --- /dev/null +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -0,0 +1,45 @@ +use lambda_http::{Error, Request, RequestExt, Response, Service}; +use std::{ + future::{ready, Future}, + pin::Pin, +}; +use tracing::info; + +#[derive(Default)] +struct MyHandler { + invoke_count: usize, +} + +impl Service for MyHandler { + type Error = Error; + type Future = Pin> + Send>>; + type Response = Response<&'static str>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: Request) -> Self::Future { + self.invoke_count += 1; + info!("[http-trait] Received event {}: {:?}", self.invoke_count, request); + info!("[http-trait] Lambda context: {:?}", request.lambda_context()); + Box::pin(ready(Ok(Response::builder() + .status(200) + .body("Hello, World!") + .unwrap()))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + lambda_http::run(MyHandler::default()).await +} diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs index 327316c3..1b3f3e0d 100644 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ b/lambda-integration-tests/src/bin/runtime-fn.rs @@ -1,4 +1,4 @@ -use lambda_runtime::{handler_fn, Context, Error}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use tracing::info; @@ -12,9 +12,11 @@ struct Response { message: String, } -async fn handler(event: Request, _context: Context) -> Result { +async fn handler(event: LambdaEvent) -> Result { info!("[handler-fn] Received event: {:?}", event); + let (event, _) = event.into_parts(); + Ok(Response { message: event.command.to_uppercase(), }) @@ -33,5 +35,5 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - lambda_runtime::run(handler_fn(handler)).await + lambda_runtime::run(service_fn(handler)).await } diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs index f7175d09..d86bafdc 100644 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -1,4 +1,4 @@ -use lambda_runtime::{Context, Error, Handler}; +use lambda_runtime::{Error, LambdaEvent, Service}; use serde::{Deserialize, Serialize}; use std::{ future::{ready, Future}, @@ -21,15 +21,20 @@ struct MyHandler { invoke_count: usize, } -impl Handler for MyHandler { +impl Service> for MyHandler { type Error = Error; - type Fut = Pin>>>; + type Future = Pin>>>; + type Response = Response; - fn call(&mut self, event: Request, _context: Context) -> Self::Fut { + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: LambdaEvent) -> Self::Future { self.invoke_count += 1; - info!("[handler] Received event {}: {:?}", self.invoke_count, event); + info!("[handler] Received event {}: {:?}", self.invoke_count, request); Box::pin(ready(Ok(Response { - message: event.command.to_uppercase(), + message: request.payload.command.to_uppercase(), }))) } } diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index 7f408d2c..848f8be5 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -28,7 +28,7 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait - # Rust function using a Handler implementation running on AL2 + # Rust function using a Service implementation running on AL2 RuntimeTraitAl2: Type: AWS::Serverless::Function Properties: @@ -38,7 +38,7 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait - # Rust function using a Handler implementation running on AL1 + # Rust function using a Service implementation running on AL1 RuntimeTrait: Type: AWS::Serverless::Function Properties: @@ -48,7 +48,7 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait - # Rust function using lambda_http::runtime running on AL2 + # Rust function using lambda_http::service_fn running on AL2 HttpFnAl2: Type: AWS::Serverless::Function Properties: @@ -78,8 +78,39 @@ Resources: Layers: - !Ref ExtensionFn - !Ref ExtensionTrait + + # Rust function using lambda_http with Service running on AL2 + HttpTraitAl2: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-trait/ + Runtime: provided.al2 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2-trait/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2-trait/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2-trait/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2-trait/post + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait - # Rust function using lambda_http::runtime running on AL1 + # Rust function using lambda_http::service_fn running on AL1 HttpFn: Type: AWS::Serverless::Function Properties: @@ -110,6 +141,37 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using lambda_http with Service running on AL1 + HttpTrait: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-trait/ + Runtime: provided + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /trait/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /trait/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /trait/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /trait/post + Layers: + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Python function running on AL2 PythonAl2: Type: AWS::Serverless::Function diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 5bf36f13..4bdb1c84 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -23,7 +23,7 @@ bytes = "1.0" http = "0.2" async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } -tower-service = "0.3" +tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } diff --git a/lambda-runtime/examples/basic.rs b/lambda-runtime/examples/basic.rs index d4c962a7..26589f0d 100644 --- a/lambda-runtime/examples/basic.rs +++ b/lambda-runtime/examples/basic.rs @@ -1,7 +1,7 @@ // This example requires the following input to succeed: // { "command": "do something" } -use lambda_runtime::{handler_fn, Context, Error}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -33,18 +33,18 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - let func = handler_fn(my_handler); + let func = service_fn(my_handler); lambda_runtime::run(func).await?; Ok(()) } -pub(crate) async fn my_handler(event: Request, ctx: Context) -> Result { +pub(crate) async fn my_handler(event: LambdaEvent) -> Result { // extract some useful info from the request - let command = event.command; + let command = event.payload.command; // prepare the response let resp = Response { - req_id: ctx.request_id, + req_id: event.context.request_id, msg: format!("Command {} executed.", command), }; diff --git a/lambda-runtime/examples/error-handling.rs b/lambda-runtime/examples/error-handling.rs index 0957f4ce..6b7a3c96 100644 --- a/lambda-runtime/examples/error-handling.rs +++ b/lambda-runtime/examples/error-handling.rs @@ -1,5 +1,5 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda -use lambda_runtime::{handler_fn, Error}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::fs::File; @@ -61,13 +61,15 @@ async fn main() -> Result<(), Error> { .init(); // call the actual handler of the request - let func = handler_fn(func); + let func = service_fn(func); lambda_runtime::run(func).await?; Ok(()) } /// The actual handler of the Lambda request. -pub(crate) async fn func(event: Value, ctx: lambda_runtime::Context) -> Result { +pub(crate) async fn func(event: LambdaEvent) -> Result { + let (event, ctx) = event.into_parts(); + // check what action was requested match serde_json::from_value::(event)?.event_type { EventType::SimpleError => { diff --git a/lambda-runtime/examples/shared_resource.rs b/lambda-runtime/examples/shared_resource.rs index 8110c15c..cd9366dd 100644 --- a/lambda-runtime/examples/shared_resource.rs +++ b/lambda-runtime/examples/shared_resource.rs @@ -4,7 +4,7 @@ // Run it with the following input: // { "command": "do something" } -use lambda_runtime::{handler_fn, Context, Error}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -56,9 +56,9 @@ async fn main() -> Result<(), Error> { let client = SharedClient::new("Shared Client 1 (perhaps a database)"); let client_ref = &client; - lambda_runtime::run(handler_fn(move |event: Request, ctx: Context| async move { - let command = event.command; - Ok::(client_ref.response(ctx.request_id, command)) + lambda_runtime::run(service_fn(move |event: LambdaEvent| async move { + let command = event.payload.command; + Ok::(client_ref.response(event.context.request_id, command)) })) .await?; Ok(()) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index ad6fecf1..c49cbfa3 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -4,16 +4,17 @@ //! The mechanism available for defining a Lambda function is as follows: //! -//! Create a type that conforms to the [`Handler`] trait. This type can then be passed -//! to the the `lambda_runtime::run` function, which launches and runs the Lambda runtime. -pub use crate::types::Context; +//! Create a type that conforms to the [`tower::Service`] trait. This type can +//! then be passed to the the `lambda_runtime::run` function, which launches +//! and runs the Lambda runtime. use hyper::client::{connect::Connection, HttpConnector}; use lambda_runtime_api_client::Client; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, env, fmt, future::Future, panic}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; -use tower_service::Service; +use tower::util::ServiceFn; +pub use tower::{self, service_fn, Service}; use tracing::{error, trace}; mod requests; @@ -24,6 +25,7 @@ mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; use types::Diagnostic; +pub use types::{Context, LambdaEvent}; /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::Error; @@ -60,42 +62,13 @@ impl Config { } } -/// A trait describing an asynchronous function `A` to `B`. -pub trait Handler { - /// Errors returned by this handler. - type Error; - /// Response of this handler. - type Fut: Future>; - /// Handle the incoming event. - fn call(&mut self, event: A, context: Context) -> Self::Fut; -} - -/// Returns a new [`HandlerFn`] with the given closure. -/// -/// [`HandlerFn`]: struct.HandlerFn.html -pub fn handler_fn(f: F) -> HandlerFn { - HandlerFn { f } -} - -/// A [`Handler`] implemented by a closure. -/// -/// [`Handler`]: trait.Handler.html -#[derive(Clone, Debug)] -pub struct HandlerFn { - f: F, -} - -impl Handler for HandlerFn +/// Return a new [`ServiceFn`] with a closure that takes an event and context as separate arguments. +#[deprecated(since = "0.5.0", note = "Use `service_fn` and `LambdaEvent` instead")] +pub fn handler_fn(f: F) -> ServiceFn) -> Fut> where F: Fn(A, Context) -> Fut, - Fut: Future>, - Error: Into> + fmt::Display, { - type Error = Error; - type Fut = Fut; - fn call(&mut self, req: A, ctx: Context) -> Self::Fut { - (self.f)(req, ctx) - } + service_fn(move |req: LambdaEvent| f(req.payload, req.context)) } struct Runtime = HttpConnector> { @@ -105,9 +78,9 @@ struct Runtime = HttpConnector> { impl Runtime where C: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + C::Future: Unpin + Send, + C::Error: Into>, + C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { pub async fn run( &self, @@ -116,9 +89,9 @@ where config: &Config, ) -> Result<(), Error> where - F: Handler, - >::Fut: Future>::Error>>, - >::Error: fmt::Display, + F: Service>, + F::Future: Future>, + F::Error: fmt::Display, A: for<'de> Deserialize<'de>, B: Serialize, { @@ -139,7 +112,7 @@ where env::set_var("_X_AMZN_TRACE_ID", xray_trace_id); let request_id = &ctx.request_id.clone(); - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(body, ctx))); + let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); let req = match task { Ok(response) => match response.await { @@ -208,25 +181,25 @@ where /// /// # Example /// ```no_run -/// use lambda_runtime::{Error, handler_fn, Context}; +/// use lambda_runtime::{Error, service_fn, LambdaEvent}; /// use serde_json::Value; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { -/// let func = handler_fn(func); +/// let func = service_fn(func); /// lambda_runtime::run(func).await?; /// Ok(()) /// } /// -/// async fn func(event: Value, _: Context) -> Result { -/// Ok(event) +/// async fn func(event: LambdaEvent) -> Result { +/// Ok(event.payload) /// } /// ``` pub async fn run(handler: F) -> Result<(), Error> where - F: Handler, - >::Fut: Future>::Error>>, - >::Error: fmt::Display, + F: Service>, + F::Future: Future>, + F::Error: fmt::Display, A: for<'de> Deserialize<'de>, B: Serialize, { @@ -462,10 +435,11 @@ mod endpoint_tests { .build() .expect("Unable to build client"); - async fn func(event: serde_json::Value, _: crate::Context) -> Result { + async fn func(event: crate::LambdaEvent) -> Result { + let (event, _) = event.into_parts(); Ok(event) } - let f = crate::handler_fn(func); + let f = crate::service_fn(func); // set env vars needed to init Config if they are not already set in the environment if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 8c9e5321..ee71ba1c 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -166,6 +166,27 @@ impl TryFrom for Context { } } +/// Incoming Lambda request containing the event payload and context. +#[derive(Clone, Debug)] +pub struct LambdaEvent { + /// Event payload. + pub payload: T, + /// Invocation context. + pub context: Context, +} + +impl LambdaEvent { + /// Creates a new Lambda request + pub fn new(payload: T, context: Context) -> Self { + Self { payload, context } + } + + /// Split the Lambda event into its payload and context. + pub fn into_parts(self) -> (T, Context) { + (self.payload, self.context) + } +} + #[cfg(test)] mod test { use super::*; From 4cd320195d5f73290e8a431923827c3d5c93cb08 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sat, 22 Jan 2022 09:27:18 +0100 Subject: [PATCH 072/394] feat(lambda-http): implement http-body::Body for lambda_http::body (#406) --- lambda-http/Cargo.toml | 2 ++ lambda-http/src/body.rs | 45 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 7ce1fdaf..3bbe9d26 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -18,7 +18,9 @@ maintenance = { status = "actively-developed" } [dependencies] base64 = "0.13.0" +bytes = "1" http = "0.2" +http-body = "0.4" lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" diff --git a/lambda-http/src/body.rs b/lambda-http/src/body.rs index c5dfdcf5..7ac31ce7 100644 --- a/lambda-http/src/body.rs +++ b/lambda-http/src/body.rs @@ -1,9 +1,11 @@ //! Provides an ALB / API Gateway oriented request and response body entity interface -use std::{borrow::Cow, ops::Deref}; - +use crate::Error; use base64::display::Base64Display; +use bytes::Bytes; +use http_body::{Body as HttpBody, SizeHint}; use serde::ser::{Error as SerError, Serialize, Serializer}; +use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// Representation of http request and response bodies as supported /// by API Gateway and ALBs. @@ -175,6 +177,45 @@ impl<'a> Serialize for Body { } } +impl HttpBody for Body { + type Data = Bytes; + type Error = Error; + + fn poll_data( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll>> { + let body = take(self.get_mut()); + Poll::Ready(match body { + Body::Empty => None, + Body::Text(s) => Some(Ok(s.into())), + Body::Binary(b) => Some(Ok(b.into())), + }) + } + + fn poll_trailers( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>> { + Poll::Ready(Ok(None)) + } + + fn is_end_stream(&self) -> bool { + match self { + Body::Empty => true, + _ => false, + } + } + + fn size_hint(&self) -> SizeHint { + match self { + Body::Empty => SizeHint::default(), + Body::Text(ref s) => SizeHint::with_exact(s.len() as u64), + Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), + } + } +} + #[cfg(test)] mod tests { use super::*; From 089fc03cac0a8ce586a93a817a53b196ebe5cc48 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Fri, 28 Jan 2022 06:01:19 +0100 Subject: [PATCH 073/394] docs(lambda-extension): update README (#408) --- lambda-extension/README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lambda-extension/README.md b/lambda-extension/README.md index 4982779f..79296608 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -9,18 +9,15 @@ The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch. ```rust,no_run -use lambda_extension::{extension_fn, Error, NextEvent}; -use log::LevelFilter; -use simple_logger::SimpleLogger; -use tracing::info; - -async fn log_extension(event: NextEvent) -> Result<(), Error> { - match event { - NextEvent::Shutdown(event) => { - info!("{}", event); +use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; + +async fn my_extension(event: LambdaEvent) -> Result<(), Error> { + match event.next { + NextEvent::Shutdown(_e) => { + // do something with the shutdown event } - NextEvent::Invoke(event) => { - info!("{}", event); + NextEvent::Invoke(_e) => { + // do something with the invoke event } } Ok(()) @@ -28,11 +25,16 @@ async fn log_extension(event: NextEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_ansi(false) + .without_time() + .init(); - let func = extension_fn(log_extension); + let func = service_fn(my_extension); lambda_extension::run(func).await } + ``` ## Deployment From 5f8eb343de42473de489f463bfadf43ac5e5cc60 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Thu, 10 Feb 2022 00:08:48 +0800 Subject: [PATCH 074/394] do not panic if AWS_LAMBDA_LOG_STREAM_NAME or AWS_LAMBDA_LOG_GROUP_NAME is missing (#411) --- lambda-runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c49cbfa3..c7aeb89a 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -55,8 +55,8 @@ impl Config { .parse::() .expect("AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var is not "), version: env::var("AWS_LAMBDA_FUNCTION_VERSION").expect("Missing AWS_LAMBDA_FUNCTION_VERSION env var"), - log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME").expect("Missing AWS_LAMBDA_LOG_STREAM_NAME env var"), - log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME").expect("Missing AWS_LAMBDA_LOG_GROUP_NAME env var"), + log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME").unwrap_or_default(), + log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME").unwrap_or_default(), }; Ok(conf) } From bc9367b6a90503cbb5cfbec605fb49a49679eeaa Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Wed, 16 Feb 2022 22:43:11 +0100 Subject: [PATCH 075/394] feat(lambda-extension): Logs API processor (#416) * Organize crate into modules. Signed-off-by: David Calavera * Initial logs processor implementation - Reorganize builder to support more options - Use no-op processors to play nice with generic types Signed-off-by: David Calavera * Fix value returned by the register function. Signed-off-by: David Calavera * Add logs subcription call Send request to the Lambda API to subscribe the extension to the logs API. Use the 2021-03-18 schema version to receive all new log types. Signed-off-by: David Calavera * Clean logs file. Signed-off-by: David Calavera * feat(lambda-extension): log processor server (DOES NOT WORK) * feat(lambda-extension): use MakeService for log processor (DOES NOT WORK) * feat(lambda-extension): restrict trait bounds for Identity/MakeIdentity * feat(lambda-extension): deserialize Vec * test: add integration tests for Logs API handler * chore: cargo fmt * feat(lambda-extension): add tracing and customizable port number for Logs API * feat(lambda-extension): implement LambdaLogRecord enum * fix(lambda-extension): fix bounds for with_logs_processor() * feat(lambda-extension): use chrono::DateTime for LambdaLog time * feat(lambda-extension): add combined example * docs(lambda-extension): add example for logs processor * feat(lambda-integration-tests): update log processor * fix(lambda-integration-tests): add tracing config to logs-trait * chore: cargo fmt Co-authored-by: David Calavera --- Makefile | 2 +- lambda-extension/Cargo.toml | 13 +- lambda-extension/README.md | 40 ++- lambda-extension/examples/combined.rs | 51 +++ lambda-extension/examples/custom_events.rs | 12 +- .../examples/custom_logs_service.rs | 64 ++++ ...it_implementation.rs => custom_service.rs} | 0 lambda-extension/examples/logs.rs | 23 ++ lambda-extension/src/error.rs | 23 ++ lambda-extension/src/events.rs | 71 +++++ lambda-extension/src/extension.rs | 300 ++++++++++++++++++ lambda-extension/src/lib.rs | 243 +------------- lambda-extension/src/logs.rs | 299 +++++++++++++++++ lambda-extension/src/requests.rs | 29 +- .../src/bin/logs-trait.rs | 76 +++++ lambda-integration-tests/template.yaml | 15 + 16 files changed, 1018 insertions(+), 243 deletions(-) create mode 100644 lambda-extension/examples/combined.rs create mode 100644 lambda-extension/examples/custom_logs_service.rs rename lambda-extension/examples/{custom_trait_implementation.rs => custom_service.rs} (100%) create mode 100644 lambda-extension/examples/logs.rs create mode 100644 lambda-extension/src/error.rs create mode 100644 lambda-extension/src/events.rs create mode 100644 lambda-extension/src/extension.rs create mode 100644 lambda-extension/src/logs.rs create mode 100644 lambda-integration-tests/src/bin/logs-trait.rs diff --git a/Makefile b/Makefile index 0890173a..d1eb2c99 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ INTEG_STACK_NAME ?= rust-lambda-integration-tests INTEG_FUNCTIONS_BUILD := runtime-fn runtime-trait http-fn http-trait INTEG_FUNCTIONS_INVOKE := RuntimeFn RuntimeFnAl2 RuntimeTrait RuntimeTraitAl2 Python PythonAl2 INTEG_API_INVOKE := RestApiUrl HttpApiUrl -INTEG_EXTENSIONS := extension-fn extension-trait +INTEG_EXTENSIONS := extension-fn extension-trait logs-trait # Using musl to run extensions on both AL1 and AL2 INTEG_ARCH := x86_64-unknown-linux-musl diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 7ad1b491..69fa7745 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -11,17 +11,18 @@ keywords = ["AWS", "Lambda", "API"] readme = "README.md" [dependencies] -tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +async-stream = "0.3" +bytes = "1.0" +chrono = { version = "0.4", features = ["serde"] } +http = "0.2" hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" -bytes = "1.0" -http = "0.2" -async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } -tower = { version = "0.4", features = ["util"] } +tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } +tower = { version = "0.4", features = ["make", "util"] } [dev-dependencies] simple-error = "0.2" diff --git a/lambda-extension/README.md b/lambda-extension/README.md index 79296608..95b08ccd 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -2,9 +2,11 @@ [![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension) -**`lambda-extension`** is a library that makes it easy to write [AWS Lambda Runtime Extensions](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html) in Rust. +**`lambda-extension`** is a library that makes it easy to write [AWS Lambda Runtime Extensions](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html) in Rust. It also helps with using [Lambda Logs API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html). -## Example extension +## Example extensions + +### Simple extension The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch. @@ -37,6 +39,40 @@ async fn main() -> Result<(), Error> { ``` +### Log processor extension + +```rust,no_run +use lambda_extension::{service_fn, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; +use tracing::info; + +async fn handler(logs: Vec) -> Result<(), Error> { + for log in logs { + match log.record { + LambdaLogRecord::Function(_record) => { + // do something with the function log record + }, + LambdaLogRecord::Extension(_record) => { + // do something with the extension log record + }, + }, + _ => (), + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let logs_processor = SharedService::new(service_fn(handler)); + + Extension::new().with_logs_processor(logs_processor).run().await?; + + Ok(()) +} + +``` + ## Deployment Lambda extensions can be added to your functions either using [Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#using-extensions-config), or adding them to [containers images](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#invocation-extensions-images). diff --git a/lambda-extension/examples/combined.rs b/lambda-extension/examples/combined.rs new file mode 100644 index 00000000..eede270f --- /dev/null +++ b/lambda-extension/examples/combined.rs @@ -0,0 +1,51 @@ +use lambda_extension::{ + service_fn, Error, Extension, LambdaEvent, LambdaLog, LambdaLogRecord, NextEvent, SharedService, +}; +use tracing::info; + +async fn my_extension(event: LambdaEvent) -> Result<(), Error> { + match event.next { + NextEvent::Shutdown(_e) => { + // do something with the shutdown event + } + NextEvent::Invoke(_e) => { + // do something with the invoke event + } + } + Ok(()) +} + +async fn my_log_processor(logs: Vec) -> Result<(), Error> { + for log in logs { + match log.record { + LambdaLogRecord::Function(record) => info!("[logs] [function] {}", record), + LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}", record), + _ => (), + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let func = service_fn(my_extension); + let logs_processor = SharedService::new(service_fn(my_log_processor)); + + Extension::new() + .with_events_processor(func) + .with_logs_processor(logs_processor) + .run() + .await +} diff --git a/lambda-extension/examples/custom_events.rs b/lambda-extension/examples/custom_events.rs index d2756c23..f796ca31 100644 --- a/lambda-extension/examples/custom_events.rs +++ b/lambda-extension/examples/custom_events.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent, Runtime}; +use lambda_extension::{service_fn, Error, Extension, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -27,9 +27,9 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - let func = service_fn(my_extension); - - let runtime = Runtime::builder().with_events(&["SHUTDOWN"]).register().await?; - - runtime.run(func).await + Extension::new() + .with_events(&["SHUTDOWN"]) + .with_events_processor(service_fn(my_extension)) + .run() + .await } diff --git a/lambda-extension/examples/custom_logs_service.rs b/lambda-extension/examples/custom_logs_service.rs new file mode 100644 index 00000000..aace5f6b --- /dev/null +++ b/lambda-extension/examples/custom_logs_service.rs @@ -0,0 +1,64 @@ +use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use std::{ + future::{ready, Future}, + pin::Pin, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, + task::Poll, +}; +use tracing::info; + +/// Custom log processor that increments a counter for each log record. +/// +/// This is a simple example of a custom log processor that can be used to +/// count the number of log records that are processed. +/// +/// This needs to derive Clone (and store the counter in an Arc) as the runtime +/// could need multiple `Service`s to process the logs. +#[derive(Clone, Default)] +struct MyLogsProcessor { + counter: Arc, +} + +impl MyLogsProcessor { + pub fn new() -> Self { + Self::default() + } +} + +/// Implementation of the actual log processor +/// +/// This receives a `Vec` whenever there are new log entries available. +impl Service> for MyLogsProcessor { + type Response = (); + type Error = Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, logs: Vec) -> Self::Future { + let counter = self.counter.fetch_add(1, SeqCst); + for log in logs { + match log.record { + LambdaLogRecord::Function(record) => info!("[logs] [function] {}: {}", counter, record), + LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}: {}", counter, record), + _ => (), + } + } + + Box::pin(ready(Ok(()))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let logs_processor = SharedService::new(MyLogsProcessor::new()); + + Extension::new().with_logs_processor(logs_processor).run().await?; + + Ok(()) +} diff --git a/lambda-extension/examples/custom_trait_implementation.rs b/lambda-extension/examples/custom_service.rs similarity index 100% rename from lambda-extension/examples/custom_trait_implementation.rs rename to lambda-extension/examples/custom_service.rs diff --git a/lambda-extension/examples/logs.rs b/lambda-extension/examples/logs.rs new file mode 100644 index 00000000..79973a46 --- /dev/null +++ b/lambda-extension/examples/logs.rs @@ -0,0 +1,23 @@ +use lambda_extension::{service_fn, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; +use tracing::info; + +async fn handler(logs: Vec) -> Result<(), Error> { + for log in logs { + match log.record { + LambdaLogRecord::Function(record) => info!("[logs] [function] {}", record), + LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}", record), + _ => (), + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let logs_processor = SharedService::new(service_fn(handler)); + + Extension::new().with_logs_processor(logs_processor).run().await?; + + Ok(()) +} diff --git a/lambda-extension/src/error.rs b/lambda-extension/src/error.rs new file mode 100644 index 00000000..2c3e23b3 --- /dev/null +++ b/lambda-extension/src/error.rs @@ -0,0 +1,23 @@ +/// Error type that extensions may result in +pub type Error = lambda_runtime_api_client::Error; + +/// Simple error that encapsulates human readable descriptions +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionError { + err: String, +} + +impl ExtensionError { + pub(crate) fn boxed>(str: T) -> Box { + Box::new(ExtensionError { err: str.into() }) + } +} + +impl std::fmt::Display for ExtensionError { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.err.fmt(f) + } +} + +impl std::error::Error for ExtensionError {} diff --git a/lambda-extension/src/events.rs b/lambda-extension/src/events.rs new file mode 100644 index 00000000..87fd62a4 --- /dev/null +++ b/lambda-extension/src/events.rs @@ -0,0 +1,71 @@ +use serde::Deserialize; + +/// Request tracing information +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Tracing { + /// The type of tracing exposed to the extension + pub r#type: String, + /// The span value + pub value: String, +} +/// Event received when there is a new Lambda invocation. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InvokeEvent { + /// The time that the function times out + pub deadline_ms: u64, + /// The ID assigned to the Lambda request + pub request_id: String, + /// The function's Amazon Resource Name + pub invoked_function_arn: String, + /// The request tracing information + pub tracing: Tracing, +} + +/// Event received when a Lambda function shuts down. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShutdownEvent { + /// The reason why the function terminates + /// It can be SPINDOWN, TIMEOUT, or FAILURE + pub shutdown_reason: String, + /// The time that the function times out + pub deadline_ms: u64, +} + +/// Event that the extension receives in +/// either the INVOKE or SHUTDOWN phase +#[derive(Debug, Deserialize)] +#[serde(rename_all = "UPPERCASE", tag = "eventType")] +pub enum NextEvent { + /// Payload when the event happens in the INVOKE phase + Invoke(InvokeEvent), + /// Payload when the event happens in the SHUTDOWN phase + Shutdown(ShutdownEvent), +} + +impl NextEvent { + /// Return whether the event is a [`NextEvent::Invoke`] event or not + pub fn is_invoke(&self) -> bool { + matches!(self, NextEvent::Invoke(_)) + } +} + +/// Wrapper with information about the next +/// event that the Lambda Runtime is going to process +pub struct LambdaEvent { + /// ID assigned to this extension by the Lambda Runtime + pub extension_id: String, + /// Next incoming event + pub next: NextEvent, +} + +impl LambdaEvent { + pub(crate) fn new(ex_id: &str, next: NextEvent) -> LambdaEvent { + LambdaEvent { + extension_id: ex_id.into(), + next, + } + } +} diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs new file mode 100644 index 00000000..71e5bcdf --- /dev/null +++ b/lambda-extension/src/extension.rs @@ -0,0 +1,300 @@ +use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent}; +use hyper::{server::conn::AddrStream, Server}; +use lambda_runtime_api_client::Client; +use std::{fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc}; +use tokio::sync::Mutex; +use tokio_stream::StreamExt; +use tower::{service_fn, MakeService, Service}; +use tracing::{error, trace}; + +const DEFAULT_LOG_PORT_NUMBER: u16 = 9002; + +/// An Extension that runs event and log processors +pub struct Extension<'a, E, L> { + extension_name: Option<&'a str>, + events: Option<&'a [&'a str]>, + events_processor: E, + log_types: Option<&'a [&'a str]>, + logs_processor: Option, + log_buffering: Option, + log_port_number: u16, +} + +impl<'a> Extension<'a, Identity, MakeIdentity>> { + /// Create a new base [`Extension`] with a no-op events processor + pub fn new() -> Self { + Extension { + extension_name: None, + events: None, + events_processor: Identity::new(), + log_types: None, + log_buffering: None, + logs_processor: None, + log_port_number: DEFAULT_LOG_PORT_NUMBER, + } + } +} + +impl<'a> Default for Extension<'a, Identity, MakeIdentity>> { + fn default() -> Self { + Self::new() + } +} + +impl<'a, E, L> Extension<'a, E, L> +where + E: Service, + E::Future: Future>, + E::Error: Into> + fmt::Display, + + // Fixme: 'static bound might be too restrictive + L: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, + L::Service: Service, Response = ()> + Send + Sync, + >>::Future: Send + 'a, + L::Error: Into>, + L::MakeError: Into>, + L::Future: Send, +{ + /// Create a new [`Extension`] with a given extension name + pub fn with_extension_name(self, extension_name: &'a str) -> Self { + Extension { + extension_name: Some(extension_name), + ..self + } + } + + /// Create a new [`Extension`] with a list of given events. + /// The only accepted events are `INVOKE` and `SHUTDOWN`. + pub fn with_events(self, events: &'a [&'a str]) -> Self { + Extension { + events: Some(events), + ..self + } + } + + /// Create a new [`Extension`] with a service that receives Lambda events. + pub fn with_events_processor(self, ep: N) -> Extension<'a, N, L> + where + N: Service, + N::Future: Future>, + N::Error: Into> + fmt::Display, + { + Extension { + events_processor: ep, + extension_name: self.extension_name, + events: self.events, + log_types: self.log_types, + log_buffering: self.log_buffering, + logs_processor: self.logs_processor, + log_port_number: self.log_port_number, + } + } + + /// Create a new [`Extension`] with a service that receives Lambda logs. + pub fn with_logs_processor(self, lp: N) -> Extension<'a, E, N> + where + N: Service<()>, + N::Future: Future>, + N::Error: Into> + fmt::Display, + { + Extension { + logs_processor: Some(lp), + events_processor: self.events_processor, + extension_name: self.extension_name, + events: self.events, + log_types: self.log_types, + log_buffering: self.log_buffering, + log_port_number: self.log_port_number, + } + } + + /// Create a new [`Extension`] with a list of logs types to subscribe. + /// The only accepted log types are `function`, `platform`, and `extension`. + pub fn with_log_types(self, log_types: &'a [&'a str]) -> Self { + Extension { + log_types: Some(log_types), + ..self + } + } + + /// Create a new [`Extension`] with specific configuration to buffer logs. + pub fn with_log_buffering(self, lb: LogBuffering) -> Self { + Extension { + log_buffering: Some(lb), + ..self + } + } + + /// Create a new [`Extension`] with a different port number to listen to logs. + pub fn with_log_port_number(self, port_number: u16) -> Self { + Extension { + log_port_number: port_number, + ..self + } + } + + /// Execute the given extension + pub async fn run(self) -> Result<(), Error> { + let client = &Client::builder().build()?; + + let extension_id = register(client, self.extension_name, self.events).await?; + let extension_id = extension_id.to_str()?; + let mut ep = self.events_processor; + + if let Some(mut log_processor) = self.logs_processor { + trace!("Log processor found"); + // Spawn task to run processor + let addr = SocketAddr::from(([0, 0, 0, 0], self.log_port_number)); + let make_service = service_fn(move |_socket: &AddrStream| { + trace!("Creating new log processor Service"); + let service = log_processor.make_service(()); + async move { + let service = Arc::new(Mutex::new(service.await?)); + Ok::<_, L::MakeError>(service_fn(move |req| log_wrapper(service.clone(), req))) + } + }); + let server = Server::bind(&addr).serve(make_service); + tokio::spawn(async move { + if let Err(e) = server.await { + error!("Error while running log processor: {}", e); + } + }); + trace!("Log processor started"); + + // Call Logs API to start receiving events + let req = requests::subscribe_logs_request( + extension_id, + self.log_types, + self.log_buffering, + self.log_port_number, + )?; + let res = client.call(req).await?; + if res.status() != http::StatusCode::OK { + return Err(ExtensionError::boxed("unable to initialize the logs api")); + } + trace!("Registered extension with Logs API"); + } + + let incoming = async_stream::stream! { + loop { + trace!("Waiting for next event (incoming loop)"); + let req = requests::next_event_request(extension_id)?; + let res = client.call(req).await; + yield res; + } + }; + + tokio::pin!(incoming); + while let Some(event) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = event?; + let (_parts, body) = event.into_parts(); + + let body = hyper::body::to_bytes(body).await?; + trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose + let event: NextEvent = serde_json::from_slice(&body)?; + let is_invoke = event.is_invoke(); + + let event = LambdaEvent::new(extension_id, event); + + let res = ep.call(event).await; + if let Err(error) = res { + let req = if is_invoke { + requests::init_error(extension_id, &error.to_string(), None)? + } else { + requests::exit_error(extension_id, &error.to_string(), None)? + }; + + client.call(req).await?; + return Err(error.into()); + } + } + Ok(()) + } +} + +/// A no-op generic processor +#[derive(Clone)] +pub struct Identity { + _phantom: std::marker::PhantomData, +} + +impl Identity { + fn new() -> Self { + Self { + _phantom: std::marker::PhantomData, + } + } +} + +impl Service for Identity { + type Error = Error; + type Future = Pin> + Send>>; + type Response = (); + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, _event: T) -> Self::Future { + Box::pin(ready(Ok(()))) + } +} + +/// Service factory to generate no-op generic processors +#[derive(Clone)] +pub struct MakeIdentity { + _phantom: std::marker::PhantomData, +} + +impl Service<()> for MakeIdentity +where + T: Send + Sync + 'static, +{ + type Error = Error; + type Response = Identity; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + core::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, _: ()) -> Self::Future { + Box::pin(ready(Ok(Identity::new()))) + } +} + +/// Initialize and register the extension in the Extensions API +async fn register<'a>( + client: &'a Client, + extension_name: Option<&'a str>, + events: Option<&'a [&'a str]>, +) -> Result { + let name = match extension_name { + Some(name) => name.into(), + None => { + let args: Vec = std::env::args().collect(); + PathBuf::from(args[0].clone()) + .file_name() + .expect("unexpected executable name") + .to_str() + .expect("unexpect executable name") + .to_string() + } + }; + + let events = events.unwrap_or(&["INVOKE", "SHUTDOWN"]); + + let req = requests::register_request(&name, events)?; + let res = client.call(req).await?; + if res.status() != http::StatusCode::OK { + return Err(ExtensionError::boxed("unable to register the extension")); + } + + let header = res + .headers() + .get(requests::EXTENSION_ID_HEADER) + .ok_or_else(|| ExtensionError::boxed("missing extension id header")) + .map_err(|e| ExtensionError::boxed(e.to_string()))?; + Ok(header.clone()) +} diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 130aae50..9bb44b50 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -1,244 +1,33 @@ #![deny(clippy::all, clippy::cargo)] -#![allow(clippy::multiple_crate_versions)] +#![allow(clippy::multiple_crate_versions, clippy::type_complexity)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] //! This module includes utilities to create Lambda Runtime Extensions. //! //! Create a type that conforms to the [`Extension`] trait. This type can then be passed //! to the the `lambda_extension::run` function, which launches and runs the Lambda runtime extension. -use hyper::client::{connect::Connection, HttpConnector}; -use lambda_runtime_api_client::Client; -use serde::Deserialize; -use std::{fmt, future::Future, path::PathBuf}; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio_stream::StreamExt; -pub use tower::{self, service_fn, Service}; -use tracing::trace; +use std::{fmt, future::Future}; +pub use tower::{self, make::Shared as SharedService, service_fn, Service}; + +mod error; +pub use error::*; +mod extension; +pub use extension::*; +mod events; +pub use events::*; +mod logs; +pub use logs::*; /// Include several request builders to interact with the Extension API. pub mod requests; -/// Error type that extensions may result in -pub type Error = lambda_runtime_api_client::Error; - -/// Simple error that encapsulates human readable descriptions -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ExtensionError { - err: String, -} - -impl ExtensionError { - fn boxed>(str: T) -> Box { - Box::new(ExtensionError { err: str.into() }) - } -} - -impl std::fmt::Display for ExtensionError { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.err.fmt(f) - } -} - -impl std::error::Error for ExtensionError {} - -/// Request tracing information -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Tracing { - /// The type of tracing exposed to the extension - pub r#type: String, - /// The span value - pub value: String, -} - -/// Event received when there is a new Lambda invocation. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct InvokeEvent { - /// The time that the function times out - pub deadline_ms: u64, - /// The ID assigned to the Lambda request - pub request_id: String, - /// The function's Amazon Resource Name - pub invoked_function_arn: String, - /// The request tracing information - pub tracing: Tracing, -} - -/// Event received when a Lambda function shuts down. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ShutdownEvent { - /// The reason why the function terminates - /// It can be SPINDOWN, TIMEOUT, or FAILURE - pub shutdown_reason: String, - /// The time that the function times out - pub deadline_ms: u64, -} - -/// Event that the extension receives in -/// either the INVOKE or SHUTDOWN phase -#[derive(Debug, Deserialize)] -#[serde(rename_all = "UPPERCASE", tag = "eventType")] -pub enum NextEvent { - /// Payload when the event happens in the INVOKE phase - Invoke(InvokeEvent), - /// Payload when the event happens in the SHUTDOWN phase - Shutdown(ShutdownEvent), -} - -impl NextEvent { - fn is_invoke(&self) -> bool { - matches!(self, NextEvent::Invoke(_)) - } -} - -/// Wrapper with information about the next -/// event that the Lambda Runtime is going to process -pub struct LambdaEvent { - /// ID assigned to this extension by the Lambda Runtime - pub extension_id: String, - /// Next incoming event - pub next: NextEvent, -} - -/// The Runtime handles all the incoming extension requests -pub struct Runtime = HttpConnector> { - extension_id: String, - client: Client, -} - -impl Runtime { - /// Create a [`RuntimeBuilder`] to initialize the [`Runtime`] - pub fn builder<'a>() -> RuntimeBuilder<'a> { - RuntimeBuilder::default() - } -} - -impl Runtime -where - C: Service + Clone + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send, - C::Error: Into>, - C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, -{ - /// Execute the given extension. - /// Register the extension with the Extensions API and wait for incoming events. - pub async fn run(&self, mut extension: E) -> Result<(), Error> - where - E: Service, - E::Future: Future>, - E::Error: Into> + fmt::Display, - { - let client = &self.client; - - let incoming = async_stream::stream! { - loop { - trace!("Waiting for next event (incoming loop)"); - let req = requests::next_event_request(&self.extension_id)?; - let res = client.call(req).await; - yield res; - } - }; - - tokio::pin!(incoming); - while let Some(event) = incoming.next().await { - trace!("New event arrived (run loop)"); - let event = event?; - let (_parts, body) = event.into_parts(); - - let body = hyper::body::to_bytes(body).await?; - trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose - let event: NextEvent = serde_json::from_slice(&body)?; - let is_invoke = event.is_invoke(); - - let event = LambdaEvent { - extension_id: self.extension_id.clone(), - next: event, - }; - - let res = extension.call(event).await; - if let Err(error) = res { - let req = if is_invoke { - requests::init_error(&self.extension_id, &error.to_string(), None)? - } else { - requests::exit_error(&self.extension_id, &error.to_string(), None)? - }; - - self.client.call(req).await?; - return Err(error.into()); - } - } - - Ok(()) - } -} - -/// Builder to construct a new extension [`Runtime`] -#[derive(Default)] -pub struct RuntimeBuilder<'a> { - extension_name: Option<&'a str>, - events: Option<&'a [&'a str]>, -} - -impl<'a> RuntimeBuilder<'a> { - /// Create a new [`RuntimeBuilder`] with a given extension name - pub fn with_extension_name(self, extension_name: &'a str) -> Self { - RuntimeBuilder { - extension_name: Some(extension_name), - ..self - } - } - - /// Create a new [`RuntimeBuilder`] with a list of given events. - /// The only accepted events are `INVOKE` and `SHUTDOWN`. - pub fn with_events(self, events: &'a [&'a str]) -> Self { - RuntimeBuilder { - events: Some(events), - ..self - } - } - - /// Initialize and register the extension in the Extensions API - pub async fn register(&self) -> Result { - let name = match self.extension_name { - Some(name) => name.into(), - None => { - let args: Vec = std::env::args().collect(); - PathBuf::from(args[0].clone()) - .file_name() - .expect("unexpected executable name") - .to_str() - .expect("unexpect executable name") - .to_string() - } - }; - - let events = self.events.unwrap_or(&["INVOKE", "SHUTDOWN"]); - - let client = Client::builder().build()?; - - let req = requests::register_request(&name, events)?; - let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { - return Err(ExtensionError::boxed("unable to register the extension")); - } - - let extension_id = res.headers().get(requests::EXTENSION_ID_HEADER).unwrap().to_str()?; - Ok(Runtime { - extension_id: extension_id.into(), - client, - }) - } -} - -/// Execute the given extension -pub async fn run(extension: E) -> Result<(), Error> +/// Execute the given events processor +pub async fn run(events_processor: E) -> Result<(), Error> where E: Service, E::Future: Future>, E::Error: Into> + fmt::Display, { - Runtime::builder().register().await?.run(extension).await + let ext = Extension::new().with_events_processor(events_processor); + ext.run().await } diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs new file mode 100644 index 00000000..66f47ee1 --- /dev/null +++ b/lambda-extension/src/logs.rs @@ -0,0 +1,299 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::{boxed::Box, sync::Arc}; +use tokio::sync::Mutex; +use tower::Service; +use tracing::{error, trace}; + +/// Payload received from the Lambda Logs API +/// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-msg +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct LambdaLog { + /// Time when the log was generated + pub time: DateTime, + /// Log record entry + #[serde(flatten)] + pub record: LambdaLogRecord, +} + +/// Record in a LambdaLog entry +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(tag = "type", content = "record", rename_all = "lowercase")] +pub enum LambdaLogRecord { + /// Function log records + Function(String), + + /// Extension log records + Extension(String), + + /// Platform start record + #[serde(rename = "platform.start", rename_all = "camelCase")] + PlatformStart { + /// Request identifier + request_id: String, + }, + /// Platform stop record + #[serde(rename = "platform.end", rename_all = "camelCase")] + PlatformEnd { + /// Request identifier + request_id: String, + }, + /// Platform report record + #[serde(rename = "platform.report", rename_all = "camelCase")] + PlatformReport { + /// Request identifier + request_id: String, + /// Request metrics + metrics: LogPlatformReportMetrics, + }, + /// Runtime or execution environment error record + #[serde(rename = "platform.fault")] + PlatformFault(String), + /// Extension-specific record + #[serde(rename = "platform.extension", rename_all = "camelCase")] + PlatformExtension { + /// Name of the extension + name: String, + /// State of the extension + state: String, + /// Events sent to the extension + events: Vec, + }, + /// Log processor-specific record + #[serde(rename = "platform.logsSubscription", rename_all = "camelCase")] + PlatformLogsSubscription { + /// Name of the extension + name: String, + /// State of the extensions + state: String, + /// Types of records sent to the extension + types: Vec, + }, + /// Record generated when the log processor is falling behind + #[serde(rename = "platform.logsDropped", rename_all = "camelCase")] + PlatformLogsDropped { + /// Reason for dropping the logs + reason: String, + /// Number of records dropped + dropped_records: u64, + /// Total size of the dropped records + dropped_bytes: u64, + }, + /// Record marking the completion of an invocation + #[serde(rename = "platform.runtimeDone", rename_all = "camelCase")] + PlatformRuntimeDone { + /// Request identifier + request_id: String, + /// Status of the invocation + status: String, + }, +} + +/// Platform report metrics +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct LogPlatformReportMetrics { + /// Duration in milliseconds + pub duration_ms: f64, + /// Billed duration in milliseconds + pub billed_duration_ms: u64, + /// Memory allocated in megabytes + #[serde(rename = "memorySizeMB")] + pub memory_size_mb: u64, + /// Maximum memory used for the invoke in megabytes + #[serde(rename = "maxMemoryUsedMB")] + pub max_memory_used_mb: u64, + /// Init duration in case of a cold start + #[serde(default = "Option::default")] + pub init_duration_ms: Option, +} + +/// Log buffering configuration. +/// Allows Lambda to buffer logs before deliverying them to a subscriber. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LogBuffering { + /// The maximum time (in milliseconds) to buffer a batch. + /// Default: 1,000. Minimum: 25. Maximum: 30,000 + pub timeout_ms: usize, + /// The maximum size (in bytes) of the logs to buffer in memory. + /// Default: 262,144. Minimum: 262,144. Maximum: 1,048,576 + pub max_bytes: usize, + /// The maximum number of events to buffer in memory. + /// Default: 10,000. Minimum: 1,000. Maximum: 10,000 + pub max_items: usize, +} + +impl Default for LogBuffering { + fn default() -> Self { + LogBuffering { + timeout_ms: 1_000, + max_bytes: 262_144, + max_items: 10_000, + } + } +} + +/// Wrapper function that sends logs to the subscriber Service +/// +/// This takes an `hyper::Request` and transforms it into `Vec` for the +/// underlying `Service` to process. +pub(crate) async fn log_wrapper( + service: Arc>, + req: hyper::Request, +) -> Result, Box> +where + S: Service, Response = ()>, + S::Error: Into>, + S::Future: Send, +{ + trace!("Received logs request"); + // Parse the request body as a Vec + let body = match hyper::body::to_bytes(req.into_body()).await { + Ok(body) => body, + Err(e) => { + error!("Error reading logs request body: {}", e); + return Ok(hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .body(hyper::Body::empty()) + .unwrap()); + } + }; + let logs: Vec = match serde_json::from_slice(&body) { + Ok(logs) => logs, + Err(e) => { + error!("Error parsing logs: {}", e); + return Ok(hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .body(hyper::Body::empty()) + .unwrap()); + } + }; + + { + let mut service = service.lock().await; + let _ = service.call(logs).await; + } + + Ok(hyper::Response::new(hyper::Body::empty())) +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::TimeZone; + + #[test] + fn deserialize_full() { + let data = r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#; + let expected = LambdaLog { + time: Utc.ymd(2020, 8, 20).and_hms_milli(12, 31, 32, 123), + record: LambdaLogRecord::Function("hello world".to_string()), + }; + + let actual = serde_json::from_str::(data).unwrap(); + + assert_eq!(expected, actual); + } + + macro_rules! deserialize_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let actual = serde_json::from_str::(&input).expect("unable to deserialize"); + + assert!(actual.record == expected); + } + )* + } + } + + deserialize_tests! { + // function + function: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#, + LambdaLogRecord::Function("hello world".to_string()), + ), + + // extension + extension: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "extension", "record": "hello world"}"#, + LambdaLogRecord::Extension("hello world".to_string()), + ), + + // platform.start + platform_start: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.start","record": {"requestId": "6f7f0961f83442118a7af6fe80b88d56"}}"#, + LambdaLogRecord::PlatformStart { + request_id: "6f7f0961f83442118a7af6fe80b88d56".to_string(), + }, + ), + // platform.end + platform_end: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.end","record": {"requestId": "6f7f0961f83442118a7af6fe80b88d56"}}"#, + LambdaLogRecord::PlatformEnd { + request_id: "6f7f0961f83442118a7af6fe80b88d56".to_string(), + }, + ), + // platform.report + platform_report: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.report","record": {"requestId": "6f7f0961f83442118a7af6fe80b88d56","metrics": {"durationMs": 1.23,"billedDurationMs": 123,"memorySizeMB": 123,"maxMemoryUsedMB": 123,"initDurationMs": 1.23}}}"#, + LambdaLogRecord::PlatformReport { + request_id: "6f7f0961f83442118a7af6fe80b88d56".to_string(), + metrics: LogPlatformReportMetrics { + duration_ms: 1.23, + billed_duration_ms: 123, + memory_size_mb: 123, + max_memory_used_mb: 123, + init_duration_ms: Some(1.23), + }, + }, + ), + // platform.fault + platform_fault: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.fault","record": "RequestId: d783b35e-a91d-4251-af17-035953428a2c Process exited before completing request"}"#, + LambdaLogRecord::PlatformFault( + "RequestId: d783b35e-a91d-4251-af17-035953428a2c Process exited before completing request" + .to_string(), + ), + ), + // platform.extension + platform_extension: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.extension","record": {"name": "Foo.bar","state": "Ready","events": ["INVOKE", "SHUTDOWN"]}}"#, + LambdaLogRecord::PlatformExtension { + name: "Foo.bar".to_string(), + state: "Ready".to_string(), + events: vec!["INVOKE".to_string(), "SHUTDOWN".to_string()], + }, + ), + // platform.logsSubscription + platform_logssubscription: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.logsSubscription","record": {"name": "test","state": "active","types": ["test"]}}"#, + LambdaLogRecord::PlatformLogsSubscription { + name: "test".to_string(), + state: "active".to_string(), + types: vec!["test".to_string()], + }, + ), + // platform.logsDropped + platform_logsdropped: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "platform.logsDropped","record": {"reason": "Consumer seems to have fallen behind as it has not acknowledged receipt of logs.","droppedRecords": 123,"droppedBytes": 12345}}"#, + LambdaLogRecord::PlatformLogsDropped { + reason: "Consumer seems to have fallen behind as it has not acknowledged receipt of logs." + .to_string(), + dropped_records: 123, + dropped_bytes: 12345, + }, + ), + // platform.runtimeDone + platform_runtimedone: ( + r#"{"time": "2021-02-04T20:00:05.123Z","type": "platform.runtimeDone","record": {"requestId":"6f7f0961f83442118a7af6fe80b88d56","status": "success"}}"#, + LambdaLogRecord::PlatformRuntimeDone { + request_id: "6f7f0961f83442118a7af6fe80b88d56".to_string(), + status: "success".to_string(), + }, + ), + } +} diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 2fdbf2a6..6cff70b6 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::{Error, LogBuffering}; use http::{Method, Request}; use hyper::Body; use lambda_runtime_api_client::build_request; @@ -29,6 +29,33 @@ pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result< Ok(req) } +pub(crate) fn subscribe_logs_request( + extension_id: &str, + types: Option<&[&str]>, + buffering: Option, + port_number: u16, +) -> Result, Error> { + let types = types.unwrap_or(&["platform", "function"]); + + let data = serde_json::json!({ + "schemaVersion": "2021-03-18", + "types": types, + "buffering": buffering.unwrap_or_default(), + "destination": { + "protocol": "HTTP", + "URI": format!("http://sandbox.localdomain:{}", port_number), + } + }); + + let req = build_request() + .method(Method::PUT) + .uri("/2020-08-15/logs") + .header(EXTENSION_ID_HEADER, extension_id) + .body(Body::from(serde_json::to_string(&data)?))?; + + Ok(req) +} + /// Payload to send error information to the Extensions API. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs new file mode 100644 index 00000000..871a5019 --- /dev/null +++ b/lambda-integration-tests/src/bin/logs-trait.rs @@ -0,0 +1,76 @@ +use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use std::{ + future::{ready, Future}, + pin::Pin, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, + task::Poll, +}; +use tracing::info; + +/// Custom log processor that increments a counter for each log record. +/// +/// This is a simple example of a custom log processor that can be used to +/// count the number of log records that are processed. +/// +/// This needs to derive Clone (and store the counter in an Arc) as the runtime +/// could need multiple `Service`s to process the logs. +#[derive(Clone, Default)] +struct MyLogsProcessor { + counter: Arc, +} + +impl MyLogsProcessor { + pub fn new() -> Self { + Self::default() + } +} + +/// Implementation of the actual log processor +/// +/// This receives a `Vec` whenever there are new log entries available. +impl Service> for MyLogsProcessor { + type Response = (); + type Error = Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, logs: Vec) -> Self::Future { + let counter = self.counter.fetch_add(1, SeqCst); + for log in logs { + match log.record { + LambdaLogRecord::Function(record) => { + info!("[logs] {} [function] {}: {}", log.time, counter, record.trim()) + } + LambdaLogRecord::Extension(record) => { + info!("[logs] {} [extension] {}: {}", log.time, counter, record.trim()) + } + _ => (), + } + } + + Box::pin(ready(Ok(()))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let logs_processor = SharedService::new(MyLogsProcessor::new()); + Extension::new().with_logs_processor(logs_processor).run().await?; + + Ok(()) +} diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index 848f8be5..d3716d7c 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -15,6 +15,7 @@ Resources: CodeUri: ../build/runtime-fn/ Runtime: provided.al2 Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -25,6 +26,7 @@ Resources: CodeUri: ../build/runtime-fn/ Runtime: provided Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -35,6 +37,7 @@ Resources: CodeUri: ../build/runtime-trait/ Runtime: provided.al2 Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -45,6 +48,7 @@ Resources: CodeUri: ../build/runtime-trait/ Runtime: provided Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -76,6 +80,7 @@ Resources: Method: POST Path: /al2/post Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -107,6 +112,7 @@ Resources: Method: POST Path: /al2-trait/post Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -138,6 +144,7 @@ Resources: Method: POST Path: /post Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -169,6 +176,7 @@ Resources: Method: POST Path: /trait/post Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -180,6 +188,7 @@ Resources: Handler: main.handler Runtime: python3.9 Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait @@ -191,9 +200,15 @@ Resources: Handler: main.handler Runtime: python3.7 Layers: + - !Ref LogsTrait - !Ref ExtensionFn - !Ref ExtensionTrait + LogsTrait: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ../build/logs-trait/ + ExtensionFn: Type: AWS::Serverless::LayerVersion Properties: From 854ca9c5b2925802fcb99d1b29559d22681f7779 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 16 Feb 2022 23:36:56 -0800 Subject: [PATCH 076/394] Make errors implement Debug+Display (#418) std::error::Error has the minimal requirement of describing errors with both of these traits, I think we should enforce it too. This will allow us to send the full callstack to CloudWatch when there is an error. Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c7aeb89a..a5be8fd1 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -91,7 +91,7 @@ where where F: Service>, F::Future: Future>, - F::Error: fmt::Display, + F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, B: Serialize, { @@ -125,7 +125,7 @@ where .into_req() } Err(err) => { - error!("{}", err); // logs the error in CloudWatch + error!("{:?}", err); // logs the error in CloudWatch EventErrorRequest { request_id, diagnostic: Diagnostic { @@ -137,7 +137,7 @@ where } }, Err(err) => { - error!("{:?}", err); // inconsistent with other log record formats - to be reviewed + error!("{:?}", err); EventErrorRequest { request_id, diagnostic: Diagnostic { @@ -199,7 +199,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service>, F::Future: Future>, - F::Error: fmt::Display, + F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, B: Serialize, { From 93af9cb3a7bcf0a556fc897f50d1efaad5c7a4e8 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Thu, 17 Feb 2022 16:53:05 +0100 Subject: [PATCH 077/394] feat(lambda-extension): make errors implement Debug+Display (#419) --- lambda-extension/src/extension.rs | 17 ++++++++++------- lambda-extension/src/lib.rs | 2 +- lambda-extension/src/logs.rs | 9 ++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 71e5bcdf..81462c24 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,7 +1,9 @@ use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent}; use hyper::{server::conn::AddrStream, Server}; use lambda_runtime_api_client::Client; -use std::{fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc}; +use std::{ + convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, +}; use tokio::sync::Mutex; use tokio_stream::StreamExt; use tower::{service_fn, MakeService, Service}; @@ -45,14 +47,14 @@ impl<'a, E, L> Extension<'a, E, L> where E: Service, E::Future: Future>, - E::Error: Into> + fmt::Display, + E::Error: Into> + fmt::Display + fmt::Debug, // Fixme: 'static bound might be too restrictive L: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, L::Service: Service, Response = ()> + Send + Sync, >>::Future: Send + 'a, - L::Error: Into>, - L::MakeError: Into>, + L::Error: Into> + fmt::Debug, + L::MakeError: Into> + fmt::Debug, L::Future: Send, { /// Create a new [`Extension`] with a given extension name @@ -199,6 +201,7 @@ where let res = ep.call(event).await; if let Err(error) = res { + println!("{:?}", error); let req = if is_invoke { requests::init_error(extension_id, &error.to_string(), None)? } else { @@ -228,8 +231,8 @@ impl Identity { } impl Service for Identity { - type Error = Error; - type Future = Pin> + Send>>; + type Error = Infallible; + type Future = Pin> + Send>>; type Response = (); fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { @@ -251,7 +254,7 @@ impl Service<()> for MakeIdentity where T: Send + Sync + 'static, { - type Error = Error; + type Error = Infallible; type Response = Identity; type Future = Pin> + Send>>; diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 9bb44b50..e3e72eba 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -26,7 +26,7 @@ pub async fn run(events_processor: E) -> Result<(), Error> where E: Service, E::Future: Future>, - E::Error: Into> + fmt::Display, + E::Error: Into> + fmt::Display + fmt::Debug, { let ext = Extension::new().with_events_processor(events_processor); ext.run().await diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 66f47ee1..5fd32e4f 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::{boxed::Box, sync::Arc}; +use std::{boxed::Box, fmt, sync::Arc}; use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; @@ -144,7 +144,7 @@ pub(crate) async fn log_wrapper( ) -> Result, Box> where S: Service, Response = ()>, - S::Error: Into>, + S::Error: Into> + fmt::Debug, S::Future: Send, { trace!("Received logs request"); @@ -172,7 +172,10 @@ where { let mut service = service.lock().await; - let _ = service.call(logs).await; + match service.call(logs).await { + Ok(_) => (), + Err(err) => println!("{:?}", err), + } } Ok(hyper::Response::new(hyper::Body::empty())) From d9cf7e873c11ce7070afbcd45fc57ffb19513cb6 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Thu, 17 Feb 2022 17:23:24 +0100 Subject: [PATCH 078/394] chore: bump version to 0.5 (#420) --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 4 ++-- lambda-integration-tests/Cargo.toml | 9 +++++---- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 69fa7745..cafa4ea0 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_extension" -version = "0.1.0" +version = "0.5.0" edition = "2018" authors = ["David Calavera "] description = "AWS Lambda Extension API" @@ -16,7 +16,7 @@ bytes = "1.0" chrono = { version = "0.4", features = ["serde"] } http = "0.2" hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } -lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 3bbe9d26..ac1b45b9 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.4.1" +version = "0.5.0" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -21,7 +21,7 @@ base64 = "0.13.0" bytes = "1" http = "0.2" http-body = "0.4" -lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } +lambda_runtime = { path = "../lambda-runtime", version = "0.5" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index eafea4d2..f70b1c16 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "lambda_integration_tests" -version = "0.4.1" +version = "0.5.0" +authors = ["Nicolas Moutschen "] edition = "2018" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" @@ -12,9 +13,9 @@ readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lambda_http = { path = "../lambda-http", version = "0.4.1" } -lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" } -lambda_extension = { path = "../lambda-extension", version = "0.1.0" } +lambda_http = { path = "../lambda-http", version = "0.5" } +lambda_runtime = { path = "../lambda-runtime", version = "0.5" } +lambda_extension = { path = "../lambda-extension", version = "0.5" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 0e005802..2b8291b8 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.4.1" +version = "0.5.0" edition = "2018" authors = ["David Calavera "] description = "AWS Lambda Runtime interaction API" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 4bdb1c84..b9b7c490 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.4.1" +version = "0.5.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" @@ -25,7 +25,7 @@ async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } [dev-dependencies] tracing-subscriber = "0.3" From e1797cc09a0d1339d40ab9b49108599595049897 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sat, 19 Feb 2022 18:22:31 +0100 Subject: [PATCH 079/394] feat(lambda-http): add example with tower_http crate (#425) * feat(lambda-runtime): add all features for tower re-export * feat(lambda-http): add CORS layer example * revert(lambda-http): revert changes to LambdaRequest and Body types --- lambda-http/Cargo.toml | 1 + lambda-http/examples/hello-cors.rs | 29 +++++++++++++++++++ .../src/bin/logs-trait.rs | 1 + lambda-runtime/Cargo.toml | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 lambda-http/examples/hello-cors.rs diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ac1b45b9..cb1f7a97 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -30,3 +30,4 @@ serde_urlencoded = "0.7.0" log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } +tower-http = { version = "0.2", features = ["cors"] } \ No newline at end of file diff --git a/lambda-http/examples/hello-cors.rs b/lambda-http/examples/hello-cors.rs new file mode 100644 index 00000000..79d2a986 --- /dev/null +++ b/lambda-http/examples/hello-cors.rs @@ -0,0 +1,29 @@ +use http::Method; +use lambda_http::{service_fn, tower::ServiceBuilder, Body, Error, IntoResponse, Request, RequestExt, Response}; +use tower_http::cors::{Any, CorsLayer}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + // Define a layer to inject CORS headers + let cors_layer = CorsLayer::new() + .allow_methods(vec![Method::GET, Method::POST]) + .allow_origin(Any); + + let handler = ServiceBuilder::new() + // Add the CORS layer to the service + .layer(cors_layer) + .service(service_fn(func)); + + lambda_http::run(handler).await?; + Ok(()) +} + +async fn func(event: Request) -> Result, Error> { + Ok(match event.query_string_parameters().get("first_name") { + Some(first_name) => format!("Hello, {}!", first_name).into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }) +} diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs index 871a5019..a9bbe7d5 100644 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ b/lambda-integration-tests/src/bin/logs-trait.rs @@ -34,6 +34,7 @@ impl MyLogsProcessor { impl Service> for MyLogsProcessor { type Response = (); type Error = Error; + #[allow(clippy::type_complexity)] type Future = Pin> + Send>>; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index b9b7c490..cd12e046 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -23,7 +23,7 @@ bytes = "1.0" http = "0.2" async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } -tower = { version = "0.4", features = ["util"] } +tower = { version = "0.4", features = ["full"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } From 3d5565df8590b0024d04aa5dbecd081e0d335972 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 19 Feb 2022 10:19:29 -0800 Subject: [PATCH 080/394] Replace custom http events with aws_lambda_events (#426) * Replace custom http events with aws_lambda_events The Alb and ApiGw events in aws_lambda_events are thoroughly tested with multiple payloads that those services generate. Signed-off-by: David Calavera * Add support for WebSocket requests Signed-off-by: David Calavera * Don't add the default stage to the request URI ApiGw use $default as an identifier, but it doesn't add it to the URI. Signed-off-by: David Calavera * Fix formatting Signed-off-by: David Calavera * Fix examples Signed-off-by: David Calavera * Replace custom response structs with aws_lambda_events Signed-off-by: David Calavera * Fix example Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 + lambda-http/examples/hello-cors.rs | 2 +- lambda-http/examples/hello-http.rs | 2 +- .../examples/shared-resources-example.rs | 2 +- lambda-http/src/body.rs | 295 ------ lambda-http/src/ext.rs | 39 +- lambda-http/src/lib.rs | 11 +- lambda-http/src/request.rs | 870 ++++++------------ lambda-http/src/response.rs | 226 +---- lambda-http/src/strmap.rs | 221 ----- .../data/apigw_multi_value_proxy_request.json | 3 - .../src/bin/logs-trait.rs | 5 +- 12 files changed, 357 insertions(+), 1321 deletions(-) delete mode 100644 lambda-http/src/body.rs delete mode 100644 lambda-http/src/strmap.rs diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index cb1f7a97..8b1d9285 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -17,6 +17,7 @@ travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" } maintenance = { status = "actively-developed" } [dependencies] +aws_lambda_events = { version = "0.6", default-features = false, features = ["alb", "apigw"]} base64 = "0.13.0" bytes = "1" http = "0.2" @@ -25,6 +26,7 @@ lambda_runtime = { path = "../lambda-runtime", version = "0.5" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" +query_map = { version = "0.4", features = ["url-query"] } [dev-dependencies] log = "^0.4" diff --git a/lambda-http/examples/hello-cors.rs b/lambda-http/examples/hello-cors.rs index 79d2a986..275873a4 100644 --- a/lambda-http/examples/hello-cors.rs +++ b/lambda-http/examples/hello-cors.rs @@ -19,7 +19,7 @@ async fn main() -> Result<(), Error> { } async fn func(event: Request) -> Result, Error> { - Ok(match event.query_string_parameters().get("first_name") { + Ok(match event.query_string_parameters().first("first_name") { Some(first_name) => format!("Hello, {}!", first_name).into_response(), _ => Response::builder() .status(400) diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs index 40352dab..5b679196 100644 --- a/lambda-http/examples/hello-http.rs +++ b/lambda-http/examples/hello-http.rs @@ -7,7 +7,7 @@ async fn main() -> Result<(), Error> { } async fn func(event: Request) -> Result { - Ok(match event.query_string_parameters().get("first_name") { + Ok(match event.query_string_parameters().first("first_name") { Some(first_name) => format!("Hello, {}!", first_name).into_response(), _ => Response::builder() .status(400) diff --git a/lambda-http/examples/shared-resources-example.rs b/lambda-http/examples/shared-resources-example.rs index 24e56f97..a90dd815 100644 --- a/lambda-http/examples/shared-resources-example.rs +++ b/lambda-http/examples/shared-resources-example.rs @@ -20,7 +20,7 @@ async fn main() -> Result<(), Error> { // Define a closure here that makes use of the shared client. let handler_func_closure = move |event: Request| async move { - Ok(match event.query_string_parameters().get("first_name") { + Ok(match event.query_string_parameters().first("first_name") { Some(first_name) => shared_client_ref .response(event.lambda_context().request_id, first_name) .into_response(), diff --git a/lambda-http/src/body.rs b/lambda-http/src/body.rs deleted file mode 100644 index 7ac31ce7..00000000 --- a/lambda-http/src/body.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! Provides an ALB / API Gateway oriented request and response body entity interface - -use crate::Error; -use base64::display::Base64Display; -use bytes::Bytes; -use http_body::{Body as HttpBody, SizeHint}; -use serde::ser::{Error as SerError, Serialize, Serializer}; -use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; - -/// Representation of http request and response bodies as supported -/// by API Gateway and ALBs. -/// -/// These come in three flavors -/// * `Empty` ( no body ) -/// * `Text` ( text data ) -/// * `Binary` ( binary data ) -/// -/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the [hyper crate](https://crates.io/crates/hyper) -/// -/// # Examples -/// -/// Body types are inferred with `From` implementations. -/// -/// ## Text -/// -/// Types like `String`, `str` whose type reflects -/// text produce `Body::Text` variants -/// -/// ``` -/// assert!(match lambda_http::Body::from("text") { -/// lambda_http::Body::Text(_) => true, -/// _ => false -/// }) -/// ``` -/// -/// ## Binary -/// -/// Types like `Vec` and `&[u8]` whose types reflect raw bytes produce `Body::Binary` variants -/// -/// ``` -/// assert!(match lambda_http::Body::from("text".as_bytes()) { -/// lambda_http::Body::Binary(_) => true, -/// _ => false -/// }) -/// ``` -/// -/// `Binary` responses bodies will automatically get based64 encoded to meet API Gateway's response expectations. -/// -/// ## Empty -/// -/// The unit type ( `()` ) whose type represents an empty value produces `Body::Empty` variants -/// -/// ``` -/// assert!(match lambda_http::Body::from(()) { -/// lambda_http::Body::Empty => true, -/// _ => false -/// }) -/// ``` -/// -/// -/// For more information about API Gateway's body types, -/// refer to [this documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html). -#[derive(Debug, PartialEq)] -pub enum Body { - /// An empty body - Empty, - /// A body containing string data - Text(String), - /// A body containing binary data - Binary(Vec), -} - -impl Body { - /// Decodes body, if needed. - /// - /// # Panics - /// - /// Panics when aws communicates to handler that request is base64 encoded but - /// it can not be base64 decoded - pub(crate) fn from_maybe_encoded(is_base64_encoded: bool, body: Cow<'_, str>) -> Body { - if is_base64_encoded { - Body::from(::base64::decode(body.as_ref()).expect("failed to decode aws base64 encoded body")) - } else { - Body::from(body.as_ref()) - } - } -} - -impl Default for Body { - fn default() -> Self { - Body::Empty - } -} - -impl From<()> for Body { - fn from(_: ()) -> Self { - Body::Empty - } -} - -impl<'a> From<&'a str> for Body { - fn from(s: &'a str) -> Self { - Body::Text(s.into()) - } -} - -impl From for Body { - fn from(b: String) -> Self { - Body::Text(b) - } -} - -impl From> for Body { - #[inline] - fn from(cow: Cow<'static, str>) -> Body { - match cow { - Cow::Borrowed(b) => Body::from(b.to_owned()), - Cow::Owned(o) => Body::from(o), - } - } -} - -impl From> for Body { - #[inline] - fn from(cow: Cow<'static, [u8]>) -> Body { - match cow { - Cow::Borrowed(b) => Body::from(b), - Cow::Owned(o) => Body::from(o), - } - } -} - -impl From> for Body { - fn from(b: Vec) -> Self { - Body::Binary(b) - } -} - -impl<'a> From<&'a [u8]> for Body { - fn from(b: &'a [u8]) -> Self { - Body::Binary(b.to_vec()) - } -} - -impl Deref for Body { - type Target = [u8]; - - #[inline] - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl AsRef<[u8]> for Body { - #[inline] - fn as_ref(&self) -> &[u8] { - match self { - Body::Empty => &[], - Body::Text(ref bytes) => bytes.as_ref(), - Body::Binary(ref bytes) => bytes.as_ref(), - } - } -} - -impl<'a> Serialize for Body { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Body::Text(data) => { - serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?) - } - Body::Binary(data) => serializer.collect_str(&Base64Display::with_config(data, base64::STANDARD)), - Body::Empty => serializer.serialize_unit(), - } - } -} - -impl HttpBody for Body { - type Data = Bytes; - type Error = Error; - - fn poll_data( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll>> { - let body = take(self.get_mut()); - Poll::Ready(match body { - Body::Empty => None, - Body::Text(s) => Some(Ok(s.into())), - Body::Binary(b) => Some(Ok(b.into())), - }) - } - - fn poll_trailers( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll, Self::Error>> { - Poll::Ready(Ok(None)) - } - - fn is_end_stream(&self) -> bool { - match self { - Body::Empty => true, - _ => false, - } - } - - fn size_hint(&self) -> SizeHint { - match self { - Body::Empty => SizeHint::default(), - Body::Text(ref s) => SizeHint::with_exact(s.len() as u64), - Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json; - use std::collections::HashMap; - - #[test] - fn body_has_default() { - assert_eq!(Body::default(), Body::Empty); - } - - #[test] - fn from_unit() { - assert_eq!(Body::from(()), Body::Empty); - } - - #[test] - fn from_str() { - match Body::from(String::from("foo").as_str()) { - Body::Text(_) => (), - not => assert!(false, "expected Body::Text(...) got {:?}", not), - } - } - - #[test] - fn from_string() { - match Body::from(String::from("foo")) { - Body::Text(_) => (), - not => assert!(false, "expected Body::Text(...) got {:?}", not), - } - } - - #[test] - fn from_cow_str() { - match Body::from(Cow::from("foo")) { - Body::Text(_) => (), - not => assert!(false, "expected Body::Text(...) got {:?}", not), - } - } - - #[test] - fn from_cow_bytes() { - match Body::from(Cow::from("foo".as_bytes())) { - Body::Binary(_) => (), - not => assert!(false, "expected Body::Binary(...) got {:?}", not), - } - } - - #[test] - fn from_bytes() { - match Body::from("foo".as_bytes()) { - Body::Binary(_) => (), - not => assert!(false, "expected Body::Binary(...) got {:?}", not), - } - } - - #[test] - fn serialize_text() { - let mut map = HashMap::new(); - map.insert("foo", Body::from("bar")); - assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"bar"}"#); - } - - #[test] - fn serialize_binary() { - let mut map = HashMap::new(); - map.insert("foo", Body::from("bar".as_bytes())); - assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"YmFy"}"#); - } - - #[test] - fn serialize_empty() { - let mut map = HashMap::new(); - map.insert("foo", Body::Empty); - assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":null}"#); - } -} diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 2f56d78c..e33e639c 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -1,23 +1,24 @@ //! Extension methods for `http::Request` types -use crate::{request::RequestContext, strmap::StrMap, Body}; +use crate::{request::RequestContext, Body}; use lambda_runtime::Context; +use query_map::QueryMap; use serde::{de::value::Error as SerdeError, Deserialize}; use std::{error::Error, fmt}; /// ALB/API gateway pre-parsed http query string parameters -pub(crate) struct QueryStringParameters(pub(crate) StrMap); +pub(crate) struct QueryStringParameters(pub(crate) QueryMap); /// API gateway pre-extracted url path parameters /// /// These will always be empty for ALB requests -pub(crate) struct PathParameters(pub(crate) StrMap); +pub(crate) struct PathParameters(pub(crate) QueryMap); /// API gateway configured /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// /// These will always be empty for ALB requests -pub(crate) struct StageVariables(pub(crate) StrMap); +pub(crate) struct StageVariables(pub(crate) QueryMap); /// Request payload deserialization errors /// @@ -112,37 +113,37 @@ pub trait RequestExt { /// name are expected, `query_string_parameters().get_all("many")` to retrieve them all. /// /// No query parameters - /// will yield an empty `StrMap`. - fn query_string_parameters(&self) -> StrMap; + /// will yield an empty `QueryMap`. + fn query_string_parameters(&self) -> QueryMap; /// Configures instance with query string parameters under #[cfg(test)] configurations /// /// This is intended for use in mock testing contexts. fn with_query_string_parameters(self, parameters: Q) -> Self where - Q: Into; + Q: Into; /// Return pre-extracted path parameters, parameter provided in url placeholders /// `/foo/{bar}/baz/{boom}`, /// associated with the API gateway request. No path parameters - /// will yield an empty `StrMap` + /// will yield an empty `QueryMap` /// /// These will always be empty for ALB triggered requests - fn path_parameters(&self) -> StrMap; + fn path_parameters(&self) -> QueryMap; /// Configures instance with path parameters under #[cfg(test)] configurations /// /// This is intended for use in mock testing contexts. fn with_path_parameters

(self, parameters: P) -> Self where - P: Into; + P: Into; /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// associated with the API gateway request. No stage parameters - /// will yield an empty `StrMap` + /// will yield an empty `QueryMap` /// /// These will always be empty for ALB triggered requests - fn stage_variables(&self) -> StrMap; + fn stage_variables(&self) -> QueryMap; /// Configures instance with stage variables under #[cfg(test)] configurations /// @@ -150,7 +151,7 @@ pub trait RequestExt { #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where - V: Into; + V: Into; /// Return request context data assocaited with the ALB or API gateway request fn request_context(&self) -> RequestContext; @@ -176,7 +177,7 @@ pub trait RequestExt { } impl RequestExt for http::Request { - fn query_string_parameters(&self) -> StrMap { + fn query_string_parameters(&self) -> QueryMap { self.extensions() .get::() .map(|ext| ext.0.clone()) @@ -185,14 +186,14 @@ impl RequestExt for http::Request { fn with_query_string_parameters(self, parameters: Q) -> Self where - Q: Into, + Q: Into, { let mut s = self; s.extensions_mut().insert(QueryStringParameters(parameters.into())); s } - fn path_parameters(&self) -> StrMap { + fn path_parameters(&self) -> QueryMap { self.extensions() .get::() .map(|ext| ext.0.clone()) @@ -201,14 +202,14 @@ impl RequestExt for http::Request { fn with_path_parameters

(self, parameters: P) -> Self where - P: Into, + P: Into, { let mut s = self; s.extensions_mut().insert(PathParameters(parameters.into())); s } - fn stage_variables(&self) -> StrMap { + fn stage_variables(&self) -> QueryMap { self.extensions() .get::() .map(|ext| ext.0.clone()) @@ -218,7 +219,7 @@ impl RequestExt for http::Request { #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where - V: Into, + V: Into, { let mut s = self; s.extensions_mut().insert(StageVariables(variables.into())); diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index e1119dd0..95b26f25 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -51,7 +51,7 @@ //! "hello {}", //! request //! .query_string_parameters() -//! .get("name") +//! .first("name") //! .unwrap_or_else(|| "stranger") //! )) //! } @@ -66,16 +66,15 @@ pub use http::{self, Response}; use lambda_runtime::LambdaEvent; pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; -mod body; pub mod ext; pub mod request; mod response; -mod strmap; -pub use crate::{body::Body, ext::RequestExt, response::IntoResponse, strmap::StrMap}; +pub use crate::{ext::RequestExt, response::IntoResponse}; use crate::{ request::{LambdaRequest, RequestOrigin}, response::LambdaResponse, }; +pub use aws_lambda_events::encodings::Body; use std::{ future::Future, marker::PhantomData, @@ -134,7 +133,7 @@ where } } -impl<'a, R, S> Service>> for Adapter<'a, R, S> +impl<'a, R, S> Service> for Adapter<'a, R, S> where S: Service + Send, S::Future: Send + 'a, @@ -148,7 +147,7 @@ where core::task::Poll::Ready(Ok(())) } - fn call(&mut self, req: LambdaEvent>) -> Self::Future { + fn call(&mut self, req: LambdaEvent) -> 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))); diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index fd509412..63c649c6 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,17 +3,18 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use crate::{ - body::Body, - ext::{PathParameters, QueryStringParameters, StageVariables}, - strmap::StrMap, +use crate::ext::{PathParameters, QueryStringParameters, StageVariables}; +use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; +use aws_lambda_events::apigw::{ + ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext, + ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext, }; -use serde::{ - de::{Deserializer, Error as DeError, MapAccess, Visitor}, - Deserialize, -}; -use serde_json::{error::Error as JsonError, Value}; -use std::{borrow::Cow, collections::HashMap, fmt, io::Read, mem}; +use aws_lambda_events::encodings::Body; +use http::header::HeaderName; +use query_map::QueryMap; +use serde::Deserialize; +use serde_json::error::Error as JsonError; +use std::{io::Read, mem}; /// Internal representation of an Lambda http event from /// ALB, API Gateway REST and HTTP API proxy event perspectives @@ -23,84 +24,23 @@ use std::{borrow::Cow, collections::HashMap, fmt, io::Read, mem}; #[doc(hidden)] #[derive(Deserialize, Debug)] #[serde(untagged)] -pub enum LambdaRequest<'a> { - #[serde(rename_all = "camelCase")] - ApiGatewayV2 { - version: Cow<'a, str>, - route_key: Cow<'a, str>, - raw_path: Cow<'a, str>, - raw_query_string: Cow<'a, str>, - cookies: Option>>, - #[serde(deserialize_with = "deserialize_headers")] - headers: http::HeaderMap, - #[serde(default, deserialize_with = "nullable_default")] - query_string_parameters: StrMap, - #[serde(default, deserialize_with = "nullable_default")] - path_parameters: StrMap, - #[serde(default, deserialize_with = "nullable_default")] - stage_variables: StrMap, - body: Option>, - #[serde(default)] - is_base64_encoded: bool, - request_context: ApiGatewayV2RequestContext, - }, - #[serde(rename_all = "camelCase")] - Alb { - path: Cow<'a, str>, - #[serde(deserialize_with = "deserialize_method")] - http_method: http::Method, - #[serde(deserialize_with = "deserialize_headers")] - headers: http::HeaderMap, - /// For alb events these are only present when - /// the `lambda.multi_value_headers.enabled` target group setting turned on - #[serde(default, deserialize_with = "deserialize_multi_value_headers")] - multi_value_headers: http::HeaderMap, - #[serde(default, deserialize_with = "nullable_default")] - query_string_parameters: StrMap, - /// For alb events these are only present when - /// the `lambda.multi_value_headers.enabled` target group setting turned on - #[serde(default, deserialize_with = "nullable_default")] - multi_value_query_string_parameters: StrMap, - body: Option>, - #[serde(default)] - is_base64_encoded: bool, - request_context: AlbRequestContext, - }, - #[serde(rename_all = "camelCase")] - ApiGateway { - path: Cow<'a, str>, - #[serde(deserialize_with = "deserialize_method")] - http_method: http::Method, - #[serde(deserialize_with = "deserialize_headers")] - headers: http::HeaderMap, - #[serde(default, deserialize_with = "deserialize_multi_value_headers")] - multi_value_headers: http::HeaderMap, - #[serde(default, deserialize_with = "nullable_default")] - query_string_parameters: StrMap, - #[serde(default, deserialize_with = "nullable_default")] - multi_value_query_string_parameters: StrMap, - #[serde(default, deserialize_with = "nullable_default")] - path_parameters: StrMap, - #[serde(default, deserialize_with = "nullable_default")] - stage_variables: StrMap, - body: Option>, - #[serde(default)] - is_base64_encoded: bool, - request_context: ApiGatewayRequestContext, - #[serde(default, deserialize_with = "nullable_default")] - resource: Option, - }, +pub enum LambdaRequest { + ApiGatewayV1(ApiGatewayProxyRequest), + ApiGatewayV2(ApiGatewayV2httpRequest), + Alb(AlbTargetGroupRequest), + WebSocket(ApiGatewayWebsocketProxyRequest), } -impl LambdaRequest<'_> { +impl LambdaRequest { /// Return the `RequestOrigin` of the request to determine where the `LambdaRequest` /// originated from, so that the appropriate response can be selected based on what /// type of response the request origin expects. pub fn request_origin(&self) -> RequestOrigin { match self { + LambdaRequest::ApiGatewayV1 { .. } => RequestOrigin::ApiGatewayV1, LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2, LambdaRequest::Alb { .. } => RequestOrigin::Alb, - LambdaRequest::ApiGateway { .. } => RequestOrigin::ApiGateway, + LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, } } } @@ -109,488 +49,287 @@ impl LambdaRequest<'_> { #[doc(hidden)] #[derive(Debug)] pub enum RequestOrigin { + /// API Gateway request origin + ApiGatewayV1, /// API Gateway v2 request origin ApiGatewayV2, - /// API Gateway request origin - ApiGateway, /// ALB request origin Alb, + /// API Gateway WebSocket + WebSocket, } -/// See [context-variable-reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for more detail. -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2RequestContext { - /// The API owner's AWS account ID. - pub account_id: String, - /// The identifier API Gateway assigns to your API. - pub api_id: String, - /// The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. - #[serde(default)] - pub authorizer: HashMap, - /// The full domain name used to invoke the API. This should be the same as the incoming Host header. - pub domain_name: String, - /// The first label of the $context.domainName. This is often used as a caller/customer identifier. - pub domain_prefix: String, - /// The HTTP method used. - pub http: Http, - /// The ID that API Gateway assigns to the API request. - pub request_id: String, - /// Undocumented, could be resourcePath - pub route_key: String, - /// The deployment stage of the API request (for example, Beta or Prod). - pub stage: String, - /// Undocumented, could be requestTime - pub time: String, - /// Undocumented, could be requestTimeEpoch - pub time_epoch: usize, -} +fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request { + let http_method = ag.request_context.http.method.clone(); + let builder = http::Request::builder() + .uri({ + let scheme = ag + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + let host = ag + .headers + .get(http::header::HOST) + .and_then(|s| s.to_str().ok()) + .or_else(|| ag.request_context.domain_name.as_deref()) + .unwrap_or_default(); + + let path = apigw_path_with_stage(&ag.request_context.stage, ag.raw_path.as_deref().unwrap_or_default()); + let mut url = format!("{}://{}{}", scheme, host, path); + + if let Some(query) = ag.raw_query_string { + url.push('?'); + url.push_str(&query); + } + url + }) + .extension(QueryStringParameters(ag.query_string_parameters)) + .extension(PathParameters(QueryMap::from(ag.path_parameters))) + .extension(StageVariables(QueryMap::from(ag.stage_variables))) + .extension(RequestContext::ApiGatewayV2(ag.request_context)); + + let mut headers = ag.headers; + if let Some(cookies) = ag.cookies { + if let Ok(header_value) = http::header::HeaderValue::from_str(&cookies.join(";")) { + headers.append(http::header::COOKIE, header_value); + } + } -/// See [context-variable-reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for more detail. -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayRequestContext { - /// The API owner's AWS account ID. - pub account_id: String, - /// The identifier that API Gateway assigns to your resource. - pub resource_id: String, - /// The deployment stage of the API request (for example, Beta or Prod). - pub stage: String, - /// The full domain name used to invoke the API. This should be the same as the incoming Host header. - pub domain_name: Option, - /// The first label of the $context.domainName. This is often used as a caller/customer identifier. - pub domain_prefix: Option, - /// The ID that API Gateway assigns to the API request. - pub request_id: String, - /// The path to your resource. For example, for the non-proxy request URI of `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, The $context.resourcePath value is /root/child. - pub resource_path: String, - /// The request protocol, for example, HTTP/1.1. - pub protocol: Option, - /// The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm). - pub request_time: Option, - /// The Epoch-formatted request time, in milliseconds. - pub request_time_epoch: i64, - /// The identifier API Gateway assigns to your API. - pub apiid: Option, - /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. - pub http_method: String, - /// The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. - #[serde(default)] - pub authorizer: HashMap, - /// The identifier API Gateway assigns to your API. - pub api_id: String, - /// Cofnito identity information - #[serde(default)] - pub identity: Identity, -} + let base64 = ag.is_base64_encoded; -/// Elastic load balancer context information -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct AlbRequestContext { - /// Elastic load balancer context information - pub elb: Elb, -} + let mut req = builder + .body( + ag.body + .as_deref() + .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)), + ) + .expect("failed to build request"); -/// Event request context as an enumeration of request contexts -/// for both ALB and API Gateway and HTTP API events -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum RequestContext { - /// API Gateway v2 request context - ApiGatewayV2(ApiGatewayV2RequestContext), - /// API Gateway request context - ApiGateway(ApiGatewayRequestContext), - /// ALB request context - Alb(AlbRequestContext), -} + // no builder method that sets headers in batch + let _ = mem::replace(req.headers_mut(), headers); + let _ = mem::replace(req.method_mut(), http_method); -/// Elastic load balancer context information -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Elb { - /// AWS ARN identifier for the ELB Target Group this lambda was triggered by - pub target_group_arn: String, + req } -/// Http information captured API Gateway v2 request context -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Http { - #[serde(deserialize_with = "deserialize_method")] - /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. - pub method: http::Method, - /// The request path. For example, for a non-proxy request URL of - /// `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, - /// the $context.path value is `/{stage}/root/child`. - pub path: String, - /// The request protocol, for example, HTTP/1.1. - pub protocol: String, - /// The source IP address of the TCP connection making the request to API Gateway. - pub source_ip: String, - /// The User-Agent header of the API caller. - pub user_agent: String, -} +fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { + let http_method = ag.http_method; + let builder = http::Request::builder() + .uri({ + let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); + let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + + let mut url = match host { + None => path, + Some(host) => { + let scheme = ag + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + format!("{}://{}{}", scheme, host, path) + } + }; + + if !ag.multi_value_query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&ag.multi_value_query_string_parameters.to_query_string()); + } else if !ag.query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&ag.query_string_parameters.to_query_string()); + } + url + }) + // multi-valued query string parameters are always a super + // set of singly valued query string parameters, + // when present, multi-valued query string parameters are preferred + .extension(QueryStringParameters( + if ag.multi_value_query_string_parameters.is_empty() { + ag.query_string_parameters + } else { + ag.multi_value_query_string_parameters + }, + )) + .extension(PathParameters(QueryMap::from(ag.path_parameters))) + .extension(StageVariables(QueryMap::from(ag.stage_variables))) + .extension(RequestContext::ApiGatewayV1(ag.request_context)); + + // merge headers into multi_value_headers and make + // multi-value_headers our cannoncial source of request headers + let mut headers = ag.multi_value_headers; + headers.extend(ag.headers); + + let base64 = ag.is_base64_encoded.unwrap_or_default(); + let mut req = builder + .body( + ag.body + .as_deref() + .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)), + ) + .expect("failed to build request"); -/// Identity assoicated with API Gateway request -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Identity { - /// The source IP address of the TCP connection making the request to API Gateway. - pub source_ip: String, - /// The Amazon Cognito identity ID of the caller making the request. - /// Available only if the request was signed with Amazon Cognito credentials. - pub cognito_identity_id: Option, - /// The Amazon Cognito identity pool ID of the caller making the request. - /// Available only if the request was signed with Amazon Cognito credentials. - pub cognito_identity_pool_id: Option, - /// A comma-separated list of the Amazon Cognito authentication providers used by the caller making the request. - /// Available only if the request was signed with Amazon Cognito credentials. - pub cognito_authentication_provider: Option, - /// The Amazon Cognito authentication type of the caller making the request. - /// Available only if the request was signed with Amazon Cognito credentials. - pub cognito_authentication_type: Option, - /// The AWS account ID associated with the request. - pub account_id: Option, - /// The principal identifier of the caller making the request. - pub caller: Option, - /// For API methods that require an API key, this variable is the API key associated with the method request. - /// For methods that don't require an API key, this variable is null. - pub api_key: Option, - /// Undocumented. Can be the API key ID associated with an API request that requires an API key. - /// The description of `api_key` and `access_key` may actually be reversed. - pub access_key: Option, - /// The principal identifier of the user making the request. Used in Lambda authorizers. - pub user: Option, - /// The User-Agent header of the API caller. - pub user_agent: Option, - /// The Amazon Resource Name (ARN) of the effective user identified after authentication. - pub user_arn: Option, -} + // no builder method that sets headers in batch + let _ = mem::replace(req.headers_mut(), headers); + let _ = mem::replace(req.method_mut(), http_method); -/// Deserialize a str into an http::Method -fn deserialize_method<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct MethodVisitor; + req +} - impl<'de> Visitor<'de> for MethodVisitor { - type Value = http::Method; +fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { + let http_method = alb.http_method; + let builder = http::Request::builder() + .uri({ + let scheme = alb + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + let host = alb + .headers + .get(http::header::HOST) + .and_then(|s| s.to_str().ok()) + .unwrap_or_default(); + + let mut url = format!("{}://{}{}", scheme, host, alb.path.unwrap_or_default()); + if !alb.multi_value_query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&alb.multi_value_query_string_parameters.to_query_string()); + } else if !alb.query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&alb.query_string_parameters.to_query_string()); + } - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a Method") - } + url + }) + // multi valued query string parameters are always a super + // set of singly valued query string parameters, + // when present, multi-valued query string parameters are preferred + .extension(QueryStringParameters( + if alb.multi_value_query_string_parameters.is_empty() { + alb.query_string_parameters + } else { + alb.multi_value_query_string_parameters + }, + )) + .extension(RequestContext::Alb(alb.request_context)); - fn visit_str(self, v: &str) -> Result - where - E: DeError, - { - v.parse().map_err(E::custom) - } - } + // merge headers into multi_value_headers and make + // multi-value_headers our cannoncial source of request headers + let mut headers = alb.multi_value_headers; + headers.extend(alb.headers); - deserializer.deserialize_str(MethodVisitor) -} + let base64 = alb.is_base64_encoded; -/// Deserialize a map of Cow<'_, str> => Vec> into an http::HeaderMap -fn deserialize_multi_value_headers<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct HeaderVisitor; + let mut req = builder + .body( + alb.body + .as_deref() + .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)), + ) + .expect("failed to build request"); - impl<'de> Visitor<'de> for HeaderVisitor { - type Value = http::HeaderMap; + // no builder method that sets headers in batch + let _ = mem::replace(req.headers_mut(), headers); + let _ = mem::replace(req.method_mut(), http_method); - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a multi valued HeaderMap") - } + req +} - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut headers = map - .size_hint() - .map(http::HeaderMap::with_capacity) - .unwrap_or_else(http::HeaderMap::new); - while let Some((key, values)) = map.next_entry::, Vec>>()? { - // note the aws docs for multi value headers include an empty key. I'm not sure if this is a doc bug - // or not by the http crate doesn't handle it - // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - if !key.is_empty() { - for value in values { - let header_name = key.parse::().map_err(A::Error::custom)?; - let header_value = http::header::HeaderValue::from_maybe_shared(value.into_owned()) - .map_err(A::Error::custom)?; - headers.append(header_name, header_value); - } +fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request { + let http_method = ag.http_method; + let builder = http::Request::builder() + .uri({ + let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); + let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + + let mut url = match host { + None => path, + Some(host) => { + let scheme = ag + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + format!("{}://{}{}", scheme, host, path) } + }; + + if !ag.multi_value_query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&ag.multi_value_query_string_parameters.to_query_string()); + } else if !ag.query_string_parameters.is_empty() { + url.push('?'); + url.push_str(&ag.query_string_parameters.to_query_string()); } - Ok(headers) - } - } - - Ok(deserializer.deserialize_map(HeaderVisitor).unwrap_or_default()) -} - -/// Deserialize a map of Cow<'_, str> => Cow<'_, str> into an http::HeaderMap -fn deserialize_headers<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct HeaderVisitor; + url + }) + // multi-valued query string parameters are always a super + // set of singly valued query string parameters, + // when present, multi-valued query string parameters are preferred + .extension(QueryStringParameters( + if ag.multi_value_query_string_parameters.is_empty() { + ag.query_string_parameters + } else { + ag.multi_value_query_string_parameters + }, + )) + .extension(PathParameters(QueryMap::from(ag.path_parameters))) + .extension(StageVariables(QueryMap::from(ag.stage_variables))) + .extension(RequestContext::WebSocket(ag.request_context)); + + // merge headers into multi_value_headers and make + // multi-value_headers our cannoncial source of request headers + let mut headers = ag.multi_value_headers; + headers.extend(ag.headers); + + let base64 = ag.is_base64_encoded.unwrap_or_default(); + let mut req = builder + .body( + ag.body + .as_deref() + .map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)), + ) + .expect("failed to build request"); - impl<'de> Visitor<'de> for HeaderVisitor { - type Value = http::HeaderMap; + // no builder method that sets headers in batch + let _ = mem::replace(req.headers_mut(), headers); + let _ = mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET)); - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a HeaderMap") - } + req +} - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut headers = map - .size_hint() - .map(http::HeaderMap::with_capacity) - .unwrap_or_else(http::HeaderMap::new); - while let Some((key, value)) = map.next_entry::, Cow<'_, str>>()? { - let header_name = key.parse::().map_err(A::Error::custom)?; - let header_value = - http::header::HeaderValue::from_maybe_shared(value.into_owned()).map_err(A::Error::custom)?; - headers.append(header_name, header_value); - } - Ok(headers) - } +fn apigw_path_with_stage(stage: &Option, path: &str) -> String { + match stage { + None => path.into(), + Some(stage) if stage == "$default" => path.into(), + Some(stage) => format!("/{}{}", stage, path), } - - Ok(deserializer.deserialize_map(HeaderVisitor).unwrap_or_default()) } -/// deserializes (json) null values to their default values -// https://github.com/serde-rs/serde/issues/1098 -fn nullable_default<'de, T, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, - T: Default + Deserialize<'de>, -{ - let opt = Option::deserialize(deserializer)?; - Ok(opt.unwrap_or_default()) +/// Event request context as an enumeration of request contexts +/// for both ALB and API Gateway and HTTP API events +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum RequestContext { + /// API Gateway proxy request context + ApiGatewayV1(ApiGatewayProxyRequestContext), + /// API Gateway v2 request context + ApiGatewayV2(ApiGatewayV2httpRequestContext), + /// ALB request context + Alb(AlbTargetGroupRequestContext), + /// WebSocket request context + WebSocket(ApiGatewayWebsocketProxyRequestContext), } /// Converts LambdaRequest types into `http::Request` types -impl<'a> From> for http::Request { - fn from(value: LambdaRequest<'_>) -> Self { +impl<'a> From for http::Request { + fn from(value: LambdaRequest) -> Self { match value { - LambdaRequest::ApiGatewayV2 { - raw_path, - raw_query_string, - mut headers, - query_string_parameters, - path_parameters, - stage_variables, - body, - is_base64_encoded, - request_context, - cookies, - .. - } => { - if let Some(cookies) = cookies { - if let Ok(header_value) = http::header::HeaderValue::from_str(&cookies.join(";")) { - headers.append(http::header::COOKIE, header_value); - } - } - - let builder = http::Request::builder() - .method(request_context.http.method.as_ref()) - .uri({ - let mut url = format!( - "{}://{}{}", - headers - .get("X-Forwarded-Proto") - .and_then(|val| val.to_str().ok()) - .unwrap_or("https"), - headers - .get(http::header::HOST) - .and_then(|val| val.to_str().ok()) - .unwrap_or_else(|| request_context.domain_name.as_ref()), - raw_path - ); - if !raw_query_string.is_empty() { - url.push('?'); - url.push_str(raw_query_string.as_ref()); - } - url - }) - .extension(QueryStringParameters(query_string_parameters)) - .extension(PathParameters(path_parameters)) - .extension(StageVariables(stage_variables)) - .extension(RequestContext::ApiGatewayV2(request_context)); - - let mut req = builder - .body(body.map_or_else(Body::default, |b| Body::from_maybe_encoded(is_base64_encoded, b))) - .expect("failed to build request"); - - // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - - req - } - LambdaRequest::ApiGateway { - path, - http_method, - headers, - mut multi_value_headers, - query_string_parameters, - multi_value_query_string_parameters, - path_parameters, - stage_variables, - body, - is_base64_encoded, - request_context, - resource: _, - } => { - let builder = http::Request::builder() - .method(http_method) - .uri({ - let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); - let mut uri = match host { - Some(host) => { - format!( - "{}://{}{}", - headers - .get("X-Forwarded-Proto") - .and_then(|val| val.to_str().ok()) - .unwrap_or("https"), - host, - path - ) - } - None => path.to_string(), - }; - - if !multi_value_query_string_parameters.is_empty() { - uri.push('?'); - uri.push_str(multi_value_query_string_parameters.to_query_string().as_str()); - } else if !query_string_parameters.is_empty() { - uri.push('?'); - uri.push_str(query_string_parameters.to_query_string().as_str()); - } - - uri - }) - // multi-valued query string parameters are always a super - // set of singly valued query string parameters, - // when present, multi-valued query string parameters are preferred - .extension(QueryStringParameters( - if multi_value_query_string_parameters.is_empty() { - query_string_parameters - } else { - multi_value_query_string_parameters - }, - )) - .extension(PathParameters(path_parameters)) - .extension(StageVariables(stage_variables)) - .extension(RequestContext::ApiGateway(request_context)); - - let mut req = builder - .body(body.map_or_else(Body::default, |b| Body::from_maybe_encoded(is_base64_encoded, b))) - .expect("failed to build request"); - - // merge headers into multi_value_headers and make - // multi-value_headers our cannoncial source of request headers - for (key, value) in headers { - // see HeaderMap#into_iter() docs for cases when key element may be None - if let Some(first_key) = key { - // if it contains the key, avoid appending a duplicate value - if !multi_value_headers.contains_key(&first_key) { - multi_value_headers.append(first_key, value); - } - } - } - - // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), multi_value_headers); - - req - } - LambdaRequest::Alb { - path, - http_method, - headers, - mut multi_value_headers, - query_string_parameters, - multi_value_query_string_parameters, - body, - is_base64_encoded, - request_context, - } => { - // build an http::Request from a lambda_http::LambdaRequest - let builder = http::Request::builder() - .method(http_method) - .uri({ - let host = headers.get(http::header::HOST).and_then(|val| val.to_str().ok()); - let mut uri = match host { - Some(host) => { - format!( - "{}://{}{}", - headers - .get("X-Forwarded-Proto") - .and_then(|val| val.to_str().ok()) - .unwrap_or("https"), - host, - path - ) - } - None => path.to_string(), - }; - - if !multi_value_query_string_parameters.is_empty() { - uri.push('?'); - uri.push_str(multi_value_query_string_parameters.to_query_string().as_str()); - } else if !query_string_parameters.is_empty() { - uri.push('?'); - uri.push_str(query_string_parameters.to_query_string().as_str()); - } - - uri - }) - // multi valued query string parameters are always a super - // set of singly valued query string parameters, - // when present, multi-valued query string parameters are preferred - .extension(QueryStringParameters( - if multi_value_query_string_parameters.is_empty() { - query_string_parameters - } else { - multi_value_query_string_parameters - }, - )) - .extension(RequestContext::Alb(request_context)); - - let mut req = builder - .body(body.map_or_else(Body::default, |b| Body::from_maybe_encoded(is_base64_encoded, b))) - .expect("failed to build request"); - - // merge headers into multi_value_headers and make - // multi-value_headers our cannoncial source of request headers - for (key, value) in headers { - // see HeaderMap#into_iter() docs for cases when key element may be None - if let Some(first_key) = key { - // if it contains the key, avoid appending a duplicate value - if !multi_value_headers.contains_key(&first_key) { - multi_value_headers.append(first_key, value); - } - } - } - - // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), multi_value_headers); - - req - } + LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag), + LambdaRequest::ApiGatewayV1(ag) => into_proxy_request(ag), + LambdaRequest::Alb(alb) => into_alb_request(alb), + LambdaRequest::WebSocket(ag) => into_websocket_request(ag), } } } @@ -638,12 +377,15 @@ pub fn from_str(s: &str) -> Result { serde_json::from_str(s).map(LambdaRequest::into) } +fn x_forwarded_proto() -> HeaderName { + HeaderName::from_static("x-forwarded-proto") +} + #[cfg(test)] mod tests { use super::*; use crate::RequestExt; - use serde_json; - use std::{collections::HashMap, fs::File}; + use std::fs::File; #[test] fn deserializes_apigw_request_events_from_readables() { @@ -734,14 +476,14 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!( req.uri(), - "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me" + "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/test/hello?name=me" ); // Ensure this is an APIGW request let req_context = req.request_context(); assert!( match req_context { - RequestContext::ApiGateway(_) => true, + RequestContext::ApiGatewayV1(_) => true, _ => false, }, "expected ApiGateway context, got {:?}", @@ -798,7 +540,7 @@ mod tests { // test RequestExt#query_string_parameters does the right thing assert_eq!( - request.query_string_parameters().get_all("multivalueName"), + request.query_string_parameters().all("multivalueName"), Some(vec!["you", "me"]) ); } @@ -820,7 +562,7 @@ mod tests { // test RequestExt#query_string_parameters does the right thing assert_eq!( - request.query_string_parameters().get_all("myKey"), + request.query_string_parameters().all("myKey"), Some(vec!["val1", "val2"]) ); } @@ -861,66 +603,6 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "/test/hello?name=me"); - } - - #[test] - fn deserialize_with_null() { - #[derive(Debug, PartialEq, Deserialize)] - struct Test { - #[serde(deserialize_with = "nullable_default")] - foo: HashMap, - } - - assert_eq!( - serde_json::from_str::(r#"{"foo":null}"#).expect("failed to deserialize"), - Test { foo: HashMap::new() } - ) - } - - #[test] - fn deserialize_with_missing() { - #[derive(Debug, PartialEq, Deserialize)] - struct Test { - #[serde(default, deserialize_with = "nullable_default")] - foo: HashMap, - } - - assert_eq!( - serde_json::from_str::(r#"{}"#).expect("failed to deserialize"), - Test { foo: HashMap::new() } - ) - } - - #[test] - fn deserialize_null_headers() { - #[derive(Debug, PartialEq, Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_headers")] - headers: http::HeaderMap, - } - - assert_eq!( - serde_json::from_str::(r#"{"headers":null}"#).expect("failed to deserialize"), - Test { - headers: http::HeaderMap::new() - } - ) - } - - #[test] - fn deserialize_null_multi_value_headers() { - #[derive(Debug, PartialEq, Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_multi_value_headers")] - multi_value_headers: http::HeaderMap, - } - - assert_eq!( - serde_json::from_str::(r#"{"multi_value_headers":null}"#).expect("failed to deserialize"), - Test { - multi_value_headers: http::HeaderMap::new() - } - ) + assert_eq!(req.uri(), "/test/test/hello?name=me"); } } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 8c3e2a1d..4ea9c895 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -1,108 +1,23 @@ //! Response types -use crate::{body::Body, request::RequestOrigin}; +use crate::request::RequestOrigin; +use aws_lambda_events::encodings::Body; +use aws_lambda_events::event::alb::AlbTargetGroupResponse; +use aws_lambda_events::event::apigw::{ApiGatewayProxyResponse, ApiGatewayV2httpResponse}; use http::{ - header::{HeaderMap, HeaderValue, CONTENT_TYPE, SET_COOKIE}, + header::{CONTENT_TYPE, SET_COOKIE}, Response, }; -use serde::{ - ser::{Error as SerError, SerializeMap, SerializeSeq}, - Serialize, Serializer, -}; +use serde::Serialize; /// Representation of Lambda response #[doc(hidden)] #[derive(Serialize, Debug)] #[serde(untagged)] pub enum LambdaResponse { - ApiGatewayV2(ApiGatewayV2Response), - Alb(AlbResponse), - ApiGateway(ApiGatewayResponse), -} - -/// Representation of API Gateway v2 lambda response -#[doc(hidden)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2Response { - status_code: u16, - #[serde(serialize_with = "serialize_headers")] - headers: HeaderMap, - #[serde(serialize_with = "serialize_headers_slice")] - cookies: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - body: Option, - is_base64_encoded: bool, -} - -/// Representation of ALB lambda response -#[doc(hidden)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct AlbResponse { - status_code: u16, - status_description: String, - #[serde(serialize_with = "serialize_headers")] - headers: HeaderMap, - #[serde(skip_serializing_if = "Option::is_none")] - body: Option, - is_base64_encoded: bool, -} - -/// Representation of API Gateway lambda response -#[doc(hidden)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayResponse { - status_code: u16, - #[serde(serialize_with = "serialize_headers")] - headers: HeaderMap, - #[serde(serialize_with = "serialize_multi_value_headers")] - multi_value_headers: HeaderMap, - #[serde(skip_serializing_if = "Option::is_none")] - body: Option, - is_base64_encoded: bool, -} - -/// Serialize a http::HeaderMap into a serde str => str map -fn serialize_multi_value_headers(headers: &HeaderMap, serializer: S) -> Result -where - S: Serializer, -{ - let mut map = serializer.serialize_map(Some(headers.keys_len()))?; - for key in headers.keys() { - let mut map_values = Vec::new(); - for value in headers.get_all(key) { - map_values.push(value.to_str().map_err(S::Error::custom)?) - } - map.serialize_entry(key.as_str(), &map_values)?; - } - map.end() -} - -/// Serialize a http::HeaderMap into a serde str => Vec map -fn serialize_headers(headers: &HeaderMap, serializer: S) -> Result -where - S: Serializer, -{ - let mut map = serializer.serialize_map(Some(headers.keys_len()))?; - for key in headers.keys() { - let map_value = headers[key].to_str().map_err(S::Error::custom)?; - map.serialize_entry(key.as_str(), map_value)?; - } - map.end() -} - -/// Serialize a &[HeaderValue] into a Vec -fn serialize_headers_slice(headers: &[HeaderValue], serializer: S) -> Result -where - S: Serializer, -{ - let mut seq = serializer.serialize_seq(Some(headers.len()))?; - for header in headers { - seq.serialize_element(header.to_str().map_err(S::Error::custom)?)?; - } - seq.end() + ApiGatewayV2(ApiGatewayV2httpResponse), + ApiGatewayV1(ApiGatewayProxyResponse), + Alb(AlbTargetGroupResponse), } /// tranformation from http type to internal type @@ -125,34 +40,48 @@ impl LambdaResponse { RequestOrigin::ApiGatewayV2 => { // ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute, // so remove them from the headers. - let cookies: Vec = headers.get_all(SET_COOKIE).iter().cloned().collect(); + let cookies = headers + .get_all(SET_COOKIE) + .iter() + .cloned() + .map(|v| v.to_str().ok().unwrap_or_default().to_string()) + .collect(); headers.remove(SET_COOKIE); - LambdaResponse::ApiGatewayV2(ApiGatewayV2Response { + LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse { body, - status_code, - is_base64_encoded, + status_code: status_code as i64, + is_base64_encoded: Some(is_base64_encoded), cookies, - headers, + headers: headers.clone(), + multi_value_headers: headers, }) } - RequestOrigin::ApiGateway => LambdaResponse::ApiGateway(ApiGatewayResponse { + RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, - status_code, - is_base64_encoded, + status_code: status_code as i64, + is_base64_encoded: Some(is_base64_encoded), headers: headers.clone(), multi_value_headers: headers, }), - RequestOrigin::Alb => LambdaResponse::Alb(AlbResponse { + RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse { body, - status_code, + status_code: status_code as i64, is_base64_encoded, - headers, - status_description: format!( + headers: headers.clone(), + multi_value_headers: headers, + status_description: Some(format!( "{} {}", status_code, parts.status.canonical_reason().unwrap_or_default() - ), + )), + }), + RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { + body, + status_code: status_code as i64, + is_base64_encoded: Some(is_base64_encoded), + headers: headers.clone(), + multi_value_headers: headers, }), } } @@ -189,12 +118,15 @@ where } } -impl IntoResponse for B -where - B: Into, -{ +impl IntoResponse for String { + fn into_response(self) -> Response { + Response::new(Body::from(self)) + } +} + +impl IntoResponse for &str { fn into_response(self) -> Response { - Response::new(self.into()) + Response::new(Body::from(self)) } } @@ -213,42 +145,10 @@ impl IntoResponse for serde_json::Value { #[cfg(test)] mod tests { - use super::{ - AlbResponse, ApiGatewayResponse, ApiGatewayV2Response, Body, IntoResponse, LambdaResponse, RequestOrigin, - }; + use super::{Body, IntoResponse, LambdaResponse, RequestOrigin}; use http::{header::CONTENT_TYPE, Response}; use serde_json::{self, json}; - fn api_gateway_response() -> ApiGatewayResponse { - ApiGatewayResponse { - status_code: 200, - headers: Default::default(), - multi_value_headers: Default::default(), - body: Default::default(), - is_base64_encoded: Default::default(), - } - } - - fn alb_response() -> AlbResponse { - AlbResponse { - status_code: 200, - status_description: "200 OK".to_string(), - headers: Default::default(), - body: Default::default(), - is_base64_encoded: Default::default(), - } - } - - fn api_gateway_v2_response() -> ApiGatewayV2Response { - ApiGatewayV2Response { - status_code: 200, - headers: Default::default(), - body: Default::default(), - cookies: Default::default(), - is_base64_encoded: Default::default(), - } - } - #[test] fn json_into_response() { let response = json!({ "hello": "lambda"}).into_response(); @@ -274,40 +174,10 @@ mod tests { } } - #[test] - fn serialize_body_for_api_gateway() { - let mut resp = api_gateway_response(); - resp.body = Some("foo".into()); - assert_eq!( - serde_json::to_string(&resp).expect("failed to serialize response"), - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"foo","isBase64Encoded":false}"# - ); - } - - #[test] - fn serialize_body_for_alb() { - let mut resp = alb_response(); - resp.body = Some("foo".into()); - assert_eq!( - serde_json::to_string(&resp).expect("failed to serialize response"), - r#"{"statusCode":200,"statusDescription":"200 OK","headers":{},"body":"foo","isBase64Encoded":false}"# - ); - } - - #[test] - fn serialize_body_for_api_gateway_v2() { - let mut resp = api_gateway_v2_response(); - resp.body = Some("foo".into()); - assert_eq!( - serde_json::to_string(&resp).expect("failed to serialize response"), - r#"{"statusCode":200,"headers":{},"cookies":[],"body":"foo","isBase64Encoded":false}"# - ); - } - #[test] fn serialize_multi_value_headers() { let res = LambdaResponse::from_response( - &RequestOrigin::ApiGateway, + &RequestOrigin::ApiGatewayV1, Response::builder() .header("multi", "a") .header("multi", "b") @@ -333,8 +203,8 @@ mod tests { ); let json = serde_json::to_string(&res).expect("failed to serialize to json"); assert_eq!( - json, - r#"{"statusCode":200,"headers":{},"cookies":["cookie1=a","cookie2=b"],"isBase64Encoded":false}"# + "{\"statusCode\":200,\"headers\":{},\"multiValueHeaders\":{},\"isBase64Encoded\":false,\"cookies\":[\"cookie1=a\",\"cookie2=b\"]}", + json ) } } diff --git a/lambda-http/src/strmap.rs b/lambda-http/src/strmap.rs deleted file mode 100644 index 066c575a..00000000 --- a/lambda-http/src/strmap.rs +++ /dev/null @@ -1,221 +0,0 @@ -use serde::{ - de::{MapAccess, Visitor}, - Deserialize, Deserializer, -}; -use std::{ - collections::{hash_map::Keys, HashMap}, - fmt, - sync::Arc, -}; - -/// A read-only view into a map of string data which may contain multiple values -/// -/// Internally data is always represented as many valued -#[derive(Default, Debug, PartialEq)] -pub struct StrMap(pub(crate) Arc>>); - -impl StrMap { - /// Return a named value where available. - /// If there is more than one value associated with this name, - /// the first one will be returned - pub fn get(&self, key: &str) -> Option<&str> { - self.0 - .get(key) - .and_then(|values| values.first().map(|owned| owned.as_str())) - } - - /// Return all values associated with name where available - pub fn get_all(&self, key: &str) -> Option> { - self.0 - .get(key) - .map(|values| values.iter().map(|owned| owned.as_str()).collect::>()) - } - - /// Return true if the underlying map is empty - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Return an iterator over keys and values - pub fn iter(&self) -> StrMapIter<'_> { - StrMapIter { - data: self, - keys: self.0.keys(), - current: None, - next_idx: 0, - } - } - - /// Return the URI query representation for this map - pub fn to_query_string(&self) -> String { - if self.is_empty() { - "".into() - } else { - self.iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect::>() - .join("&") - } - } -} - -impl Clone for StrMap { - fn clone(&self) -> Self { - // only clone the inner data - StrMap(self.0.clone()) - } -} - -impl From>> for StrMap { - fn from(inner: HashMap>) -> Self { - StrMap(Arc::new(inner)) - } -} - -/// A read only reference to `StrMap` key and value slice pairings -pub struct StrMapIter<'a> { - data: &'a StrMap, - keys: Keys<'a, String, Vec>, - current: Option<(&'a String, Vec<&'a str>)>, - next_idx: usize, -} - -impl<'a> Iterator for StrMapIter<'a> { - type Item = (&'a str, &'a str); - - #[inline] - fn next(&mut self) -> Option<(&'a str, &'a str)> { - if self.current.is_none() { - self.current = self.keys.next().map(|k| (k, self.data.get_all(k).unwrap_or_default())); - }; - - let mut reset = false; - let ret = if let Some((key, values)) = &self.current { - let value = values[self.next_idx]; - - if self.next_idx + 1 < values.len() { - self.next_idx += 1; - } else { - reset = true; - } - - Some((key.as_str(), value)) - } else { - None - }; - - if reset { - self.current = None; - self.next_idx = 0; - } - - ret - } -} - -/// internal type used when deserializing StrMaps from -/// potentially one or many valued maps -#[derive(Deserialize)] -#[serde(untagged)] -enum OneOrMany { - One(String), - Many(Vec), -} - -impl<'de> Deserialize<'de> for StrMap { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct StrMapVisitor; - - impl<'de> Visitor<'de> for StrMapVisitor { - type Value = StrMap; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a StrMap") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut inner = map.size_hint().map(HashMap::with_capacity).unwrap_or_else(HashMap::new); - // values may either be String or Vec - // to handle both single and multi value data - while let Some((key, value)) = map.next_entry::<_, OneOrMany>()? { - inner.insert( - key, - match value { - OneOrMany::One(one) => vec![one], - OneOrMany::Many(many) => many, - }, - ); - } - Ok(StrMap(Arc::new(inner))) - } - } - - deserializer.deserialize_map(StrMapVisitor) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashMap; - - #[test] - fn str_map_default_is_empty() { - assert!(StrMap::default().is_empty()) - } - - #[test] - fn str_map_get() { - let mut data = HashMap::new(); - data.insert("foo".into(), vec!["bar".into()]); - let strmap = StrMap(data.into()); - assert_eq!(strmap.get("foo"), Some("bar")); - assert_eq!(strmap.get("bar"), None); - } - - #[test] - fn str_map_get_all() { - let mut data = HashMap::new(); - data.insert("foo".into(), vec!["bar".into(), "baz".into()]); - let strmap = StrMap(data.into()); - assert_eq!(strmap.get_all("foo"), Some(vec!["bar", "baz"])); - assert_eq!(strmap.get_all("bar"), None); - } - - #[test] - fn str_map_iter() { - let mut data = HashMap::new(); - data.insert("foo".into(), vec!["bar".into()]); - data.insert("baz".into(), vec!["boom".into()]); - let strmap = StrMap(data.into()); - let mut values = strmap.iter().map(|(_, v)| v).collect::>(); - values.sort(); - assert_eq!(values, vec!["bar", "boom"]); - } - - #[test] - fn test_empty_str_map_to_query_string() { - let data = HashMap::new(); - let strmap = StrMap(data.into()); - let query = strmap.to_query_string(); - assert_eq!("", &query); - } - - #[test] - fn test_str_map_to_query_string() { - let mut data = HashMap::new(); - data.insert("foo".into(), vec!["bar".into(), "qux".into()]); - data.insert("baz".into(), vec!["quux".into()]); - - let strmap = StrMap(data.into()); - let query = strmap.to_query_string(); - assert!(query.contains("foo=bar&foo=qux")); - assert!(query.contains("baz=quux")); - } -} diff --git a/lambda-http/tests/data/apigw_multi_value_proxy_request.json b/lambda-http/tests/data/apigw_multi_value_proxy_request.json index 5b254c8b..8f84aeb9 100644 --- a/lambda-http/tests/data/apigw_multi_value_proxy_request.json +++ b/lambda-http/tests/data/apigw_multi_value_proxy_request.json @@ -51,9 +51,6 @@ "CloudFront-Viewer-Country":[ "US" ], - "":[ - "" - ], "Content-Type":[ "application/json" ], diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs index a9bbe7d5..3f5a4909 100644 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ b/lambda-integration-tests/src/bin/logs-trait.rs @@ -28,14 +28,15 @@ impl MyLogsProcessor { } } +type MyLogsFuture = Pin> + Send>>; + /// Implementation of the actual log processor /// /// This receives a `Vec` whenever there are new log entries available. impl Service> for MyLogsProcessor { type Response = (); type Error = Error; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; + type Future = MyLogsFuture; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { Poll::Ready(Ok(())) From 86a881a40ebc96896c1c25a2c84b8cdbeb5da00a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 19 Feb 2022 15:33:19 -0800 Subject: [PATCH 081/394] Remove reference to unmaintained repository (#427) * Remove reference to unmaintained repository * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 285a97b4..7baccd3e 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ $ npx serverless invoke -f hello -d '{"foo":"bar"}' #### Docker -Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/softprops/lambda-rust). +Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rustserverless/lambda-rust). Running the following command will start a ephemeral docker container which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`, typically this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`) @@ -186,7 +186,7 @@ $ docker run --rm \ -v ${PWD}:/code \ -v ${HOME}/.cargo/registry:/root/.cargo/registry \ -v ${HOME}/.cargo/git:/root/.cargo/git \ - softprops/lambda-rust + rustserverless/lambda-rust ``` With your application build and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambdaci :provided docker container](https://hub.docker.com/r/lambci/lambda/) which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted. From 8c19f505aeb82858bbadfe61d4bc83edc96759c4 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sun, 20 Feb 2022 00:35:50 +0100 Subject: [PATCH 082/394] docs: revamp build & deployment section (#428) * Add references to zigbuild * Add section about AWS SAM * Update Docker repository --- README.md | 139 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 7baccd3e..b94fa6cf 100644 --- a/README.md +++ b/README.md @@ -33,85 +33,67 @@ async fn func(event: LambdaEvent) -> Result { } ``` -### Deployment +## Building and deploying your Lambda functions -There are currently multiple ways of building this package: manually with the AWS CLI, and with the [Serverless framework](https://serverless.com/framework/). +There are currently multiple ways of building this package: manually with the AWS CLI, with [AWS SAM](https://github.com/aws/aws-sam-cli), and with the [Serverless framework](https://serverless.com/framework/). -#### AWS CLI +### 1. Cross-compiling your Lambda functions -To deploy the basic sample as a Lambda function using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html), we first need to manually build it with [`cargo`](https://doc.rust-lang.org/cargo/). Due to a few differences in dependencies, the process for building for Amazon Linux 2 is slightly different than building for Amazon Linux. +At the time of writing, the ability to cross compile to a different architecture is limited. For example, you might experience issues if you are compiling from a MacOS machine, or from a Linux machine with a different architecture (e.g. compiling to Arm64 from x86_64). The most robust way we've found is using [`cargo-zigbuild`](https://github.com/messense/cargo-zigbuild) to compile for the target architecture. -**Building for Amazon Linux 2** +#### 1.1. Setup the cross-compilation environment -Decide which target you'd like to use. For ARM Lambda Functions you'll want to use `aarch64-unknown-linux-gnu` and for x86 Lambda Functions you'll want to use `x86_64-unknown-linux-gnu`. +_You can skip this step if you are compiling for the same target as your host architecture (e.g. x86_64 Linux to x86_64 Linux), unless you're building for an Amazon Linux 1 runtime._ -Run this script once to add your desired target, in this example we'll use x86: +Run this script once to add your desired target: ```bash -$ rustup target add x86_64-unknown-linux-gnu +# For Arm64 Lambda functions +rustup target add aarch64-unknown-linux-gnu +# For x86_64 Lambda functions +rustup target add x86_64-unknown-linux-gnu ``` -Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: +Once this is done, install [Zig](https://ziglang.org/) using the instructions in their [installation guide](https://ziglang.org/learn/getting-started/#installing-zig), and install `cargo-zigbuild`: ```bash -$ cargo build -p lambda_runtime --example basic --release --target x86_64-unknown-linux-gnu +cargo install cargo-zigbuild ``` -_Building on MacOS Using Docker_ +#### 1.2. Build your Lambda functions -At the time of writing, the ability to cross compile to x86 or aarch64 AL2 on MacOS is limited. The most robust way we've found is using Docker to produce the artifacts for you. This guide will work for both Intel and Apple Silicon MacOS and requires that you have set up Docker correctly for either architecture. [See here for a guide on how to do this.](https://docs.docker.com/desktop/mac/install/) +__Amazon Linux 2__ -The following command will pull the [official Rust Docker Image](https://hub.docker.com/_/rust) for a given architecture you plan to use in Lambda and use it to run any cargo commands you need, such as build. +We recommend you to use Amazon Linux 2 runtimes (such as `provided.al2`) as much as possible for building Lambda functions in Rust. To build your Lambda functions for Amazon Linux 2 runtimes, run: ```bash -$ LAMBDA_ARCH="linux/arm64" # set this to either linux/arm64 for ARM functions, or linux/amd64 for x86 functions. -$ RUST_TARGET="aarch64-unknown-linux-gnu" # corresponding with the above, set this to aarch64 or x86_64 -unknown-linux-gnu for ARM or x86 functions. -$ RUST_VERSION="latest" # Set this to a specific version of rust you want to compile for, or to latest if you want the latest stable version. -$ docker run \ - --platform ${LAMBDA_ARCH} \ - --rm --user "$(id -u)":"$(id -g)" \ - -v "${PWD}":/usr/src/myapp -w /usr/src/myapp rust:${RUST_VERSION} \ - cargo build -p lambda_runtime --example basic --release --target ${RUST_TARGET} # This line can be any cargo command +# Note: replace "aarch64" with "x86_64" if you are building for x86_64 +cargo zigbuild --release --target aarch64-unknown-linux-gnu ``` -In short, the above command does the following: +__Amazon Linux 1__ -1. Gives the current user ownership of the artifacts produced by the cargo run. -2. Mounts the current working directory as a volume within the pulled Docker image. -3. Pulls a given Rust Docker image for a given platform. -4. Executes the command on the line beginning with `cargo` within the project directory within the image. +Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with `cargo-zigbuild`, you can specify a different version of glibc. -It is important to note that build artifacts produced from the above command can be found under the expected `target/` directory in the project after build. +If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: -**Building for Amazon Linux 1** - -Run this script once to add the new target: -```bash -$ rustup target add x86_64-unknown-linux-musl ``` - -* **Note:** If you are running on Mac OS you'll need to install the linker for the target platform. You do this using the `musl-cross` tap from [Homebrew](https://brew.sh/) which provides a complete cross-compilation toolchain for Mac OS. Once `musl-cross` is installed we will also need to inform cargo of the newly installed linker when building for the `x86_64-unknown-linux-musl` platform. - - -```bash -$ brew install filosottile/musl-cross/musl-cross -$ mkdir .cargo -$ echo $'[target.x86_64-unknown-linux-musl]\nlinker = "x86_64-linux-musl-gcc"' > .cargo/config +# Note: replace "aarch64" with "x86_64" if you are building for x86_64 +cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.17 ``` -Compile one of the examples as a _release_ with a specific _target_ for deployment to AWS: -```bash -$ cargo build -p lambda_runtime --example basic --release --target x86_64-unknown-linux-musl -``` +### 2. Deploying the binary to AWS Lambda + +For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated executable to `bootstrap` and add it to a zip archive. -**Uploading the Resulting Binary to Lambda** +__Note__: Depending on the target you used above, you'll find the provided basic executable under the corresponding directory. For example, if you are building the aarch64-unknown-linux-gnu as your target, it will be under `./target/aarch64-unknown-linux-gnu/release/`. -For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated `basic` executable to `bootstrap` and add it to a zip archive. +#### 2.1. Deploying with the AWS CLI -NOTE: Depending on the target you used above, you'll find the provided basic executable under the corresponding directory. In the following example, we've compiled for x86_64-unknown-linux-gnu. +First, you will need to create a ZIP archive of your Lambda function. For example, if you are using the `basic` example and aarch64-unknown-linux-gnu as your target, you can run: ```bash -$ cp ./target/x86_64-unknown-linux-gnu/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +cp ./target/aarch64-unknown-linux-gnu/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap ``` Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! @@ -140,7 +122,58 @@ $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} **Note:** `--cli-binary-format raw-in-base64-out` is a required argument when using the AWS CLI version 2. [More Information](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html#cliv2-migration-binaryparam) -#### Serverless Framework +#### 2.2. AWS Serverless Application Model (SAM) + +You can use Lambda functions built in Rust with the [AWS Serverless Application Model (SAM)](https://aws.amazon.com/serverless/sam/). To do so, you will need to install the [AWS SAM CLI](https://github.com/aws/aws-sam-cli), which will help you package and deploy your Lambda functions in your AWS account. + +You will need to create a `template.yaml` file containing your desired infrastructure in YAML. Here is an example with a single Lambda function: + +```yaml +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + MemorySize: 128 + Architectures: ["aarch64"] + Handler: bootstrap + Runtime: provided.al2 + Timeout: 5 + CodeUri: build/ + +Outputs: + FunctionName: + Value: !Ref HelloWorldFunction + Description: Name of the Lambda function +``` + +After building your function, you will also need to store the binary as `bootstrap` in a dedicated directory for that function (e.g. `build/bootstrap`): + +```bash +mkdir build +cp ./target/aarch64-unknown-linux-gnu/release/examples/basic ./build/bootstrap +``` + +You can then deploy your Lambda function using the AWS SAM CLI: + +```bash +sam deploy --guided +``` + +At the end, `sam` will output the actual Lambda function name. You can use this name to invoke your function: + +```bash +$ aws lambda invoke + --cli-binary-format raw-in-base64-out \ + --function-name HelloWorldFunction-XXXXXXXX \ # Replace with the actual function name + --payload '{"command": "Say Hi!"}' \ + output.json +$ cat output.json # Prints: {"msg": "Command Say Hi! executed."} +``` + +#### 2.3. Serverless Framework Alternatively, you can build a Rust-based Lambda function declaratively using the [Serverless framework Rust plugin](https://github.com/softprops/serverless-rust). @@ -174,7 +207,7 @@ Invoke it using serverless framework or a configured AWS integrated trigger sour $ npx serverless invoke -f hello -d '{"foo":"bar"}' ``` -#### Docker +### Docker Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rustserverless/lambda-rust). @@ -212,17 +245,17 @@ $ unzip -o \ Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The lambda handler code does not need to be modified between the local and AWS versions. -## `lambda` +## `lambda_runtime` `lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides `lambda_runtime::run`, a function that runs a `tower::Service`. To write a function that will handle request, you need to pass it through `service_fn`, which will convert your function into a `tower::Service`, which can then be run by `lambda_runtime::run`. -## AWS event objects +### AWS event objects This project does not currently include Lambda event struct definitions though we [intend to do so in the future](https://github.com/awslabs/aws-lambda-rust-runtime/issues/12). Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. -## Custom event objects +### Custom event objects To serialize and deserialize events and responses, we suggest using the use the [`serde`](https://github.com/serde-rs/serde) library. To receive custom events, annotate your structure with Serde's macros: From fbc24b2f03a992a516b34fc8f5eca914bbc9fdb8 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Sun, 20 Feb 2022 22:01:37 +0100 Subject: [PATCH 083/394] fixes following v0.5.0 releases (#429) * fix: rename lambda extension to match crates.io * docs: fix lambda-extension crate name --- README.md | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-integration-tests/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b94fa6cf..7b5b9c86 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. -- [![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. +- [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index cafa4ea0..734323f8 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lambda_extension" +name = "lambda-extension" version = "0.5.0" edition = "2018" authors = ["David Calavera "] diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index f70b1c16..4724b6a1 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -15,7 +15,7 @@ readme = "../README.md" [dependencies] lambda_http = { path = "../lambda-http", version = "0.5" } lambda_runtime = { path = "../lambda-runtime", version = "0.5" } -lambda_extension = { path = "../lambda-extension", version = "0.5" } +lambda-extension = { path = "../lambda-extension", version = "0.5" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } From ac6d74c065aaa7e59f0fb7c01ed660dcf8228bbd Mon Sep 17 00:00:00 2001 From: wancup <62416191+wancup@users.noreply.github.com> Date: Sat, 26 Feb 2022 07:43:51 +0900 Subject: [PATCH 084/394] docs: fix docker mirror repository URL (#432) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b5b9c86..3f294139 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ $ npx serverless invoke -f hello -d '{"foo":"bar"}' ### Docker -Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rustserverless/lambda-rust). +Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rust-serverless/lambda-rust). Running the following command will start a ephemeral docker container which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`, typically this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`) From 850606f456abc3c7f791495c6e7ddcda64bf9953 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 26 Feb 2022 00:22:20 -0800 Subject: [PATCH 085/394] Fix issue parsing WebSocket requests. (#433) * Fix issue parsing WebSocket requests. Allow patch versions of aws_lambda_events. Signed-off-by: David Calavera * Update lambda_http version to release a patch. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8b1d9285..11783a7f 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.5.0" +version = "0.5.1" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -17,7 +17,7 @@ travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" } maintenance = { status = "actively-developed" } [dependencies] -aws_lambda_events = { version = "0.6", default-features = false, features = ["alb", "apigw"]} +aws_lambda_events = { version = "^0.6", default-features = false, features = ["alb", "apigw"]} base64 = "0.13.0" bytes = "1" http = "0.2" From 75e18dde1738c12ddabce52aa57dc3828048e7c6 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Tue, 1 Mar 2022 03:06:16 +0100 Subject: [PATCH 086/394] chore: rename branch to main (#435) --- .github/workflows/build.yml | 4 ++-- CONTRIBUTING.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb191e04..d71e76a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,10 +81,10 @@ jobs: - name: Run clippy check run: cargo clippy - # publish rustdoc to a gh-pages branch on pushes to master + # publish rustdoc to a gh-pages branch on pushes to main # this can be helpful to those depending on the mainline branch publish-docs: - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: [build] steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a293ae8..09a50434 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ reported the issue. Please try to include as much information as you can. Detail ## Contributing via Pull Requests Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *master* branch. +1. You are working against the latest source on the *main* branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. @@ -56,6 +56,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. From e43bc285aa307bc61f54fb911a29d59afd1e0a54 Mon Sep 17 00:00:00 2001 From: Michael Lazear Date: Wed, 2 Mar 2022 22:30:13 -0800 Subject: [PATCH 087/394] Call `handler.poll_ready()` before `handler.call()` (#437) According to the tower::Service documentation and API contract, `poll_ready` must be called and a `Poll:Ready` must be obtained before invoking `call` --- lambda-runtime/src/lib.rs | 73 +++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index a5be8fd1..a178fa3b 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -13,8 +13,8 @@ use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, env, fmt, future::Future, panic}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; -use tower::util::ServiceFn; pub use tower::{self, service_fn, Service}; +use tower::{util::ServiceFn, ServiceExt}; use tracing::{error, trace}; mod requests; @@ -112,41 +112,56 @@ where env::set_var("_X_AMZN_TRACE_ID", xray_trace_id); let request_id = &ctx.request_id.clone(); - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); - - let req = match task { - Ok(response) => match response.await { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionRequest { - request_id, - body: response, - } - .into_req() - } - Err(err) => { - error!("{:?}", err); // logs the error in CloudWatch - EventErrorRequest { - request_id, - diagnostic: Diagnostic { - error_type: type_name_of_val(&err).to_owned(), - error_message: format!("{}", err), // returns the error to the caller via Lambda API - }, + let req = match handler.ready().await { + Ok(handler) => { + let task = + panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + match task { + Ok(response) => match response.await { + Ok(response) => { + trace!("Ok response from handler (run loop)"); + EventCompletionRequest { + request_id, + body: response, + } + .into_req() + } + Err(err) => { + error!("{:?}", err); // logs the error in CloudWatch + EventErrorRequest { + request_id, + diagnostic: Diagnostic { + error_type: type_name_of_val(&err).to_owned(), + error_message: format!("{}", err), // returns the error to the caller via Lambda API + }, + } + .into_req() + } + }, + Err(err) => { + error!("{:?}", err); + EventErrorRequest { + request_id, + diagnostic: Diagnostic { + error_type: type_name_of_val(&err).to_owned(), + error_message: if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {}", msg) + } else { + "Lambda panicked".to_string() + }, + }, + } + .into_req() } - .into_req() } - }, + } Err(err) => { - error!("{:?}", err); + error!("{:?}", err); // logs the error in CloudWatch EventErrorRequest { request_id, diagnostic: Diagnostic { error_type: type_name_of_val(&err).to_owned(), - error_message: if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {}", msg) - } else { - "Lambda panicked".to_string() - }, + error_message: format!("{}", err), // returns the error to the caller via Lambda API }, } .into_req() From 8858f6b804f78824d84cfd5694f1224cb64a1167 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 7 Mar 2022 23:37:37 -0800 Subject: [PATCH 088/394] Update readme to use cargo-lambda (#441) This simplifies the process of building functions. Signed-off-by: David Calavera --- README.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3f294139..116524d9 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,14 @@ rustup target add aarch64-unknown-linux-gnu rustup target add x86_64-unknown-linux-gnu ``` -Once this is done, install [Zig](https://ziglang.org/) using the instructions in their [installation guide](https://ziglang.org/learn/getting-started/#installing-zig), and install `cargo-zigbuild`: +Once this is done, install `cargo-lambda`: ```bash -cargo install cargo-zigbuild +cargo install cargo-lambda ``` +This Cargo subcommand will give you the option to install [Zig](https://ziglang.org/) to use as the linker. You can also install [Zig](https://ziglang.org/) using the instructions in their [installation guide](https://ziglang.org/learn/getting-started/#installing-zig). + #### 1.2. Build your Lambda functions __Amazon Linux 2__ @@ -68,7 +70,7 @@ We recommend you to use Amazon Linux 2 runtimes (such as `provided.al2`) as much ```bash # Note: replace "aarch64" with "x86_64" if you are building for x86_64 -cargo zigbuild --release --target aarch64-unknown-linux-gnu +cargo lambda build --release --target aarch64-unknown-linux-gnu ``` __Amazon Linux 1__ @@ -79,21 +81,21 @@ If you are building for Amazon Linux 1, or you want to support both Amazon Linux ``` # Note: replace "aarch64" with "x86_64" if you are building for x86_64 -cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.17 +cargo lambda build --release --target aarch64-unknown-linux-gnu.2.17 ``` ### 2. Deploying the binary to AWS Lambda For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated executable to `bootstrap` and add it to a zip archive. -__Note__: Depending on the target you used above, you'll find the provided basic executable under the corresponding directory. For example, if you are building the aarch64-unknown-linux-gnu as your target, it will be under `./target/aarch64-unknown-linux-gnu/release/`. +You can find the `bootstrap` binary for your function under the `target/lambda` directory. #### 2.1. Deploying with the AWS CLI First, you will need to create a ZIP archive of your Lambda function. For example, if you are using the `basic` example and aarch64-unknown-linux-gnu as your target, you can run: ```bash -cp ./target/aarch64-unknown-linux-gnu/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap +zip -j lambda.zip target/lambda/basic/bootstrap ``` Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! @@ -141,7 +143,7 @@ Resources: Handler: bootstrap Runtime: provided.al2 Timeout: 5 - CodeUri: build/ + CodeUri: target/lambda/basic/ Outputs: FunctionName: @@ -149,13 +151,6 @@ Outputs: Description: Name of the Lambda function ``` -After building your function, you will also need to store the binary as `bootstrap` in a dedicated directory for that function (e.g. `build/bootstrap`): - -```bash -mkdir build -cp ./target/aarch64-unknown-linux-gnu/release/examples/basic ./build/bootstrap -``` - You can then deploy your Lambda function using the AWS SAM CLI: ```bash From 0d6b25a8eee47e4a5a9257d6504d0ec8df9b0429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Monniot?= Date: Tue, 8 Mar 2022 23:27:53 -0800 Subject: [PATCH 089/394] Relax Send constraint in lambda-http (#442) --- lambda-http/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 95b26f25..53fb9735 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -91,7 +91,7 @@ pub type Request = http::Request; #[doc(hidden)] pub struct TransformResponse<'a, R, E> { request_origin: RequestOrigin, - fut: Pin> + Send + 'a>>, + fut: Pin> + 'a>>, } impl<'a, R, E> Future for TransformResponse<'a, R, E> @@ -121,8 +121,8 @@ pub struct Adapter<'a, R, S> { impl<'a, R, S> From for Adapter<'a, R, S> where - S: Service + Send, - S::Future: Send + 'a, + S: Service, + S::Future: 'a, R: IntoResponse, { fn from(service: S) -> Self { @@ -135,8 +135,8 @@ where impl<'a, R, S> Service> for Adapter<'a, R, S> where - S: Service + Send, - S::Future: Send + 'a, + S: Service, + S::Future: 'a, R: IntoResponse, { type Response = LambdaResponse; @@ -162,8 +162,8 @@ where /// converting the result into a [`LambdaResponse`]. pub async fn run<'a, R, S>(handler: S) -> Result<(), Error> where - S: Service + Send, - S::Future: Send + 'a, + S: Service, + S::Future: 'a, R: IntoResponse, { lambda_runtime::run(Adapter::from(handler)).await From 0bcc004a1ffba650493733dd5c73de988a8ef4f9 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 11 Mar 2022 10:57:58 -0800 Subject: [PATCH 090/394] Update CLI instructions to use cargo-lambda with the output-format flag (#446) This gives people the ability to get the zip file directly after building the function without having to run an extra command --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 116524d9..ca55d88b 100644 --- a/README.md +++ b/README.md @@ -92,18 +92,18 @@ You can find the `bootstrap` binary for your function under the `target/lambda` #### 2.1. Deploying with the AWS CLI -First, you will need to create a ZIP archive of your Lambda function. For example, if you are using the `basic` example and aarch64-unknown-linux-gnu as your target, you can run: +First, you will need to create a ZIP archive of your Lambda function. Cargo-lambda can do that for you automatically when it builds your binary if you add the `output-format` flag. For example, if you are using the `basic` example and aarch64-unknown-linux-gnu as your target, you can run: ```bash -zip -j lambda.zip target/lambda/basic/bootstrap +cargo lambda build --release --target aarch64-unknown-linux-gnu --output-format zip ``` -Now that we have a deployment package (`lambda.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! +Now that we have a deployment package (`target/lambda/basic/bootstrap.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! ```bash $ aws lambda create-function --function-name rustTest \ - --handler doesnt.matter \ - --zip-file fileb://./lambda.zip \ + --handler bootstrap \ + --zip-file fileb://./target/lambda/basic/bootstrap.zip \ --runtime provided.al2 \ # Change this to provided.al if you would like to use Amazon Linux 1. --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ From 2463dfb6c45fc261ba55f66860ad95fedb4310db Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 17 Mar 2022 08:31:34 -0700 Subject: [PATCH 091/394] Be nice to more standard http servers. (#448) * Be nice to more standard http servers. Handle HTTP status codes coming from the runtime server in a way that plays nice with local development tools like the runtime interface emulator. Signed-off-by: David Calavera * Enable http niciness only when binaries are compiled in debug mode. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 2 +- lambda-runtime/src/lib.rs | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 63c649c6..cff30fc7 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -72,7 +72,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request Date: Sun, 20 Mar 2022 09:27:43 -0700 Subject: [PATCH 092/394] Release version 0.5.1 of the runtime. (#449) Signed-off-by: David Calavera --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index cd12e046..98592eb7 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.5.0" +version = "0.5.1" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2018" From 3e378187896da8b493f6e46e6a9c232e0419a5f3 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 23 Mar 2022 08:02:56 -0700 Subject: [PATCH 093/394] Expose the raw http path coming from the lambda event. (#453) The API GW url includes th API GW stage information, so it's not easy to know what's the raw http path coming into the event. With this change, we expose the raw http path in a general way, so people that need to know the exact raw path have a common interface, regardless of where the event comes from. Signed-off-by: David Calavera --- lambda-http/examples/hello-raw-http-path.rs | 13 ++++++++++ lambda-http/src/ext.rs | 28 +++++++++++++++++++++ lambda-http/src/request.rs | 17 ++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 lambda-http/examples/hello-raw-http-path.rs diff --git a/lambda-http/examples/hello-raw-http-path.rs b/lambda-http/examples/hello-raw-http-path.rs new file mode 100644 index 00000000..06bcdf71 --- /dev/null +++ b/lambda-http/examples/hello-raw-http-path.rs @@ -0,0 +1,13 @@ +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_http::run(service_fn(func)).await?; + Ok(()) +} + +async fn func(event: Request) -> Result { + let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response(); + + Ok(res) +} diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index e33e639c..b53cd851 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -20,6 +20,9 @@ pub(crate) struct PathParameters(pub(crate) QueryMap); /// These will always be empty for ALB requests pub(crate) struct StageVariables(pub(crate) QueryMap); +/// ALB/API gateway raw http path without any stage information +pub(crate) struct RawHttpPath(pub(crate) String); + /// Request payload deserialization errors /// /// Returned by [`RequestExt#payload()`](trait.RequestExt.html#tymethod.payload) @@ -104,6 +107,12 @@ impl Error for PayloadError { /// } /// ``` pub trait RequestExt { + /// Return the raw http path for a request without any stage information. + fn raw_http_path(&self) -> String; + + /// Configures instance with the raw http path. + fn with_raw_http_path(self, path: &str) -> Self; + /// Return pre-parsed http query string parameters, parameters /// provided after the `?` portion of a url, /// associated with the API gateway request. @@ -177,6 +186,19 @@ pub trait RequestExt { } impl RequestExt for http::Request { + fn raw_http_path(&self) -> String { + self.extensions() + .get::() + .map(|ext| ext.0.clone()) + .unwrap_or_default() + } + + fn with_raw_http_path(self, path: &str) -> Self { + let mut s = self; + s.extensions_mut().insert(RawHttpPath(path.into())); + s + } + fn query_string_parameters(&self) -> QueryMap { self.extensions() .get::() @@ -401,4 +423,10 @@ mod tests { let payload: Option = request.payload().unwrap_or_default(); assert_eq!(payload, None); } + + #[test] + fn requests_can_mock_raw_http_path_ext() { + let request = Request::default().with_raw_http_path("/raw-path"); + assert_eq!("/raw-path", request.raw_http_path().as_str()); + } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index cff30fc7..9c9d84db 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,7 +3,7 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use crate::ext::{PathParameters, QueryStringParameters, StageVariables}; +use crate::ext::{PathParameters, QueryStringParameters, RawHttpPath, StageVariables}; use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; use aws_lambda_events::apigw::{ ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext, @@ -61,6 +61,8 @@ pub enum RequestOrigin { fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request { let http_method = ag.request_context.http.method.clone(); + let raw_path = ag.raw_path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let scheme = ag @@ -75,7 +77,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request http::Request http::Request { let http_method = ag.http_method; + let raw_path = ag.path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); - let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); let mut url = match host { None => path, @@ -141,6 +146,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { } url }) + .extension(RawHttpPath(raw_path)) // multi-valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred @@ -178,6 +184,8 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let http_method = alb.http_method; + let raw_path = alb.path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let scheme = alb @@ -191,7 +199,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { .and_then(|s| s.to_str().ok()) .unwrap_or_default(); - let mut url = format!("{}://{}{}", scheme, host, alb.path.unwrap_or_default()); + let mut url = format!("{}://{}{}", scheme, host, &raw_path); if !alb.multi_value_query_string_parameters.is_empty() { url.push('?'); url.push_str(&alb.multi_value_query_string_parameters.to_query_string()); @@ -202,6 +210,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { url }) + .extension(RawHttpPath(raw_path)) // multi valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred From eac2e144f8210a38c333ec57bb00ef94cd28160e Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 23 Mar 2022 23:09:20 +0800 Subject: [PATCH 094/394] allow customized User_Agent for lambda runtime api client (#454) * allow customized User_Agent for lambda runtime api client * update the README --- lambda-runtime-api-client/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 83d789cc..253d561c 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -14,7 +14,8 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tower_service::Service; const USER_AGENT_HEADER: &str = "User-Agent"; -const USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); +const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); +const CUSTOM_USER_AGENT: Option<&str> = option_env!("LAMBDA_RUNTIME_USER_AGENT"); /// Error type that lambdas may result in pub type Error = Box; @@ -128,7 +129,13 @@ where /// Create a request builder. /// This builder uses `aws-lambda-rust/CRATE_VERSION` as /// the default User-Agent. +/// Configure environment variable `LAMBDA_RUNTIME_USER_AGENT` +/// at compile time to modify User-Agent value. pub fn build_request() -> http::request::Builder { + const USER_AGENT: &str = match CUSTOM_USER_AGENT { + Some(value) => value, + None => DEFAULT_USER_AGENT, + }; http::Request::builder().header(USER_AGENT_HEADER, USER_AGENT) } From 7c4032ca17ab8ad11b7cdf04279265f81fa87692 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 4 Apr 2022 06:08:12 -0700 Subject: [PATCH 095/394] Document option to run functions locally. (#460) Cargo-Lambda includes a very straightforward command to run lambda locally. --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ca55d88b..f9627140 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. - ## Example function The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. @@ -236,7 +235,15 @@ $ unzip -o \ # Ctrl-D to yield control back to your function ``` -### Debugging +## Local development and testing + +### Cargo Lambda + +[Cargo Lambda](https://crates.io/crates/cargo-lambda) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. I the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. + +You can read more about how [cargo lambda start](https://github.com/calavera/cargo-lambda#start) and [cargo lambda invoke](https://github.com/calavera/cargo-lambda#invoke) work on the [project's README](https://github.com/calavera/cargo-lambda#cargo-lambda). + +### Lambda Debug Proxy Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The lambda handler code does not need to be modified between the local and AWS versions. @@ -246,9 +253,9 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt To write a function that will handle request, you need to pass it through `service_fn`, which will convert your function into a `tower::Service`, which can then be run by `lambda_runtime::run`. -### AWS event objects +## AWS event objects -This project does not currently include Lambda event struct definitions though we [intend to do so in the future](https://github.com/awslabs/aws-lambda-rust-runtime/issues/12). Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. +This project does not currently include Lambda event struct definitions. Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. ### Custom event objects From aff310b0e52d8f4a389b98b672a7121b230fbe82 Mon Sep 17 00:00:00 2001 From: Luke Pfeiffer Date: Fri, 8 Apr 2022 11:10:39 -0700 Subject: [PATCH 096/394] Fixed invalid architectures reference in the SAM snippet (#1) (#464) Lambda functions don't list an `aarch64` architecture as a valid option for the `Architectures` list in the serverless function SAM resource definition. This should instead be `arm64`. Valid architectures can be found here: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-architectures --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9627140..8bc7417d 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Resources: Type: AWS::Serverless::Function Properties: MemorySize: 128 - Architectures: ["aarch64"] + Architectures: ["arm64"] Handler: bootstrap Runtime: provided.al2 Timeout: 5 From efb7f6905b410d8a1348eb83b8e5a6cc37820d83 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 5 May 2022 09:12:09 -0700 Subject: [PATCH 097/394] Release lambda_http 0.5.2 (#469) * Release lambda_http 0.5.2 Signed-off-by: David Calavera * Update query_map to match version of aws_lambda_events. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 11783a7f..ac5bc283 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.5.1" +version = "0.5.2" authors = ["Doug Tangren"] edition = "2018" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -26,7 +26,7 @@ lambda_runtime = { path = "../lambda-runtime", version = "0.5" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" -query_map = { version = "0.4", features = ["url-query"] } +query_map = { version = "0.5", features = ["url-query"] } [dev-dependencies] log = "^0.4" From 4b4c02c3956e7bdcee3da893427ed7c97f7c2cd2 Mon Sep 17 00:00:00 2001 From: Everett Pompeii Date: Sat, 7 May 2022 14:26:59 -0400 Subject: [PATCH 098/394] Fix typo (#477) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bc7417d..27be42e0 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ $ unzip -o \ ### Cargo Lambda -[Cargo Lambda](https://crates.io/crates/cargo-lambda) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. I the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. +[Cargo Lambda](https://crates.io/crates/cargo-lambda) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. You can read more about how [cargo lambda start](https://github.com/calavera/cargo-lambda#start) and [cargo lambda invoke](https://github.com/calavera/cargo-lambda#invoke) work on the [project's README](https://github.com/calavera/cargo-lambda#cargo-lambda). From 4b3e6c3eb0d21c25af6cdc1bcf4840577c586461 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 9 May 2022 08:06:37 -0700 Subject: [PATCH 099/394] Pin aws_lambda_events dependency to patch versions (#476) If you already have a version of aws_lambda_events in your project, and you update lambda_http, the events package's version might not be updated. This can cause compilation issues like the ones we saw in #471. With this change, the events package will be update with new patch versions, which should prevent issues like that from happening. --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ac5bc283..2b5caec1 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -17,7 +17,7 @@ travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" } maintenance = { status = "actively-developed" } [dependencies] -aws_lambda_events = { version = "^0.6", default-features = false, features = ["alb", "apigw"]} +aws_lambda_events = { version = "^0.6.3", default-features = false, features = ["alb", "apigw"]} base64 = "0.13.0" bytes = "1" http = "0.2" @@ -32,4 +32,4 @@ query_map = { version = "0.5", features = ["url-query"] } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } -tower-http = { version = "0.2", features = ["cors"] } \ No newline at end of file +tower-http = { version = "0.2", features = ["cors"] } From 76d26b9efedd11fe3982e36db45617b68b8d9924 Mon Sep 17 00:00:00 2001 From: david-perez Date: Mon, 9 May 2022 17:06:55 +0200 Subject: [PATCH 100/394] Don't require Lambda `Service` to use `lambda_http::Error` (#474) Currently, the `Service` that you pass to `lambda_http::run` is required to use `lambda_http::Error` as its associated error type, a type-erased boxed error type. However, all the runtime needs is for the error type to implement `Debug` and `Display`. This commit relaxes this constraint, allowing users to run a broader class of `Service`s. --- .../examples/shared-resources-example.rs | 22 ++++++++++--------- lambda-http/src/lib.rs | 21 ++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lambda-http/examples/shared-resources-example.rs b/lambda-http/examples/shared-resources-example.rs index a90dd815..fd27e65f 100644 --- a/lambda-http/examples/shared-resources-example.rs +++ b/lambda-http/examples/shared-resources-example.rs @@ -1,4 +1,4 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{service_fn, Body, Error, IntoResponse, Request, RequestExt, Response}; struct SharedClient { name: &'static str, @@ -20,15 +20,17 @@ async fn main() -> Result<(), Error> { // Define a closure here that makes use of the shared client. let handler_func_closure = move |event: Request| async move { - Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => shared_client_ref - .response(event.lambda_context().request_id, first_name) - .into_response(), - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) + Result::, std::convert::Infallible>::Ok( + match event.query_string_parameters().first("first_name") { + Some(first_name) => shared_client_ref + .response(event.lambda_context().request_id, first_name) + .into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }, + ) }; // Pass the closure to the runtime here. diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 53fb9735..62de0f13 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -23,7 +23,9 @@ //! async fn main() -> Result<(), Error> { //! // initialize dependencies once here for the lifetime of your //! // lambda task -//! lambda_http::run(service_fn(|request| async { Ok("👋 world!") })).await?; +//! lambda_http::run(service_fn(|request| async { +//! Result::<&str, std::convert::Infallible>::Ok("👋 world!") +//! })).await?; //! Ok(()) //! } //! ``` @@ -44,7 +46,7 @@ //! //! async fn hello( //! request: Request -//! ) -> Result { +//! ) -> Result { //! let _context = request.lambda_context(); //! //! Ok(format!( @@ -119,9 +121,9 @@ pub struct Adapter<'a, R, S> { _phantom_data: PhantomData<&'a R>, } -impl<'a, R, S> From for Adapter<'a, R, S> +impl<'a, R, S, E> From for Adapter<'a, R, S> where - S: Service, + S: Service, S::Future: 'a, R: IntoResponse, { @@ -133,14 +135,14 @@ where } } -impl<'a, R, S> Service> for Adapter<'a, R, S> +impl<'a, R, S, E> Service> for Adapter<'a, R, S> where - S: Service, + S: Service, S::Future: 'a, R: IntoResponse, { type Response = LambdaResponse; - type Error = Error; + type Error = E; type Future = TransformResponse<'a, R, Self::Error>; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { @@ -160,11 +162,12 @@ where /// /// This takes care of transforming the LambdaEvent into a [`Request`] and then /// converting the result into a [`LambdaResponse`]. -pub async fn run<'a, R, S>(handler: S) -> Result<(), Error> +pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> where - S: Service, + S: Service, S::Future: 'a, R: IntoResponse, + E: std::fmt::Debug + std::fmt::Display, { lambda_runtime::run(Adapter::from(handler)).await } From 22f39419d96673b26591f51b484ed99276056661 Mon Sep 17 00:00:00 2001 From: Zack Kanter Date: Sun, 22 May 2022 12:11:36 -0400 Subject: [PATCH 101/394] Update README to fix various typos (#485) --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 27be42e0..704b13d8 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} Alternatively, you can build a Rust-based Lambda function declaratively using the [Serverless framework Rust plugin](https://github.com/softprops/serverless-rust). -A number of getting started Serverless application templates exist to get you up and running quickly +A number of getting started Serverless application templates exist to get you up and running quickly: - a minimal [echo function](https://github.com/softprops/serverless-aws-rust) to demonstrate what the smallest Rust function setup looks like - a minimal [http function](https://github.com/softprops/serverless-aws-rust-http) to demonstrate how to interface with API Gateway using Rust's native [http](https://crates.io/crates/http) crate (note this will be a git dependency until 0.2 is published) @@ -188,7 +188,7 @@ $ npx serverless install \ && npm install --silent ``` -Deploy it using the standard serverless workflow +Deploy it using the standard serverless workflow: ```bash # build, package, and deploy service to aws lambda @@ -205,7 +205,7 @@ $ npx serverless invoke -f hello -d '{"foo":"bar"}' Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rust-serverless/lambda-rust). -Running the following command will start a ephemeral docker container which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`, typically this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`) +Running the following command will start an ephemeral docker container, which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`. Typically, this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`): ```bash # build and package deploy-ready artifact @@ -216,7 +216,7 @@ $ docker run --rm \ rustserverless/lambda-rust ``` -With your application build and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambdaci :provided docker container](https://hub.docker.com/r/lambci/lambda/) which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted. +With your application build and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: ```bash # start a docker container replicating the "provided" lambda runtime @@ -245,7 +245,7 @@ You can read more about how [cargo lambda start](https://github.com/calavera/car ### Lambda Debug Proxy -Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The lambda handler code does not need to be modified between the local and AWS versions. +Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. ## `lambda_runtime` @@ -259,7 +259,7 @@ This project does not currently include Lambda event struct definitions. Instead ### Custom event objects -To serialize and deserialize events and responses, we suggest using the use the [`serde`](https://github.com/serde-rs/serde) library. To receive custom events, annotate your structure with Serde's macros: +To serialize and deserialize events and responses, we suggest using the [`serde`](https://github.com/serde-rs/serde) library. To receive custom events, annotate your structure with Serde's macros: ```rust use serde::{Serialize, Deserialize}; From bb34300f829fc6a04dca75d0cf817118daf15c99 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 23 May 2022 07:15:11 -0700 Subject: [PATCH 102/394] Use raw_query_string to populate QueryStringParameters (#483) API GW v2 is not compatible with the whatgw specification. Using the raw query string makes the request behave closer to the standard. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9c9d84db..d4770dd6 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -63,6 +63,17 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request Date: Fri, 27 May 2022 02:42:35 -0700 Subject: [PATCH 103/394] Update readme with more up to date information (#484) - Simplify the build and deployment sections. - Add a new section about unit tests. Signed-off-by: David Calavera --- README.md | 151 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 704b13d8..f49ebd1e 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,36 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. -## Example function +## Getting started -The code below creates a simple function that receives an event with a `firstName` field and returns a message to the caller. Notice: this crate is tested against latest stable Rust. +The easiest way to start writing Lambda functions with Rust is by using Cargo-Lambda. This Cargo subcommand provides several commands to help you in your journey with Rust on AWS Lambda. + +You can install `cargo-lambda` with a package manager like Homebrew: + +```bash +brew tap cargo-lambda/cargo-lambda +brew install cargo-lambda +``` + +Or by compiling it from source: + +```bash +cargo install cargo-lambda +``` + +See other installation options in [the cargo-lambda documentation](https://github.com/cargo-lambda/cargo-lambda#installation). + +### Your first function + +To create your first function, run `cargo-lambda` with the subcomand `new`. This command will generate a Rust package with the initial source code for your function: + +``` +cargo lambda new YOUR_FUNCTION_NAME +``` + +### Example function + +If you'd like to manually create your first function, the code below shows you a simple function that receives an event with a `firstName` field and returns a message to the caller. ```rust,no_run use lambda_runtime::{service_fn, LambdaEvent, Error}; @@ -34,32 +61,18 @@ async fn func(event: LambdaEvent) -> Result { ## Building and deploying your Lambda functions -There are currently multiple ways of building this package: manually with the AWS CLI, with [AWS SAM](https://github.com/aws/aws-sam-cli), and with the [Serverless framework](https://serverless.com/framework/). +If you already have `cargo-lambda` installed in your machine, run the next command to build your function: -### 1. Cross-compiling your Lambda functions - -At the time of writing, the ability to cross compile to a different architecture is limited. For example, you might experience issues if you are compiling from a MacOS machine, or from a Linux machine with a different architecture (e.g. compiling to Arm64 from x86_64). The most robust way we've found is using [`cargo-zigbuild`](https://github.com/messense/cargo-zigbuild) to compile for the target architecture. - -#### 1.1. Setup the cross-compilation environment - -_You can skip this step if you are compiling for the same target as your host architecture (e.g. x86_64 Linux to x86_64 Linux), unless you're building for an Amazon Linux 1 runtime._ - -Run this script once to add your desired target: - -```bash -# For Arm64 Lambda functions -rustup target add aarch64-unknown-linux-gnu -# For x86_64 Lambda functions -rustup target add x86_64-unknown-linux-gnu +``` +cargo lambda build --release ``` -Once this is done, install `cargo-lambda`: +There are other ways of building your function: manually with the AWS CLI, with [AWS SAM](https://github.com/aws/aws-sam-cli), and with the [Serverless framework](https://serverless.com/framework/). -```bash -cargo install cargo-lambda -``` -This Cargo subcommand will give you the option to install [Zig](https://ziglang.org/) to use as the linker. You can also install [Zig](https://ziglang.org/) using the instructions in their [installation guide](https://ziglang.org/learn/getting-started/#installing-zig). +### 1. Cross-compiling your Lambda functions + +By default, `cargo-lambda` builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. #### 1.2. Build your Lambda functions @@ -68,13 +81,12 @@ __Amazon Linux 2__ We recommend you to use Amazon Linux 2 runtimes (such as `provided.al2`) as much as possible for building Lambda functions in Rust. To build your Lambda functions for Amazon Linux 2 runtimes, run: ```bash -# Note: replace "aarch64" with "x86_64" if you are building for x86_64 -cargo lambda build --release --target aarch64-unknown-linux-gnu +cargo lambda build --release --arm64 ``` __Amazon Linux 1__ -Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with `cargo-zigbuild`, you can specify a different version of glibc. +Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with `cargo-lambda`, you can specify a different version of glibc. If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: @@ -89,15 +101,50 @@ For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-cus You can find the `bootstrap` binary for your function under the `target/lambda` directory. -#### 2.1. Deploying with the AWS CLI +#### 2.2. Deploying with cargo-lambda -First, you will need to create a ZIP archive of your Lambda function. Cargo-lambda can do that for you automatically when it builds your binary if you add the `output-format` flag. For example, if you are using the `basic` example and aarch64-unknown-linux-gnu as your target, you can run: +You can use `cargo-lambda` for simple function deployments. Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: ```bash -cargo lambda build --release --target aarch64-unknown-linux-gnu --output-format zip +cargo lambda deploy \ + --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role ``` -Now that we have a deployment package (`target/lambda/basic/bootstrap.zip`), we can use the [AWS CLI](https://aws.amazon.com/cli/) to create a new Lambda function. Make sure to replace the execution role with an existing role in your account! +> **warning** +> Make sure to replace the execution role with an existing role in your account! + +This command will create a Lambda function with the same name of your rust package. You can change the name +of the function by adding the argument at the end of the command: + + +```bash +cargo lambda deploy \ + --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ + my-first-lambda-function +``` + +> **info** +> See other deployment options in [the cargo-lambda documentation](https://github.com/cargo-lambda/cargo-lambda#deploy). + +You can test the function with `cargo-lambda`'s invoke subcommand: + +```bash +cargo lambda invoke --remote \ + --data-ascii '{"command": "hi"}' \ + --output-format json \ + my-first-lambda-function +``` + +#### 2.2. Deploying with the AWS CLI + +You can also use the AWS CLI to deploy your Rust functions. First, you will need to create a ZIP archive of your function. Cargo-lambda can do that for you automatically when it builds your binary if you add the `output-format` flag: + +```bash +cargo lambda build --release --arm64 --output-format zip +``` + +You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip`. Use that file path to deploy your function with the [AWS CLI](https://aws.amazon.com/cli/): + ```bash $ aws lambda create-function --function-name rustTest \ @@ -109,6 +156,9 @@ $ aws lambda create-function --function-name rustTest \ --tracing-config Mode=Active ``` +> **warning** +> Make sure to replace the execution role with an existing role in your account! + You can now test the function using the AWS CLI or the AWS Lambda console ```bash @@ -123,7 +173,7 @@ $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} **Note:** `--cli-binary-format raw-in-base64-out` is a required argument when using the AWS CLI version 2. [More Information](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html#cliv2-migration-binaryparam) -#### 2.2. AWS Serverless Application Model (SAM) +#### 2.3. AWS Serverless Application Model (SAM) You can use Lambda functions built in Rust with the [AWS Serverless Application Model (SAM)](https://aws.amazon.com/serverless/sam/). To do so, you will need to install the [AWS SAM CLI](https://github.com/aws/aws-sam-cli), which will help you package and deploy your Lambda functions in your AWS account. @@ -167,7 +217,7 @@ $ aws lambda invoke $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} ``` -#### 2.3. Serverless Framework +#### 2.4. Serverless Framework Alternatively, you can build a Rust-based Lambda function declaratively using the [Serverless framework Rust plugin](https://github.com/softprops/serverless-rust). @@ -201,7 +251,7 @@ Invoke it using serverless framework or a configured AWS integrated trigger sour $ npx serverless invoke -f hello -d '{"foo":"bar"}' ``` -### Docker +#### 2.5. Docker Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rust-serverless/lambda-rust). @@ -237,6 +287,39 @@ $ unzip -o \ ## Local development and testing +### Testing your code with unit and integration tests + +AWS Lambda events are plain structures deserialized from JSON objects. +If your function handler uses the standard runtime, you can use `serde` to deserialize +your text fixtures into the structures, and call your handler directly: + +```rust,no_run +#[test] +fn test_my_lambda_handler() { + let input = serde_json::from_str("{\"command\": \"Say Hi!\"}").expect("failed to parse event"); + let context = lambda_runtime::types::Context::default(); + + let event = lambda_runtime::types::LambdaEvent::new(input, context); + + my_lambda_handler(event).expect("failed to handle event"); +} +``` + +If you're using `lambda_http` to receive HTTP events, you can also create `http_lambda::Request` +structures from plain text fixtures: + +```rust,no_run +#[test] +fn test_my_lambda_handler() { + let input = include_str!("apigw_proxy_request.json"); + + let request = lambda_http::request::from_str(input) + .expect("failed to create request"); + + let response = my_lambda_handler(request).expect("failed to handle request"); +} +``` + ### Cargo Lambda [Cargo Lambda](https://crates.io/crates/cargo-lambda) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. @@ -261,7 +344,7 @@ This project does not currently include Lambda event struct definitions. Instead To serialize and deserialize events and responses, we suggest using the [`serde`](https://github.com/serde-rs/serde) library. To receive custom events, annotate your structure with Serde's macros: -```rust +```rust,no_run use serde::{Serialize, Deserialize}; use serde_json::json; use std::error::Error; From 4217f5147a334d2e57b443ccb15c80a49c1bd999 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 30 May 2022 11:14:18 -0700 Subject: [PATCH 104/394] Reduce dependency features (#486) This saves about 12 seconds of build times in simple functions. - Stop compiling Tower full - Remove server and runtime features from hyper Signed-off-by: David Calavera --- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 2b8291b8..8a8a29b2 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -12,6 +12,6 @@ readme = "README.md" [dependencies] http = "0.2" -hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +hyper = { version = "0.14", features = ["http1", "client", "stream", "tcp"] } tower-service = "0.3" tokio = { version = "1.0", features = ["io-util"] } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 98592eb7..a5cc27aa 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -16,14 +16,14 @@ simulated = [] [dependencies] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } -hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +hyper = { version = "0.14", features = ["http1", "client", "stream", "tcp"] } serde = { version = "1", features = ["derive"] } serde_json = "^1" bytes = "1.0" http = "0.2" async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } -tower = { version = "0.4", features = ["full"] } +tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } From 2dedba800874579c7823cfc57414bfc208d2f7db Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 31 May 2022 20:41:01 -0700 Subject: [PATCH 105/394] Move examples to a central directory (#487) * Move examples to a central directory Signed-off-by: David Calavera * Remove duplicated gitignore files. Signed-off-by: David Calavera --- .github/workflows/build.yml | 14 + .gitignore | 2 +- Cargo.toml | 4 +- examples/basic-error-handling/Cargo.toml | 22 ++ examples/basic-error-handling/README.md | 11 + .../basic-error-handling/src/main.rs | 3 - examples/basic-lambda/Cargo.toml | 20 ++ examples/basic-lambda/README.md | 11 + .../basic-lambda/src/main.rs | 3 - examples/basic-shared-resource/Cargo.toml | 20 ++ examples/basic-shared-resource/README.md | 11 + .../basic-shared-resource/src/main.rs | 3 - examples/check-examples.sh | 11 + examples/extension-basic/Cargo.toml | 20 ++ examples/extension-basic/README.md | 14 + .../extension-basic/src/main.rs | 3 - examples/extension-combined/Cargo.toml | 20 ++ examples/extension-combined/README.md | 14 + .../extension-combined/src/main.rs | 3 - examples/extension-custom-events/Cargo.toml | 20 ++ examples/extension-custom-events/README.md | 14 + .../extension-custom-events/src/main.rs | 3 - examples/extension-custom-service/Cargo.toml | 20 ++ examples/extension-custom-service/README.md | 14 + .../extension-custom-service/src/main.rs | 3 - examples/extension-logs-basic/Cargo.toml | 20 ++ examples/extension-logs-basic/README.md | 14 + .../extension-logs-basic/src/main.rs | 8 + .../extension-logs-custom-service/Cargo.toml | 20 ++ .../extension-logs-custom-service/README.md | 14 + .../extension-logs-custom-service/src/main.rs | 8 + examples/extension-logs-kinesis-firehose | 1 + examples/http-basic-lambda/Cargo.toml | 20 ++ examples/http-basic-lambda/README.md | 11 + examples/http-basic-lambda/src/main.rs | 29 ++ examples/http-cors/Cargo.toml | 21 ++ examples/http-cors/README.md | 11 + .../http-cors/src/main.rs | 13 +- examples/http-query-parameters/Cargo.toml | 20 ++ examples/http-query-parameters/README.md | 11 + examples/http-query-parameters/src/main.rs | 27 ++ examples/http-raw-path/Cargo.toml | 20 ++ examples/http-raw-path/README.md | 11 + examples/http-raw-path/src/main.rs | 21 ++ examples/http-shared-resource/Cargo.toml | 20 ++ examples/http-shared-resource/README.md | 11 + .../http-shared-resource/src/main.rs | 28 +- lambda-extension/Cargo.toml | 3 - lambda-extension/README.md | 31 ++- lambda-http/Cargo.toml | 2 +- lambda-http/examples/hello-http.rs | 17 -- lambda-http/examples/hello-raw-http-path.rs | 13 - lambda-runtime/Cargo.toml | 6 +- lambda-runtime/examples/README.md | 250 ------------------ 54 files changed, 633 insertions(+), 331 deletions(-) create mode 100644 examples/basic-error-handling/Cargo.toml create mode 100644 examples/basic-error-handling/README.md rename lambda-runtime/examples/error-handling.rs => examples/basic-error-handling/src/main.rs (95%) create mode 100644 examples/basic-lambda/Cargo.toml create mode 100644 examples/basic-lambda/README.md rename lambda-runtime/examples/basic.rs => examples/basic-lambda/src/main.rs (90%) create mode 100644 examples/basic-shared-resource/Cargo.toml create mode 100644 examples/basic-shared-resource/README.md rename lambda-runtime/examples/shared_resource.rs => examples/basic-shared-resource/src/main.rs (92%) create mode 100755 examples/check-examples.sh create mode 100644 examples/extension-basic/Cargo.toml create mode 100644 examples/extension-basic/README.md rename lambda-extension/examples/basic.rs => examples/extension-basic/src/main.rs (84%) create mode 100644 examples/extension-combined/Cargo.toml create mode 100644 examples/extension-combined/README.md rename lambda-extension/examples/combined.rs => examples/extension-combined/src/main.rs (90%) create mode 100644 examples/extension-custom-events/Cargo.toml create mode 100644 examples/extension-custom-events/README.md rename lambda-extension/examples/custom_events.rs => examples/extension-custom-events/src/main.rs (86%) create mode 100644 examples/extension-custom-service/Cargo.toml create mode 100644 examples/extension-custom-service/README.md rename lambda-extension/examples/custom_service.rs => examples/extension-custom-service/src/main.rs (89%) create mode 100644 examples/extension-logs-basic/Cargo.toml create mode 100644 examples/extension-logs-basic/README.md rename lambda-extension/examples/logs.rs => examples/extension-logs-basic/src/main.rs (63%) create mode 100644 examples/extension-logs-custom-service/Cargo.toml create mode 100644 examples/extension-logs-custom-service/README.md rename lambda-extension/examples/custom_logs_service.rs => examples/extension-logs-custom-service/src/main.rs (83%) create mode 160000 examples/extension-logs-kinesis-firehose create mode 100644 examples/http-basic-lambda/Cargo.toml create mode 100644 examples/http-basic-lambda/README.md create mode 100644 examples/http-basic-lambda/src/main.rs create mode 100644 examples/http-cors/Cargo.toml create mode 100644 examples/http-cors/README.md rename lambda-http/examples/hello-cors.rs => examples/http-cors/src/main.rs (61%) create mode 100644 examples/http-query-parameters/Cargo.toml create mode 100644 examples/http-query-parameters/README.md create mode 100644 examples/http-query-parameters/src/main.rs create mode 100644 examples/http-raw-path/Cargo.toml create mode 100644 examples/http-raw-path/README.md create mode 100644 examples/http-raw-path/src/main.rs create mode 100644 examples/http-shared-resource/Cargo.toml create mode 100644 examples/http-shared-resource/README.md rename lambda-http/examples/shared-resources-example.rs => examples/http-shared-resource/src/main.rs (51%) delete mode 100644 lambda-http/examples/hello-http.rs delete mode 100644 lambda-http/examples/hello-raw-http-path.rs delete mode 100644 lambda-runtime/examples/README.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d71e76a9..570224eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,6 +81,20 @@ jobs: - name: Run clippy check run: cargo clippy + check-examples: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + - name: Check examples + working-directory: examples + shell: bash + run: ./check-examples.sh + # publish rustdoc to a gh-pages branch on pushes to main # this can be helpful to those depending on the mainline branch publish-docs: diff --git a/.gitignore b/.gitignore index 266ec088..51840b6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +target /.cargo lambda-runtime/libtest.rmeta lambda-integration-tests/target diff --git a/Cargo.toml b/Cargo.toml index 4ef3789d..7361557e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,6 @@ members = [ "lambda-runtime-api-client", "lambda-runtime", "lambda-extension" -] \ No newline at end of file +] + +exclude = ["examples"] diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml new file mode 100644 index 00000000..e8699141 --- /dev/null +++ b/examples/basic-error-handling/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "error-handling" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +serde_json = "1.0.81" +simple-error = "0.2.3" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/basic-error-handling/README.md b/examples/basic-error-handling/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/basic-error-handling/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-runtime/examples/error-handling.rs b/examples/basic-error-handling/src/main.rs similarity index 95% rename from lambda-runtime/examples/error-handling.rs rename to examples/basic-error-handling/src/main.rs index 6b7a3c96..ee721f95 100644 --- a/lambda-runtime/examples/error-handling.rs +++ b/examples/basic-error-handling/src/main.rs @@ -53,9 +53,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml new file mode 100644 index 00000000..ebf36913 --- /dev/null +++ b/examples/basic-lambda/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "basic-lambda" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/basic-lambda/README.md b/examples/basic-lambda/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/basic-lambda/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-runtime/examples/basic.rs b/examples/basic-lambda/src/main.rs similarity index 90% rename from lambda-runtime/examples/basic.rs rename to examples/basic-lambda/src/main.rs index 26589f0d..30a74851 100644 --- a/lambda-runtime/examples/basic.rs +++ b/examples/basic-lambda/src/main.rs @@ -26,9 +26,6 @@ struct Response { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml new file mode 100644 index 00000000..a26bf3cc --- /dev/null +++ b/examples/basic-shared-resource/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "shared-resource" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/basic-shared-resource/README.md b/examples/basic-shared-resource/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/basic-shared-resource/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-runtime/examples/shared_resource.rs b/examples/basic-shared-resource/src/main.rs similarity index 92% rename from lambda-runtime/examples/shared_resource.rs rename to examples/basic-shared-resource/src/main.rs index cd9366dd..a157e5ea 100644 --- a/lambda-runtime/examples/shared_resource.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -47,9 +47,6 @@ async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/check-examples.sh b/examples/check-examples.sh new file mode 100755 index 00000000..28824579 --- /dev/null +++ b/examples/check-examples.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +for f in *; do + if [ -d "$f" ]; then + echo "==> Checking example: $f" + cd $f + cargo check + cd .. + fi +done diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml new file mode 100644 index 00000000..caf0818c --- /dev/null +++ b/examples/extension-basic/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-basic" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-basic/README.md b/examples/extension-basic/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-basic/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/basic.rs b/examples/extension-basic/src/main.rs similarity index 84% rename from lambda-extension/examples/basic.rs rename to examples/extension-basic/src/main.rs index 7a722e72..54784b03 100644 --- a/lambda-extension/examples/basic.rs +++ b/examples/extension-basic/src/main.rs @@ -18,9 +18,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml new file mode 100644 index 00000000..d776f488 --- /dev/null +++ b/examples/extension-combined/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-combined" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-combined/README.md b/examples/extension-combined/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-combined/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/combined.rs b/examples/extension-combined/src/main.rs similarity index 90% rename from lambda-extension/examples/combined.rs rename to examples/extension-combined/src/main.rs index eede270f..bc6cd5e0 100644 --- a/lambda-extension/examples/combined.rs +++ b/examples/extension-combined/src/main.rs @@ -33,9 +33,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml new file mode 100644 index 00000000..a826a137 --- /dev/null +++ b/examples/extension-custom-events/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-custom-events" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-custom-events/README.md b/examples/extension-custom-events/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-custom-events/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/custom_events.rs b/examples/extension-custom-events/src/main.rs similarity index 86% rename from lambda-extension/examples/custom_events.rs rename to examples/extension-custom-events/src/main.rs index f796ca31..7717fc2e 100644 --- a/lambda-extension/examples/custom_events.rs +++ b/examples/extension-custom-events/src/main.rs @@ -20,9 +20,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml new file mode 100644 index 00000000..c9ff789a --- /dev/null +++ b/examples/extension-custom-service/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-custom-service" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-custom-service/README.md b/examples/extension-custom-service/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-custom-service/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/custom_service.rs b/examples/extension-custom-service/src/main.rs similarity index 89% rename from lambda-extension/examples/custom_service.rs rename to examples/extension-custom-service/src/main.rs index c9dff5c3..968dd904 100644 --- a/lambda-extension/examples/custom_service.rs +++ b/examples/extension-custom-service/src/main.rs @@ -37,9 +37,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml new file mode 100644 index 00000000..d1983db8 --- /dev/null +++ b/examples/extension-logs-basic/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-logs-basic" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros", "rt"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-logs-basic/README.md b/examples/extension-logs-basic/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-logs-basic/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/logs.rs b/examples/extension-logs-basic/src/main.rs similarity index 63% rename from lambda-extension/examples/logs.rs rename to examples/extension-logs-basic/src/main.rs index 79973a46..f9ea845a 100644 --- a/lambda-extension/examples/logs.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -15,6 +15,14 @@ async fn handler(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + let logs_processor = SharedService::new(service_fn(handler)); Extension::new().with_logs_processor(logs_processor).run().await?; diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml new file mode 100644 index 00000000..cbbe20f6 --- /dev/null +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-logs-custom-service" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-logs-custom-service/README.md b/examples/extension-logs-custom-service/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-logs-custom-service/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/lambda-extension/examples/custom_logs_service.rs b/examples/extension-logs-custom-service/src/main.rs similarity index 83% rename from lambda-extension/examples/custom_logs_service.rs rename to examples/extension-logs-custom-service/src/main.rs index aace5f6b..bcdf660d 100644 --- a/lambda-extension/examples/custom_logs_service.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -56,6 +56,14 @@ impl Service> for MyLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + let logs_processor = SharedService::new(MyLogsProcessor::new()); Extension::new().with_logs_processor(logs_processor).run().await?; diff --git a/examples/extension-logs-kinesis-firehose b/examples/extension-logs-kinesis-firehose new file mode 160000 index 00000000..e2596af7 --- /dev/null +++ b/examples/extension-logs-kinesis-firehose @@ -0,0 +1 @@ +Subproject commit e2596af77d7abdf8d1c2b6e6f78cac0b6ac3e64f diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml new file mode 100644 index 00000000..1a218330 --- /dev/null +++ b/examples/http-basic-lambda/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-basic-lambda" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/http-basic-lambda/README.md b/examples/http-basic-lambda/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-basic-lambda/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs new file mode 100644 index 00000000..cf15fec9 --- /dev/null +++ b/examples/http-basic-lambda/src/main.rs @@ -0,0 +1,29 @@ +use lambda_http::{run, service_fn, Error, IntoResponse, Request, Response}; + +/// This is the main body for the function. +/// Write your code inside it. +/// There are some code examples in the Runtime repository: +/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +async fn function_handler(_event: Request) -> Result { + // Extract some useful information from the request + + // Return something that implements IntoResponse. + // It will be serialized to the right response event automatically by the runtime + let resp = Response::builder() + .status(200) + .header("content-type", "text/html") + .body("Hello AWS Lambda HTTP request") + .map_err(Box::new)?; + Ok(resp) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + run(service_fn(function_handler)).await +} diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml new file mode 100644 index 00000000..9fd7f25b --- /dev/null +++ b/examples/http-cors/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "http-cors" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tower-http = { version = "0.3.3", features = ["cors"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/http-cors/README.md b/examples/http-cors/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-cors/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-http/examples/hello-cors.rs b/examples/http-cors/src/main.rs similarity index 61% rename from lambda-http/examples/hello-cors.rs rename to examples/http-cors/src/main.rs index 275873a4..459dc6f3 100644 --- a/lambda-http/examples/hello-cors.rs +++ b/examples/http-cors/src/main.rs @@ -1,9 +1,18 @@ -use http::Method; -use lambda_http::{service_fn, tower::ServiceBuilder, Body, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{ + http::Method, service_fn, tower::ServiceBuilder, Body, Error, IntoResponse, Request, RequestExt, Response, +}; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + // Define a layer to inject CORS headers let cors_layer = CorsLayer::new() .allow_methods(vec![Method::GET, Method::POST]) diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml new file mode 100644 index 00000000..7aeb1189 --- /dev/null +++ b/examples/http-query-parameters/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-query-parameters" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/http-query-parameters/README.md b/examples/http-query-parameters/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-query-parameters/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs new file mode 100644 index 00000000..1e499948 --- /dev/null +++ b/examples/http-query-parameters/src/main.rs @@ -0,0 +1,27 @@ +use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Response}; + +/// This is the main body for the function. +/// Write your code inside it. +/// You can see more examples in Runtime's repository: +/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +async fn function_handler(event: Request) -> Result { + // Extract some useful information from the request + Ok(match event.query_string_parameters().first("first_name") { + Some(first_name) => format!("Hello, {}!", first_name).into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + run(service_fn(function_handler)).await +} diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml new file mode 100644 index 00000000..f4060428 --- /dev/null +++ b/examples/http-raw-path/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-raw-path" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/http-raw-path/README.md b/examples/http-raw-path/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-raw-path/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs new file mode 100644 index 00000000..1caafeab --- /dev/null +++ b/examples/http-raw-path/src/main.rs @@ -0,0 +1,21 @@ +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + lambda_http::run(service_fn(func)).await?; + Ok(()) +} + +async fn func(event: Request) -> Result { + let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response(); + + Ok(res) +} diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml new file mode 100644 index 00000000..207f253b --- /dev/null +++ b/examples/http-shared-resource/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-shared-resource" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/http-shared-resource/README.md b/examples/http-shared-resource/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-shared-resource/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-http/examples/shared-resources-example.rs b/examples/http-shared-resource/src/main.rs similarity index 51% rename from lambda-http/examples/shared-resources-example.rs rename to examples/http-shared-resource/src/main.rs index fd27e65f..4491ac75 100644 --- a/lambda-http/examples/shared-resources-example.rs +++ b/examples/http-shared-resource/src/main.rs @@ -12,6 +12,14 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + // Create the "client" and a reference to it, so that we can pass this into the handler closure below. let shared_client = SharedClient { name: "random_client_name_1", @@ -20,17 +28,15 @@ async fn main() -> Result<(), Error> { // Define a closure here that makes use of the shared client. let handler_func_closure = move |event: Request| async move { - Result::, std::convert::Infallible>::Ok( - match event.query_string_parameters().first("first_name") { - Some(first_name) => shared_client_ref - .response(event.lambda_context().request_id, first_name) - .into_response(), - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }, - ) + Result::, Error>::Ok(match event.query_string_parameters().first("first_name") { + Some(first_name) => shared_client_ref + .response(event.lambda_context().request_id, first_name) + .into_response(), + _ => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }) }; // Pass the closure to the runtime here. diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 734323f8..d7b0102a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -24,6 +24,3 @@ tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-th tokio-stream = "0.1.2" tower = { version = "0.4", features = ["make", "util"] } -[dev-dependencies] -simple-error = "0.2" -tracing-subscriber = "0.3" diff --git a/lambda-extension/README.md b/lambda-extension/README.md index 95b08ccd..d26e62b7 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -81,16 +81,33 @@ Regardless of how you deploy them, the extensions MUST be compiled against the s ### Building extensions -Once you've decided which target you'll use, you can install it by running the next `rustup` command: +- Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -```bash -$ rustup target add x86_64-unknown-linux-musl +- Build the extension with: + +``` +cargo lambda build --release --extension ``` -Then, you can compile the extension against that target: +If you want to run the extension in ARM processors, add the `--arm64` flag to the previous command: + +``` +cargo lambda build --release --extension --arm64 +``` + +This previous command will generate a binary file in `target/lambda/extensions` called `basic`. When the extension is registered with the [Runtime Extensions API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-extensions-api-reg), that's the name that the extension will be registered with. If you want to register the extension with a different name, you only have to rename this binary file and deploy it with the new name. + +### Deploying extensions -```bash -$ cargo build -p lambda_extension --example basic --release --target x86_64-unknown-linux-musl +- Make sure you have the right credentials in your terminal by running the AWS CLI configure command: + +``` +aws configure +``` + +- Deploy the extension as a layer with: + +``` +cargo lambda deploy --extension ``` -This previous command will generate a binary file in `target/x86_64-unknown-linux-musl/release/examples` called `basic`. When the extension is registered with the [Runtime Extensions API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-extensions-api-reg), that's the name that the extension will be registered with. If you want to register the extension with a different name, you only have to rename this binary file and deploy it with the new name. \ No newline at end of file diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 2b5caec1..a032a131 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -32,4 +32,4 @@ query_map = { version = "0.5", features = ["url-query"] } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } -tower-http = { version = "0.2", features = ["cors"] } + diff --git a/lambda-http/examples/hello-http.rs b/lambda-http/examples/hello-http.rs deleted file mode 100644 index 5b679196..00000000 --- a/lambda-http/examples/hello-http.rs +++ /dev/null @@ -1,17 +0,0 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_http::run(service_fn(func)).await?; - Ok(()) -} - -async fn func(event: Request) -> Result { - Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response(), - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) -} diff --git a/lambda-http/examples/hello-raw-http-path.rs b/lambda-http/examples/hello-raw-http-path.rs deleted file mode 100644 index 06bcdf71..00000000 --- a/lambda-http/examples/hello-raw-http-path.rs +++ /dev/null @@ -1,13 +0,0 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_http::run(service_fn(func)).await?; - Ok(()) -} - -async fn func(event: Request) -> Result { - let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response(); - - Ok(res) -} diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index a5cc27aa..1e3d2d6e 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -16,7 +16,8 @@ simulated = [] [dependencies] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } -hyper = { version = "0.14", features = ["http1", "client", "stream", "tcp"] } +# Hyper requires the `server` feature to work on nightly +hyper = { version = "0.14", features = ["http1", "client", "stream", "server"] } serde = { version = "1", features = ["derive"] } serde_json = "^1" bytes = "1.0" @@ -27,6 +28,3 @@ tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } -[dev-dependencies] -tracing-subscriber = "0.3" -simple-error = "0.2" diff --git a/lambda-runtime/examples/README.md b/lambda-runtime/examples/README.md deleted file mode 100644 index 5e080eee..00000000 --- a/lambda-runtime/examples/README.md +++ /dev/null @@ -1,250 +0,0 @@ - -## How to compile and run the examples - -1. Create a Lambda function called _RuntimeTest_ in AWS with a custom runtime and no code. - -2. Compile all examples - -``` -cargo build --release --target x86_64-unknown-linux-musl --examples -``` -3. Prepare the package for the example you want to test, e.g. -``` -cp ./target/x86_64-unknown-linux-musl/release/examples/hello ./bootstrap && zip lambda.zip bootstrap && rm bootstrap -``` -4. Upload the package to AWS Lambda -``` -aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip -``` -_Feel free to replace the names and IDs with your own values._ - -## basic.rs - -**Deployment**: -```bash -cp ./target/x86_64-unknown-linux-musl/release/examples/basic ./bootstrap && zip lambda.zip bootstrap && rm bootstrap -aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip -``` - -**Test event JSON (success)**: -```json -{ "command": "do something" } -``` - -Sample response: -```json -{ - "msg": "Command do something executed.", - "req_id": "67a038e4-dc19-4adf-aa32-5ba09312c6ca" -} -``` - -**Test event JSON (error)**: -```json -{ "foo": "do something" } -``` - -Sample response: -```json -{ - "errorType": "Runtime.ExitError", - "errorMessage": "RequestId: 586366df-f271-4e6e-9c30-b3dcab30f4e8 Error: Runtime exited with error: exit status 1" -} -``` -The runtime could not deserialize our invalid input, but it did not give a detailed explanation why the error occurred in the response. More details appear in the CloudWatch log: -``` -START RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Version: $LATEST -Error: Error("missing field `command`", line: 1, column: 22) -END RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f -REPORT RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Duration: 36.34 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 10 MB -RequestId: 6e667f61-c5d4-4b07-a60f-cd1ab339c35f Error: Runtime exited with error: exit status 1 -Runtime.ExitError -``` - - See _error-handling.rs_ example for more error handling options. - -**Deployment**: -```bash -cp ./target/x86_64-unknown-linux-musl/release/examples/macro ./bootstrap && zip lambda.zip bootstrap && rm bootstrap -aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip -``` - -**Test event JSON**: -```json -{ "foo": "bar" } -``` - -Sample response: -```json -{ - "foo": "bar" -} -``` - -## error-handling.rs - -Errors are logged by the runtime only if `log` is initialized by the handler. -These examples use `simple_logger`, but you can use any other provider that works with `log`. -``` -simple_logger::init_with_level(log::Level::Debug)?; -``` - -**Deployment**: -```bash -cp ./target/x86_64-unknown-linux-musl/release/examples/error-handling ./bootstrap && zip lambda.zip bootstrap && rm bootstrap -aws lambda update-function-code --region us-east-1 --function-name RuntimeTest --zip-file fileb://lambda.zip -``` - -The following input/output examples correspond to different `match` arms in the handler of `error-handling.rs`. - -#### Invalid event JSON - -Test input: -```json -{ - "event_type": "WrongType" -} -``` - -Lambda output: -``` -{ - "errorType": "alloc::boxed::Box", - "errorMessage": "unknown variant `WrongType`, expected one of `Response`, `ExternalError`, `SimpleError`, `CustomError`" -} -``` - -CloudWatch records: -``` -START RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba Version: $LATEST -2020-07-21 04:28:52,630 ERROR [lambda] unknown variant `WrongType`, expected one of `Response`, `ExternalError`, `SimpleError`, `CustomError` -END RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba -REPORT RequestId: b98e07c6-e2ba-4ca6-9968-d0b94729ddba Duration: 2.06 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 28 MB Init Duration: 33.67 ms -``` - -#### A simple text-only error - -Test event JSON: -```json -{ - "event_type": "SimpleError" -} -``` - -Lambda output: -``` -{ - "errorType": "alloc::boxed::Box", - "errorMessage": "A simple error as requested!" -} -``` - -CloudWatch records: -``` -START RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 Version: $LATEST -2020-07-21 04:35:28,044 ERROR [lambda] A simple error as requested! -END RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 -REPORT RequestId: 77c66dbf-bd60-4f77-8453-682d0bceba91 Duration: 0.98 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 28 MB -``` - -#### A custom error with JSON output for Display trait - -Test event JSON: -```json -{ - "event_type": "CustomError" -} -``` - -Lambda output: -``` -{ - "errorType": "alloc::boxed::Box", - "errorMessage": "{\"is_authenticated\":false,\"msg\":\"A custom error as requested!\",\"req_id\":\"b46b0588-1383-4224-bc7a-42b0d61930c1\"}" -} -``` - -CloudWatch records: -``` -START RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 Version: $LATEST -2020-07-21 04:39:00,133 ERROR [lambda] {"is_authenticated":false,"msg":"A custom error as requested!","req_id":"b46b0588-1383-4224-bc7a-42b0d61930c1"} -END RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 -REPORT RequestId: b46b0588-1383-4224-bc7a-42b0d61930c1 Duration: 0.91 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB -``` - -#### A 3rd party error from _std::fs::File::open_ - -Test event JSON: -```json -{ - "event_type": "ExternalError" -} -``` - -Lambda output: -``` -{ - "errorType": "alloc::boxed::Box", - "errorMessage": "No such file or directory (os error 2)" -} -``` - -CloudWatch records: -``` -START RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da Version: $LATEST -2020-07-21 04:43:56,254 ERROR [lambda] No such file or directory (os error 2) -END RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da -REPORT RequestId: 893d24e5-cb79-4f6f-bae0-36304c62e9da Duration: 1.15 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB -``` - -#### Handler panic - -Test event JSON: -```json -{ - "event_type": "Panic" -} -``` - -Lambda output: -``` -{ - "errorType": "Runtime.ExitError", - "errorMessage": "RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Error: Runtime exited with error: exit status 101" -} -``` - -CloudWatch records: -``` -START RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Version: $LATEST -thread 'main' panicked at 'explicit panic', lambda/examples/error-handling.rs:87:13 -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -END RequestId: 2d579019-07f7-409a-a6e6-af7725253307 -REPORT RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Duration: 43.40 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 27 MB Init Duration: 23.15 ms -RequestId: 2d579019-07f7-409a-a6e6-af7725253307 Error: Runtime exited with error: exit status 101 -Runtime.ExitError -``` - -#### A response to a successful Lambda execution - -Test event JSON: -```json -{ - "event_type": "Response" -} -``` - -Lambda output: -``` -{ - "msg": "OK", - "req_id": "9752a3ad-6566-44e4-aafd-74db1fd4f361" -} -``` - -CloudWatch records: -``` -START RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 Version: $LATEST -END RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 -REPORT RequestId: 9752a3ad-6566-44e4-aafd-74db1fd4f361 Duration: 0.89 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB -``` \ No newline at end of file From c737920ebc39c97682e9cb38b5c3f0961e662030 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 23 Jun 2022 08:13:27 -0700 Subject: [PATCH 106/394] Fix kinesis example (#493) * Remove kinesis example The repository doesn't recognize this directory properly. Signed-off-by: David Calavera * Add example back Signed-off-by: David Calavera --- examples/extension-logs-kinesis-firehose | 1 - .../Cargo.toml | 17 +++++ .../extension-logs-kinesis-firehose/README.md | 14 ++++ .../src/main.rs | 66 +++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) delete mode 160000 examples/extension-logs-kinesis-firehose create mode 100644 examples/extension-logs-kinesis-firehose/Cargo.toml create mode 100644 examples/extension-logs-kinesis-firehose/README.md create mode 100644 examples/extension-logs-kinesis-firehose/src/main.rs diff --git a/examples/extension-logs-kinesis-firehose b/examples/extension-logs-kinesis-firehose deleted file mode 160000 index e2596af7..00000000 --- a/examples/extension-logs-kinesis-firehose +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e2596af77d7abdf8d1c2b6e6f78cac0b6ac3e64f diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml new file mode 100644 index 00000000..942d0a93 --- /dev/null +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "lambda-logs-firehose-extension" +version = "0.1.0" +edition = "2021" + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +tokio = { version = "1.17.0", features = ["full"] } +aws-config = "0.13.0" +aws-sdk-firehose = "0.13.0" + diff --git a/examples/extension-logs-kinesis-firehose/README.md b/examples/extension-logs-kinesis-firehose/README.md new file mode 100644 index 00000000..6f9636c9 --- /dev/null +++ b/examples/extension-logs-kinesis-firehose/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Logs extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs new file mode 100644 index 00000000..d771cf4b --- /dev/null +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -0,0 +1,66 @@ +use aws_sdk_firehose::{model::Record, types::Blob, Client}; +use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use std::{future::Future, pin::Pin, task::Poll}; + +#[derive(Clone)] +struct FirehoseLogsProcessor { + client: Client, +} + +impl FirehoseLogsProcessor { + pub fn new(client: Client) -> Self { + FirehoseLogsProcessor { client } + } +} + +/// Implementation of the actual log processor +/// +/// This receives a `Vec` whenever there are new log entries available. +impl Service> for FirehoseLogsProcessor { + type Response = (); + type Error = Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, logs: Vec) -> Self::Future { + let mut records = Vec::with_capacity(logs.len()); + + for log in logs { + match log.record { + LambdaLogRecord::Function(record) => { + records.push(Record::builder().data(Blob::new(record.as_bytes())).build()) + } + _ => unreachable!(), + } + } + + let fut = self + .client + .put_record_batch() + .set_records(Some(records)) + .delivery_stream_name(std::env::var("KINESIS_DELIVERY_STREAM").unwrap()) + .send(); + + Box::pin(async move { + let _ = fut.await?; + Ok(()) + }) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let config = aws_config::load_from_env().await; + let logs_processor = SharedService::new(FirehoseLogsProcessor::new(Client::new(&config))); + + Extension::new() + .with_log_types(&["function"]) + .with_logs_processor(logs_processor) + .run() + .await?; + + Ok(()) +} From 28b4296ac1f558fc7f822f704d1c954bcefa87eb Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 27 Jun 2022 07:51:39 -0700 Subject: [PATCH 107/394] Ignore hosts from ALB health checks when they are not present (#495) ALB health checks don't include a host value. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 61 +++++++++++++++++-------- lambda-http/tests/data/alb_no_host.json | 17 +++++++ 2 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 lambda-http/tests/data/alb_no_host.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index d4770dd6..d6b0678d 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -76,21 +76,24 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request path, + Some(host) => { + let scheme = ag + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + format!("{}://{}{}", scheme, host, path) + } + }; if let Some(query) = ag.raw_query_string { url.push('?'); url.push_str(&query); @@ -199,18 +202,20 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let builder = http::Request::builder() .uri({ - let scheme = alb - .headers - .get(x_forwarded_proto()) - .and_then(|s| s.to_str().ok()) - .unwrap_or("https"); - let host = alb - .headers - .get(http::header::HOST) - .and_then(|s| s.to_str().ok()) - .unwrap_or_default(); + let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); + + let mut url = match host { + None => raw_path.clone(), + Some(host) => { + let scheme = alb + .headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + format!("{}://{}{}", scheme, host, &raw_path) + } + }; - let mut url = format!("{}://{}{}", scheme, host, &raw_path); if !alb.multi_value_query_string_parameters.is_empty() { url.push('?'); url.push_str(&alb.multi_value_query_string_parameters.to_query_string()); @@ -625,4 +630,20 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), "/test/test/hello?name=me"); } + + #[test] + fn deserialize_alb_no_host() { + // generated from ALB health checks + let input = include_str!("../tests/data/alb_no_host.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {:?} given {}", + result, + input + ); + let req = result.expect("failed to parse request"); + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), "/v1/health/"); + } } diff --git a/lambda-http/tests/data/alb_no_host.json b/lambda-http/tests/data/alb_no_host.json new file mode 100644 index 00000000..1e5ad01f --- /dev/null +++ b/lambda-http/tests/data/alb_no_host.json @@ -0,0 +1,17 @@ +{ + "body": "", + "headers": { + "user-agent": "ELB-HealthChecker/2.0" + }, + "httpMethod": "GET", + + "isBase64Encoded": false, + + "path": "/v1/health/", + "queryStringParameters": {}, + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:111111111:targetgroup/cdn-ingestor-tg-usea1-dev/3fe2aca58c0da101" + } + } +} \ No newline at end of file From ff948fab191012a338ad5981a8aaf815da0a00e8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 5 Jul 2022 17:49:15 -0700 Subject: [PATCH 108/394] Feature flags for lambda_http (#497) * Feature flags for lambda_http Add feature flags to lambda_http so consumer can decide which service their events come from. This makes compilation much faster when you don't put the same Lambda function behind multiple services. Signed-off-by: David Calavera * Rename features to match AWS documentation. Use the names present in the API GW documentation to differentiate the APIs. Signed-off-by: David Calavera * Fix file names Signed-off-by: David Calavera * Mention Function URLs in the readme. Signed-off-by: David Calavera * Fix typo. Signed-off-by: David Calavera --- .gitignore | 1 + README.md | 30 ++++++++++++++++++++------ lambda-http/Cargo.toml | 15 +++++++++---- lambda-http/src/request.rs | 43 ++++++++++++++++++++++++++++++------- lambda-http/src/response.rs | 29 +++++++++++++++++-------- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 51840b6d..d8feb244 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ output.json .aws-sam build +.vscode diff --git a/README.md b/README.md index f49ebd1e..46f87e5c 100644 --- a/README.md +++ b/README.md @@ -330,12 +330,6 @@ You can read more about how [cargo lambda start](https://github.com/calavera/car Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. -## `lambda_runtime` - -`lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides `lambda_runtime::run`, a function that runs a `tower::Service`. - -To write a function that will handle request, you need to pass it through `service_fn`, which will convert your function into a `tower::Service`, which can then be run by `lambda_runtime::run`. - ## AWS event objects This project does not currently include Lambda event struct definitions. Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. @@ -378,6 +372,30 @@ fn main() -> Result<(), Box> { } ``` +## Feature flags in lambda_http + +`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions. + +By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags. + +The available features flags for `lambda_http` are the following: + +- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). +- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). +- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). +- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). + +If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable: + +```toml +[dependencies.lambda_http] +version = "0.5.3" +default-features = false +features = ["apigw_rest"] +``` + +This will make your function compile much faster. + ## Supported Rust Versions (MSRV) The AWS Lambda Rust Runtime requires a minimum of Rust 1.54, and is not guaranteed to build on compiler versions earlier than that. diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index a032a131..57a43f55 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -12,12 +12,14 @@ documentation = "https://docs.rs/lambda_runtime" categories = ["web-programming::http-server"] readme = "../README.md" -[badges] -travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" } -maintenance = { status = "actively-developed" } +[features] +default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"] +apigw_rest = [] +apigw_http = [] +apigw_websockets = [] +alb = [] [dependencies] -aws_lambda_events = { version = "^0.6.3", default-features = false, features = ["alb", "apigw"]} base64 = "0.13.0" bytes = "1" http = "0.2" @@ -28,6 +30,11 @@ serde_json = "^1" serde_urlencoded = "0.7.0" query_map = { version = "0.5", features = ["url-query"] } +[dependencies.aws_lambda_events] +version = "^0.6.3" +default-features = false +features = ["alb", "apigw"] + [dev-dependencies] log = "^0.4" maplit = "1.0" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index d6b0678d..c6327b7e 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -4,11 +4,14 @@ //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! use crate::ext::{PathParameters, QueryStringParameters, RawHttpPath, StageVariables}; +#[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; -use aws_lambda_events::apigw::{ - ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext, - ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext, -}; +#[cfg(feature = "apigw_rest")] +use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext}; +#[cfg(feature = "apigw_http")] +use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext}; +#[cfg(feature = "apigw_websockets")] +use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; use aws_lambda_events::encodings::Body; use http::header::HeaderName; use query_map::QueryMap; @@ -25,9 +28,13 @@ use std::{io::Read, mem}; #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum LambdaRequest { + #[cfg(feature = "apigw_rest")] ApiGatewayV1(ApiGatewayProxyRequest), + #[cfg(feature = "apigw_http")] ApiGatewayV2(ApiGatewayV2httpRequest), + #[cfg(feature = "alb")] Alb(AlbTargetGroupRequest), + #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequest), } @@ -37,9 +44,13 @@ impl LambdaRequest { /// type of response the request origin expects. pub fn request_origin(&self) -> RequestOrigin { match self { + #[cfg(feature = "apigw_rest")] LambdaRequest::ApiGatewayV1 { .. } => RequestOrigin::ApiGatewayV1, + #[cfg(feature = "apigw_http")] LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2, + #[cfg(feature = "alb")] LambdaRequest::Alb { .. } => RequestOrigin::Alb, + #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, } } @@ -50,15 +61,20 @@ impl LambdaRequest { #[derive(Debug)] pub enum RequestOrigin { /// API Gateway request origin + #[cfg(feature = "apigw_rest")] ApiGatewayV1, /// API Gateway v2 request origin + #[cfg(feature = "apigw_http")] ApiGatewayV2, /// ALB request origin + #[cfg(feature = "alb")] Alb, /// API Gateway WebSocket + #[cfg(feature = "apigw_websockets")] WebSocket, } +#[cfg(feature = "apigw_http")] fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request { let http_method = ag.request_context.http.method.clone(); let raw_path = ag.raw_path.unwrap_or_default(); @@ -130,6 +146,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request { let http_method = ag.http_method; let raw_path = ag.path.unwrap_or_default(); @@ -196,6 +213,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { req } +#[cfg(feature = "alb")] fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let http_method = alb.http_method; let raw_path = alb.path.unwrap_or_default(); @@ -261,6 +279,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { req } +#[cfg(feature = "apigw_websockets")] fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request { let http_method = ag.http_method; let builder = http::Request::builder() @@ -338,12 +357,16 @@ fn apigw_path_with_stage(stage: &Option, path: &str) -> String { #[serde(untagged)] pub enum RequestContext { /// API Gateway proxy request context + #[cfg(feature = "apigw_rest")] ApiGatewayV1(ApiGatewayProxyRequestContext), /// API Gateway v2 request context + #[cfg(feature = "apigw_http")] ApiGatewayV2(ApiGatewayV2httpRequestContext), /// ALB request context + #[cfg(feature = "alb")] Alb(AlbTargetGroupRequestContext), /// WebSocket request context + #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequestContext), } @@ -351,9 +374,13 @@ pub enum RequestContext { impl<'a> From for http::Request { fn from(value: LambdaRequest) -> Self { match value { - LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag), + #[cfg(feature = "apigw_rest")] LambdaRequest::ApiGatewayV1(ag) => into_proxy_request(ag), + #[cfg(feature = "apigw_http")] + LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag), + #[cfg(feature = "alb")] LambdaRequest::Alb(alb) => into_alb_request(alb), + #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket(ag) => into_websocket_request(ag), } } @@ -422,7 +449,7 @@ mod tests { } #[test] - fn deserializes_minimal_apigw_v2_request_events() { + fn deserializes_minimal_apigw_http_request_events() { // from the docs // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request let input = include_str!("../tests/data/apigw_v2_proxy_request_minimal.json"); @@ -450,7 +477,7 @@ mod tests { } #[test] - fn deserializes_apigw_v2_request_events() { + fn deserializes_apigw_http_request_events() { // from the docs // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request let input = include_str!("../tests/data/apigw_v2_proxy_request.json"); @@ -593,7 +620,7 @@ mod tests { } #[test] - fn deserialize_apigw_v2_sam_local() { + fn deserialize_apigw_http_sam_local() { // manually generated from AWS SAM CLI // Steps to recreate: // * sam init diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 4ea9c895..0c46ae68 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -2,8 +2,12 @@ use crate::request::RequestOrigin; use aws_lambda_events::encodings::Body; +#[cfg(feature = "alb")] use aws_lambda_events::event::alb::AlbTargetGroupResponse; -use aws_lambda_events::event::apigw::{ApiGatewayProxyResponse, ApiGatewayV2httpResponse}; +#[cfg(any(feature = "apigw_rest", feature = "apigw_websockets"))] +use aws_lambda_events::event::apigw::ApiGatewayProxyResponse; +#[cfg(feature = "apigw_http")] +use aws_lambda_events::event::apigw::ApiGatewayV2httpResponse; use http::{ header::{CONTENT_TYPE, SET_COOKIE}, Response, @@ -15,8 +19,11 @@ use serde::Serialize; #[derive(Serialize, Debug)] #[serde(untagged)] pub enum LambdaResponse { - ApiGatewayV2(ApiGatewayV2httpResponse), + #[cfg(any(feature = "apigw_rest", feature = "apigw_websockets"))] ApiGatewayV1(ApiGatewayProxyResponse), + #[cfg(feature = "apigw_http")] + ApiGatewayV2(ApiGatewayV2httpResponse), + #[cfg(feature = "alb")] Alb(AlbTargetGroupResponse), } @@ -37,6 +44,15 @@ impl LambdaResponse { let status_code = parts.status.as_u16(); match request_origin { + #[cfg(feature = "apigw_rest")] + RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { + body, + status_code: status_code as i64, + is_base64_encoded: Some(is_base64_encoded), + headers: headers.clone(), + multi_value_headers: headers, + }), + #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { // ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute, // so remove them from the headers. @@ -57,13 +73,7 @@ impl LambdaResponse { multi_value_headers: headers, }) } - RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { - body, - status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), - headers: headers.clone(), - multi_value_headers: headers, - }), + #[cfg(feature = "alb")] RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse { body, status_code: status_code as i64, @@ -76,6 +86,7 @@ impl LambdaResponse { parts.status.canonical_reason().unwrap_or_default() )), }), + #[cfg(feature = "apigw_websockets")] RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, status_code: status_code as i64, From 8e6ae59874d5f241f07b27659a490699d8b644d3 Mon Sep 17 00:00:00 2001 From: Hugo Bastien Date: Wed, 13 Jul 2022 19:00:02 -0400 Subject: [PATCH 109/394] feat(lambda-http): accept http_body::Body in responses (#491) * feat!(lambda-http): accept http_body::Body in responses Co-authored-by: Nicolas Moutschen Co-authored-by: David Calavera --- Makefile | 9 +- examples/http-basic-lambda/src/main.rs | 6 +- examples/http-cors/src/main.rs | 2 +- examples/http-query-parameters/src/main.rs | 2 +- examples/http-raw-path/src/main.rs | 4 +- examples/http-shared-resource/src/main.rs | 9 +- lambda-http/Cargo.toml | 3 + lambda-http/src/lib.rs | 29 +- lambda-http/src/request.rs | 7 +- lambda-http/src/response.rs | 259 ++++++++++++++---- lambda-integration-tests/src/bin/http-fn.rs | 7 +- .../src/bin/http-trait.rs | 6 +- 12 files changed, 270 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index d1eb2c99..cb00545c 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ INTEG_EXTENSIONS := extension-fn extension-trait logs-trait # Using musl to run extensions on both AL1 and AL2 INTEG_ARCH := x86_64-unknown-linux-musl +define uppercase +$(shell sed -r 's/(^|-)(\w)/\U\2/g' <<< $(1)) +endef + pr-check: cargo +1.54.0 check --all cargo +stable fmt --all -- --check @@ -15,7 +19,7 @@ pr-check: integration-tests: # Build Integration functions - cross build --release --target $(INTEG_ARCH) -p lambda_integration_tests + cargo zigbuild --release --target $(INTEG_ARCH) -p lambda_integration_tests rm -rf ./build mkdir -p ./build ${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_BUILD}, build-integration-function-${function}) @@ -37,7 +41,7 @@ build-integration-function-%: build-integration-extension-%: mkdir -p ./build/$*/extensions - cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$* + cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$(call uppercase,$*) invoke-integration-function-%: aws lambda invoke --function-name $$(aws cloudformation describe-stacks --stack-name $(INTEG_STACK_NAME) \ @@ -56,4 +60,3 @@ invoke-integration-api-%: curl -X POST -d '{"command": "hello"}' $(API_URL)/trait/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2-trait/post - \ No newline at end of file diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index cf15fec9..df15ae6c 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -1,10 +1,10 @@ -use lambda_http::{run, service_fn, Error, IntoResponse, Request, Response}; +use lambda_http::{run, service_fn, Body, Error, Request, Response}; /// This is the main body for the function. /// Write your code inside it. /// There are some code examples in the Runtime repository: /// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples -async fn function_handler(_event: Request) -> Result { +async fn function_handler(_event: Request) -> Result, Error> { // Extract some useful information from the request // Return something that implements IntoResponse. @@ -12,7 +12,7 @@ async fn function_handler(_event: Request) -> Result { let resp = Response::builder() .status(200) .header("content-type", "text/html") - .body("Hello AWS Lambda HTTP request") + .body("Hello AWS Lambda HTTP request".into()) .map_err(Box::new)?; Ok(resp) } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index 459dc6f3..7e1b21fa 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -29,7 +29,7 @@ async fn main() -> Result<(), Error> { async fn func(event: Request) -> Result, Error> { Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response(), + Some(first_name) => format!("Hello, {}!", first_name).into_response().await, _ => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 1e499948..03e4b939 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -7,7 +7,7 @@ use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Res async fn function_handler(event: Request) -> Result { // Extract some useful information from the request Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response(), + Some(first_name) => format!("Hello, {}!", first_name).into_response().await, _ => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index 1caafeab..f88b7b64 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -15,7 +15,9 @@ async fn main() -> Result<(), Error> { } async fn func(event: Request) -> Result { - let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response(); + let res = format!("The raw path for this request is: {}", event.raw_http_path()) + .into_response() + .await; Ok(res) } diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index 4491ac75..48bab471 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -29,9 +29,12 @@ async fn main() -> Result<(), Error> { // Define a closure here that makes use of the shared client. let handler_func_closure = move |event: Request| async move { Result::, Error>::Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => shared_client_ref - .response(event.lambda_context().request_id, first_name) - .into_response(), + Some(first_name) => { + shared_client_ref + .response(event.lambda_context().request_id, first_name) + .into_response() + .await + } _ => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 57a43f55..92955045 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -24,11 +24,14 @@ base64 = "0.13.0" bytes = "1" http = "0.2" http-body = "0.4" +hyper = "0.14" lambda_runtime = { path = "../lambda-runtime", version = "0.5" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" query_map = { version = "0.5", features = ["url-query"] } +mime = "0.3.16" +encoding_rs = "0.8.31" [dependencies.aws_lambda_events] version = "^0.6.3" diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 62de0f13..dd02ab4d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -67,6 +67,8 @@ extern crate maplit; pub use http::{self, Response}; use lambda_runtime::LambdaEvent; pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +use request::RequestFuture; +use response::ResponseFuture; pub mod ext; pub mod request; @@ -91,9 +93,9 @@ pub type Request = http::Request; /// /// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function. #[doc(hidden)] -pub struct TransformResponse<'a, R, E> { - request_origin: RequestOrigin, - fut: Pin> + 'a>>, +pub enum TransformResponse<'a, R, E> { + Request(RequestOrigin, RequestFuture<'a, R, E>), + Response(RequestOrigin, ResponseFuture), } impl<'a, R, E> Future for TransformResponse<'a, R, E> @@ -103,11 +105,19 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll { - match self.fut.as_mut().poll(cx) { - Poll::Ready(result) => Poll::Ready( - result.map(|resp| LambdaResponse::from_response(&self.request_origin, resp.into_response())), - ), - Poll::Pending => Poll::Pending, + 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, + }, } } } @@ -153,7 +163,8 @@ where 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_origin, fut } + + TransformResponse::Request(request_origin, fut) } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index c6327b7e..77eb5913 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -17,6 +17,8 @@ use http::header::HeaderName; use query_map::QueryMap; use serde::Deserialize; use serde_json::error::Error as JsonError; +use std::future::Future; +use std::pin::Pin; use std::{io::Read, mem}; /// Internal representation of an Lambda http event from @@ -56,9 +58,12 @@ impl LambdaRequest { } } +/// RequestFuture type +pub type RequestFuture<'a, R, E> = Pin> + 'a>>; + /// Represents the origin from which the lambda was requested from. #[doc(hidden)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RequestOrigin { /// API Gateway request origin #[cfg(feature = "apigw_rest")] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 0c46ae68..adfe3528 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -8,11 +8,20 @@ use aws_lambda_events::event::alb::AlbTargetGroupResponse; use aws_lambda_events::event::apigw::ApiGatewayProxyResponse; #[cfg(feature = "apigw_http")] use aws_lambda_events::event::apigw::ApiGatewayV2httpResponse; +use encoding_rs::Encoding; +use http::header::CONTENT_ENCODING; +use http::HeaderMap; use http::{ header::{CONTENT_TYPE, SET_COOKIE}, Response, }; +use http_body::Body as HttpBody; +use hyper::body::to_bytes; +use mime::{Mime, CHARSET}; use serde::Serialize; +use std::borrow::Cow; +use std::future::ready; +use std::{fmt, future::Future, pin::Pin}; /// Representation of Lambda response #[doc(hidden)] @@ -27,14 +36,11 @@ pub enum LambdaResponse { Alb(AlbTargetGroupResponse), } -/// tranformation from http type to internal type +/// Transformation from http type to internal type impl LambdaResponse { - pub(crate) fn from_response(request_origin: &RequestOrigin, value: Response) -> Self - where - T: Into, - { + pub(crate) fn from_response(request_origin: &RequestOrigin, value: Response) -> Self { let (parts, bod) = value.into_parts(); - let (is_base64_encoded, body) = match bod.into() { + let (is_base64_encoded, body) = match bod { Body::Empty => (false, None), b @ Body::Text(_) => (false, Some(b)), b @ Body::Binary(_) => (true, Some(b)), @@ -98,71 +104,152 @@ impl LambdaResponse { } } -/// A conversion of self into a `Response` for various types. +/// Trait for generating responses /// -/// Implementations for `Response where B: Into`, -/// `B where B: Into` and `serde_json::Value` are provided -/// by default. -/// -/// # Example -/// -/// ```rust -/// use lambda_http::{Body, IntoResponse, Response}; -/// -/// assert_eq!( -/// "hello".into_response().body(), -/// Response::new(Body::from("hello")).body() -/// ); -/// ``` +/// Types that implement this trait can be used as return types for handler functions. pub trait IntoResponse { - /// Return a translation of `self` into a `Response` - fn into_response(self) -> Response; + /// Transform into a Response Future + fn into_response(self) -> ResponseFuture; } impl IntoResponse for Response where - B: Into, + B: ConvertBody + 'static, { - fn into_response(self) -> Response { + fn into_response(self) -> ResponseFuture { let (parts, body) = self.into_parts(); - Response::from_parts(parts, body.into()) + let headers = parts.headers.clone(); + + let fut = async { Response::from_parts(parts, body.convert(headers).await) }; + + Box::pin(fut) } } impl IntoResponse for String { - fn into_response(self) -> Response { - Response::new(Body::from(self)) + fn into_response(self) -> ResponseFuture { + Box::pin(ready(Response::new(Body::from(self)))) } } impl IntoResponse for &str { - fn into_response(self) -> Response { - Response::new(Body::from(self)) + fn into_response(self) -> ResponseFuture { + Box::pin(ready(Response::new(Body::from(self)))) + } +} + +impl IntoResponse for &[u8] { + fn into_response(self) -> ResponseFuture { + Box::pin(ready(Response::new(Body::from(self)))) + } +} + +impl IntoResponse for Vec { + fn into_response(self) -> ResponseFuture { + Box::pin(ready(Response::new(Body::from(self)))) } } impl IntoResponse for serde_json::Value { - fn into_response(self) -> Response { - Response::builder() - .header(CONTENT_TYPE, "application/json") - .body( - serde_json::to_string(&self) - .expect("unable to serialize serde_json::Value") - .into(), - ) - .expect("unable to build http::Response") + fn into_response(self) -> ResponseFuture { + Box::pin(async move { + Response::builder() + .header(CONTENT_TYPE, "application/json") + .body( + serde_json::to_string(&self) + .expect("unable to serialize serde_json::Value") + .into(), + ) + .expect("unable to build http::Response") + }) } } +pub type ResponseFuture = Pin>>>; + +pub trait ConvertBody { + fn convert(self, parts: HeaderMap) -> BodyFuture; +} + +impl ConvertBody for B +where + B: HttpBody + Unpin + 'static, + B::Error: fmt::Debug, +{ + fn convert(self, headers: HeaderMap) -> BodyFuture { + if headers.get(CONTENT_ENCODING).is_some() { + return convert_to_binary(self); + } + + let content_type = if let Some(value) = headers.get(http::header::CONTENT_TYPE) { + value.to_str().unwrap_or_default() + } else { + // Content-Type and Content-Encoding not set, passthrough as utf8 text + return convert_to_text(self, "utf-8"); + }; + + if content_type.starts_with("text") + || content_type.starts_with("application/json") + || content_type.starts_with("application/javascript") + || content_type.starts_with("application/xml") + { + return convert_to_text(self, content_type); + } + + convert_to_binary(self) + } +} + +fn convert_to_binary(body: B) -> BodyFuture +where + B: HttpBody + Unpin + 'static, + B::Error: fmt::Debug, +{ + Box::pin(async move { Body::from(to_bytes(body).await.expect("unable to read bytes from body").to_vec()) }) +} + +fn convert_to_text(body: B, content_type: &str) -> BodyFuture +where + B: HttpBody + Unpin + 'static, + B::Error: fmt::Debug, +{ + let mime_type = content_type.parse::(); + + let encoding = match mime_type.as_ref() { + Ok(mime) => mime.get_param(CHARSET).unwrap_or(mime::UTF_8), + Err(_) => mime::UTF_8, + }; + + let label = encoding.as_ref().as_bytes(); + let encoding = Encoding::for_label(label).unwrap_or(encoding_rs::UTF_8); + + // assumes utf-8 + Box::pin(async move { + let bytes = to_bytes(body).await.expect("unable to read bytes from body"); + let (content, _, _) = encoding.decode(&bytes); + + match content { + Cow::Borrowed(content) => Body::from(content), + Cow::Owned(content) => Body::from(content), + } + }) +} + +pub type BodyFuture = Pin>>; + #[cfg(test)] mod tests { use super::{Body, IntoResponse, LambdaResponse, RequestOrigin}; - use http::{header::CONTENT_TYPE, Response}; + use http::{ + header::{CONTENT_ENCODING, CONTENT_TYPE}, + Response, + }; + use hyper::Body as HyperBody; use serde_json::{self, json}; - #[test] - fn json_into_response() { - let response = json!({ "hello": "lambda"}).into_response(); + #[tokio::test] + async fn json_into_response() { + let response = json!({ "hello": "lambda"}).into_response().await; match response.body() { Body::Text(json) => assert_eq!(json, r#"{"hello":"lambda"}"#), _ => panic!("invalid body"), @@ -176,15 +263,95 @@ mod tests { ) } - #[test] - fn text_into_response() { - let response = "text".into_response(); + #[tokio::test] + async fn text_into_response() { + let response = "text".into_response().await; match response.body() { Body::Text(text) => assert_eq!(text, "text"), _ => panic!("invalid body"), } } + #[tokio::test] + async fn bytes_into_response() { + let response = "text".as_bytes().into_response().await; + match response.body() { + Body::Binary(data) => assert_eq!(data, "text".as_bytes()), + _ => panic!("invalid body"), + } + } + + #[tokio::test] + async fn content_encoding_header() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_ENCODING, "gzip") + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{"content-encoding":"gzip"},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# + ) + } + + #[tokio::test] + async fn content_type_header() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_TYPE, "application/json") + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + ) + } + + #[tokio::test] + async fn charset_content_type_header() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_TYPE, "application/json; charset=utf-16") + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{"content-type":"application/json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + ) + } + + #[tokio::test] + async fn content_headers_unset() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + ) + } + #[test] fn serialize_multi_value_headers() { let res = LambdaResponse::from_response( diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index b411b77f..4170d29f 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -1,11 +1,14 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{service_fn, Body, Error, IntoResponse, Request, RequestExt, Response}; use tracing::info; async fn handler(event: Request) -> Result { let _context = event.lambda_context(); info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); - Ok(Response::builder().status(200).body("Hello, world!").unwrap()) + Ok(Response::builder() + .status(200) + .body(Body::from("Hello, world!")) + .unwrap()) } #[tokio::main] diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs index 091aec8e..67cc9fc5 100644 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -1,4 +1,4 @@ -use lambda_http::{Error, Request, RequestExt, Response, Service}; +use lambda_http::{Body, Error, Request, RequestExt, Response, Service}; use std::{ future::{ready, Future}, pin::Pin, @@ -13,7 +13,7 @@ struct MyHandler { impl Service for MyHandler { type Error = Error; type Future = Pin> + Send>>; - type Response = Response<&'static str>; + type Response = Response; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { core::task::Poll::Ready(Ok(())) @@ -25,7 +25,7 @@ impl Service for MyHandler { info!("[http-trait] Lambda context: {:?}", request.lambda_context()); Box::pin(ready(Ok(Response::builder() .status(200) - .body("Hello, World!") + .body(Body::from("Hello, World!")) .unwrap()))) } } From dcebdf005ca229fe34b54a4a756ae01ff56ec16b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 16 Jul 2022 22:12:07 -0700 Subject: [PATCH 110/394] Add Tower TraceLayer examples (#500) This example shows how to print a log for every incoming request and outgoing response automatically. Signed-off-by: David Calavera --- examples/http-tower-trace/Cargo.toml | 19 +++++++++++++++++++ examples/http-tower-trace/src/main.rs | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 examples/http-tower-trace/Cargo.toml create mode 100644 examples/http-tower-trace/src/main.rs diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml new file mode 100644 index 00000000..2b8f7a60 --- /dev/null +++ b/examples/http-tower-trace/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "http-tower-trace" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = "0.5.1" +tokio = { version = "1", features = ["macros"] } +tower-http = { version = "0.3.4", features = ["trace"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs new file mode 100644 index 00000000..ec7020c1 --- /dev/null +++ b/examples/http-tower-trace/src/main.rs @@ -0,0 +1,22 @@ +use lambda_http::{run, tower::ServiceBuilder, Error}; +use lambda_http::{Request, Response}; +use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; +use tracing::Level; + +async fn handler(_req: Request) -> Result, Error> { + Ok(Response::new("Success".into())) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt().without_time().init(); + + let layer = TraceLayer::new_for_http() + .on_request(DefaultOnRequest::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)); + + let service = ServiceBuilder::new().layer(layer).service_fn(handler); + + run(service).await?; + Ok(()) +} From bb241992287105837efd5211a6a1588331a716ba Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 17 Jul 2022 21:37:13 -0700 Subject: [PATCH 111/394] Update Rust edition to 2021 (#501) The AWS SDK already made the upgrade to 2021. We've had the `warn_2018_idioms` lint flag enabled for a while. Since we're making a breaking change in the next version of lambda_http, I thought it could be a good opportunity to get this in, and bump the version to all the crates at the same time. Signed-off-by: David Calavera --- .github/workflows/build.yml | 20 +------------------- README.md | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 2 +- lambda-http/src/lib.rs | 4 ++-- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 7 files changed, 8 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 570224eb..a39c9d55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - ubuntu-latest - macOS-latest rust: - - "1.54.0" # Current MSRV + - "1.58.0" # Current MSRV - stable - beta - nightly @@ -22,24 +22,6 @@ jobs: include: - rust: nightly allow_failure: true - # exclude: - # - os: macOS-latest - # target: x86_64-unknown-linux-musl - # - os: ubuntu-latest - # rust: 1.40.0 - # target: x86_64-unknown-linux-musl - # - os: ubuntu-latest - # rust: beta - # target: x86_64-unknown-linux-musl - # - os: ubuntu-latest - # rust: nightly - # target: x86_64-unknown-linux-musl - # - os: macOS-latest - # rust: 1.40.0 - # - os: macOS-latest - # rust: beta - # - os: macOS-latest - # rust: nightly env: RUST_BACKTRACE: 1 steps: diff --git a/README.md b/README.md index 46f87e5c..a18d62a7 100644 --- a/README.md +++ b/README.md @@ -398,7 +398,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.54, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.58, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index d7b0102a..bd8ef428 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lambda-extension" version = "0.5.0" -edition = "2018" +edition = "2021" authors = ["David Calavera "] description = "AWS Lambda Extension API" license = "Apache-2.0" diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 92955045..be580699 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -2,7 +2,7 @@ name = "lambda_http" version = "0.5.2" authors = ["Doug Tangren"] -edition = "2018" +edition = "2021" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index dd02ab4d..3581002f 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -1,4 +1,4 @@ -#![warn(missing_docs)] +#![warn(missing_docs, rust_2018_idioms)] //#![deny(warnings)] //! Enriches the `lambda` crate with [`http`](https://github.com/hyperium/http) //! 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. @@ -104,7 +104,7 @@ where { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll { match *self { TransformResponse::Request(ref mut origin, ref mut request) => match request.as_mut().poll(cx) { Poll::Ready(Ok(resp)) => { diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 8a8a29b2..7515b734 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lambda_runtime_api_client" version = "0.5.0" -edition = "2018" +edition = "2021" authors = ["David Calavera "] description = "AWS Lambda Runtime interaction API" license = "Apache-2.0" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1e3d2d6e..0289920d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -3,7 +3,7 @@ name = "lambda_runtime" version = "0.5.1" authors = ["David Barsky "] description = "AWS Lambda Runtime" -edition = "2018" +edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] From 336e55268f3f2a90560ef12ef3f7efe7fd6d354c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Jul 2022 19:38:47 -0700 Subject: [PATCH 112/394] Bump crate versions to 0.6.0 (#502) Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 4 ++-- lambda-integration-tests/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index bd8ef428..bee81196 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.5.0" +version = "0.6.0" edition = "2021" authors = ["David Calavera "] description = "AWS Lambda Extension API" @@ -16,7 +16,7 @@ bytes = "1.0" chrono = { version = "0.4", features = ["serde"] } http = "0.2" hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } -lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index be580699..0f7a53a6 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.5.2" +version = "0.6.0" authors = ["Doug Tangren"] edition = "2021" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -25,7 +25,7 @@ bytes = "1" http = "0.2" http-body = "0.4" hyper = "0.14" -lambda_runtime = { path = "../lambda-runtime", version = "0.5" } +lambda_runtime = { path = "../lambda-runtime", version = "0.6" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 4724b6a1..bb4a6aea 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -13,9 +13,9 @@ readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lambda_http = { path = "../lambda-http", version = "0.5" } -lambda_runtime = { path = "../lambda-runtime", version = "0.5" } -lambda-extension = { path = "../lambda-extension", version = "0.5" } +lambda_http = { path = "../lambda-http", version = "0.6" } +lambda_runtime = { path = "../lambda-runtime", version = "0.6" } +lambda-extension = { path = "../lambda-extension", version = "0.6" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 7515b734..5cc8c2e8 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.5.0" +version = "0.6.0" edition = "2021" authors = ["David Calavera "] description = "AWS Lambda Runtime interaction API" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 0289920d..3cfb6f56 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.5.1" +version = "0.6.0" authors = ["David Barsky "] description = "AWS Lambda Runtime" edition = "2021" @@ -26,5 +26,5 @@ async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.5", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } From 71565d6883dd51701e6952ed93c72bb583f5e527 Mon Sep 17 00:00:00 2001 From: Johan Smits Date: Tue, 2 Aug 2022 18:14:19 +0200 Subject: [PATCH 113/394] Fix test example in Readme (#507) Types are private and cannot be used. Also the handler is async. We have to `await` the result. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a18d62a7..6aec7ec1 100644 --- a/README.md +++ b/README.md @@ -297,11 +297,11 @@ your text fixtures into the structures, and call your handler directly: #[test] fn test_my_lambda_handler() { let input = serde_json::from_str("{\"command\": \"Say Hi!\"}").expect("failed to parse event"); - let context = lambda_runtime::types::Context::default(); + let context = lambda_runtime::Context::default(); - let event = lambda_runtime::types::LambdaEvent::new(input, context); + let event = lambda_runtime::LambdaEvent::new(input, context); - my_lambda_handler(event).expect("failed to handle event"); + my_lambda_handler(event).await.expect("failed to handle event"); } ``` @@ -316,7 +316,7 @@ fn test_my_lambda_handler() { let request = lambda_http::request::from_str(input) .expect("failed to create request"); - let response = my_lambda_handler(request).expect("failed to handle request"); + let response = my_lambda_handler(request).await.expect("failed to handle request"); } ``` From 9119e5d9d8fb4366685b4efc7b7c994f17c9660d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 12 Aug 2022 15:07:00 -0700 Subject: [PATCH 114/394] Update links to cargo-lambda documentation (#510) The documentation is not in the project's readme anymore. These changes redirect people to the right places. --- README.md | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6aec7ec1..2b00dbe8 100644 --- a/README.md +++ b/README.md @@ -11,26 +11,35 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor ## Getting started -The easiest way to start writing Lambda functions with Rust is by using Cargo-Lambda. This Cargo subcommand provides several commands to help you in your journey with Rust on AWS Lambda. +The easiest way to start writing Lambda functions with Rust is by using Cargo Lambda. This Cargo subcommand provides several commands to help you in your journey with Rust on AWS Lambda. -You can install `cargo-lambda` with a package manager like Homebrew: +The preferred way to install Cargo Lambda is by using a package manager. + +1- Use Homebrew on [MacOS](https://brew.sh/): ```bash brew tap cargo-lambda/cargo-lambda brew install cargo-lambda ``` -Or by compiling it from source: +2- Use [Scoop](https://scoop.sh/) on Windows: + +```bash +scoop bucket add cargo-lambda https://github.com/cargo-lambda/scoop-cargo-lambda +scoop install cargo-lambda/cargo-lambda +``` + +Or PiP on any system with Python 3 installed: ```bash -cargo install cargo-lambda +pip3 install cargo-lambda ``` -See other installation options in [the cargo-lambda documentation](https://github.com/cargo-lambda/cargo-lambda#installation). +See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). ### Your first function -To create your first function, run `cargo-lambda` with the subcomand `new`. This command will generate a Rust package with the initial source code for your function: +To create your first function, run Cargo Lambda with the [subcomand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: ``` cargo lambda new YOUR_FUNCTION_NAME @@ -61,7 +70,7 @@ async fn func(event: LambdaEvent) -> Result { ## Building and deploying your Lambda functions -If you already have `cargo-lambda` installed in your machine, run the next command to build your function: +If you already have Cargo Lambda installed in your machine, run the next command to build your function: ``` cargo lambda build --release @@ -72,7 +81,7 @@ There are other ways of building your function: manually with the AWS CLI, with ### 1. Cross-compiling your Lambda functions -By default, `cargo-lambda` builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. +By default, Cargo Lambda builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. #### 1.2. Build your Lambda functions @@ -86,7 +95,7 @@ cargo lambda build --release --arm64 __Amazon Linux 1__ -Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with `cargo-lambda`, you can specify a different version of glibc. +Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with Cargo Lambda, you can specify a different version of glibc. If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: @@ -101,9 +110,9 @@ For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-cus You can find the `bootstrap` binary for your function under the `target/lambda` directory. -#### 2.2. Deploying with cargo-lambda +#### 2.2. Deploying with Cargo Lambda -You can use `cargo-lambda` for simple function deployments. Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: +Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: ```bash cargo lambda deploy \ @@ -124,9 +133,9 @@ cargo lambda deploy \ ``` > **info** -> See other deployment options in [the cargo-lambda documentation](https://github.com/cargo-lambda/cargo-lambda#deploy). +> See other deployment options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/commands/deploy.html). -You can test the function with `cargo-lambda`'s invoke subcommand: +You can test the function with the [invoke subcommand](https://www.cargo-lambda.info/commands/invoke.html): ```bash cargo lambda invoke --remote \ @@ -137,7 +146,7 @@ cargo lambda invoke --remote \ #### 2.2. Deploying with the AWS CLI -You can also use the AWS CLI to deploy your Rust functions. First, you will need to create a ZIP archive of your function. Cargo-lambda can do that for you automatically when it builds your binary if you add the `output-format` flag: +You can also use the AWS CLI to deploy your Rust functions. First, you will need to create a ZIP archive of your function. Cargo Lambda can do that for you automatically when it builds your binary if you add the `output-format` flag: ```bash cargo lambda build --release --arm64 --output-format zip @@ -322,9 +331,9 @@ fn test_my_lambda_handler() { ### Cargo Lambda -[Cargo Lambda](https://crates.io/crates/cargo-lambda) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. +[Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. -You can read more about how [cargo lambda start](https://github.com/calavera/cargo-lambda#start) and [cargo lambda invoke](https://github.com/calavera/cargo-lambda#invoke) work on the [project's README](https://github.com/calavera/cargo-lambda#cargo-lambda). +You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/watch.html) work on the [project's documentation page](https://www.cargo-lambda.info). ### Lambda Debug Proxy From 62660df19b26f2f843279874f7f904312c78c652 Mon Sep 17 00:00:00 2001 From: greenwoodcm Date: Fri, 12 Aug 2022 15:07:42 -0700 Subject: [PATCH 115/394] fix lambda-extension README (#511) the current example code doesn't work if you copy/paste it into a repo created with `cargo lambda new`, even if you change the `Cargo.toml` to pull in the `lambda-extension` crate. The default template doesn't bring in `tracing` with the `ansi` feature. This updates the README to have code that's more in line with the default template. --- lambda-extension/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda-extension/README.md b/lambda-extension/README.md index d26e62b7..0f132d75 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -29,7 +29,9 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - .with_ansi(false) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); From b88e9ab307cdccd537f6f0660cbcc48573ab1d7c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 13 Aug 2022 09:43:44 -0700 Subject: [PATCH 116/394] Fix clippy errors. (#512) Signed-off-by: David Calavera Signed-off-by: David Calavera --- examples/basic-error-handling/src/main.rs | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 2 +- lambda-http/src/ext.rs | 10 +++++----- lambda-http/src/request.rs | 2 +- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 3 +-- lambda-runtime/src/lib.rs | 2 +- lambda-runtime/src/requests.rs | 4 ++-- lambda-runtime/src/types.rs | 22 +++++++++++----------- 10 files changed, 25 insertions(+), 26 deletions(-) diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index ee721f95..8d274849 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -12,7 +12,7 @@ struct Request { } /// Event types that tell our Lambda what to do do. -#[derive(Deserialize, PartialEq)] +#[derive(Deserialize, Eq, PartialEq)] enum EventType { Response, ExternalError, diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index bee81196..34d4518a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -15,7 +15,7 @@ async-stream = "0.3" bytes = "1.0" chrono = { version = "0.4", features = ["serde"] } http = "0.2" -hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] } +hyper = { version = "0.14.20", features = ["http1", "client", "server", "stream", "runtime"] } lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 0f7a53a6..092a29d3 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.13.0" bytes = "1" http = "0.2" http-body = "0.4" -hyper = "0.14" +hyper = "0.14.20" lambda_runtime = { path = "../lambda-runtime", version = "0.6" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index b53cd851..0cd85982 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -328,7 +328,7 @@ mod tests { #[test] fn requests_have_form_post_parsable_payloads() { - #[derive(Deserialize, PartialEq, Debug)] + #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, baz: usize, @@ -349,7 +349,7 @@ mod tests { #[test] fn requests_have_json_parseable_payloads() { - #[derive(Deserialize, PartialEq, Debug)] + #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, baz: usize, @@ -370,7 +370,7 @@ mod tests { #[test] fn requests_match_form_post_content_type_with_charset() { - #[derive(Deserialize, PartialEq, Debug)] + #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, baz: usize, @@ -391,7 +391,7 @@ mod tests { #[test] fn requests_match_json_content_type_with_charset() { - #[derive(Deserialize, PartialEq, Debug)] + #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, baz: usize, @@ -412,7 +412,7 @@ mod tests { #[test] fn requests_omiting_content_types_do_not_support_parseable_payloads() { - #[derive(Deserialize, PartialEq, Debug)] + #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, baz: usize, diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 77eb5913..fba1935c 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -376,7 +376,7 @@ pub enum RequestContext { } /// Converts LambdaRequest types into `http::Request` types -impl<'a> From for http::Request { +impl From for http::Request { fn from(value: LambdaRequest) -> Self { match value { #[cfg(feature = "apigw_rest")] diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 5cc8c2e8..c3bbd754 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -12,6 +12,6 @@ readme = "README.md" [dependencies] http = "0.2" -hyper = { version = "0.14", features = ["http1", "client", "stream", "tcp"] } +hyper = { version = "0.14.20", features = ["http1", "client", "stream", "tcp"] } tower-service = "0.3" tokio = { version = "1.0", features = ["io-util"] } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 3cfb6f56..44bb4218 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -17,7 +17,7 @@ simulated = [] [dependencies] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } # Hyper requires the `server` feature to work on nightly -hyper = { version = "0.14", features = ["http1", "client", "stream", "server"] } +hyper = { version = "0.14.20", features = ["http1", "client", "stream", "server"] } serde = { version = "1", features = ["derive"] } serde_json = "^1" bytes = "1.0" @@ -27,4 +27,3 @@ tracing = { version = "0.1", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } - diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 35f3c82f..b23c3cd2 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -31,7 +31,7 @@ pub use types::{Context, LambdaEvent}; pub type Error = lambda_runtime_api_client::Error; /// Configuration derived from environment variables. -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Config { /// The name of the function. pub function_name: String, diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 4d033614..97fc7e4d 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -14,7 +14,7 @@ pub(crate) trait IntoResponse { } // /runtime/invocation/next -#[derive(Debug, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct NextEventRequest; impl IntoRequest for NextEventRequest { @@ -27,7 +27,7 @@ impl IntoRequest for NextEventRequest { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub struct NextEventResponse<'a> { // lambda-runtime-aws-request-id pub request_id: &'a str, diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index ee71ba1c..b9e36702 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -3,7 +3,7 @@ use http::{HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, convert::TryFrom}; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Diagnostic { pub(crate) error_type: String, @@ -28,33 +28,33 @@ fn round_trip_lambda_error() -> Result<(), Error> { /// The request ID, which identifies the request that triggered the function invocation. This header /// tracks the invocation within the Lambda control plane. The request ID is used to specify completion /// of a given invocation. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct RequestId(pub String); /// The date that the function times out in Unix time milliseconds. For example, `1542409706888`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct InvocationDeadline(pub u64); /// The ARN of the Lambda function, version, or alias that is specified in the invocation. /// For instance, `arn:aws:lambda:us-east-2:123456789012:function:custom-runtime`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct FunctionArn(pub String); /// The AWS X-Ray Tracing header. For more information, /// please see [AWS' documentation](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct XRayTraceId(pub String); /// For invocations from the AWS Mobile SDK contains data about client application and device. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] struct MobileClientContext(String); /// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] struct MobileClientIdentity(String); /// Client context sent by the AWS Mobile SDK. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ClientContext { /// Information about the mobile application invoking the function. #[serde(default)] @@ -68,7 +68,7 @@ pub struct ClientContext { } /// AWS Mobile SDK client fields. -#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Default, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ClientApplication { /// The mobile app installation id @@ -84,7 +84,7 @@ pub struct ClientApplication { } /// Cognito identity information sent with the event -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct CognitoIdentity { /// The unique identity id for the Cognito credentials invoking the function. pub identity_id: String, @@ -96,7 +96,7 @@ pub struct CognitoIdentity { /// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) /// and the headers returned by the poll request to the Runtime APIs. #[non_exhaustive] -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] pub struct Context { /// The AWS request ID generated by the Lambda service. pub request_id: String, From a20193dab7fd5b16df500677ecf031bef42fdab0 Mon Sep 17 00:00:00 2001 From: greenwoodcm Date: Mon, 15 Aug 2022 12:51:57 -0700 Subject: [PATCH 117/394] fix clippy warnings in unit tests (#513) not sure if there's a way for `cargo clippy` to lint the tests by default... --- lambda-http/src/request.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index fba1935c..c7ba1e20 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -472,10 +472,7 @@ mod tests { // Ensure this is an APIGWv2 request let req_context = req.request_context(); assert!( - match req_context { - RequestContext::ApiGatewayV2(_) => true, - _ => false, - }, + matches!(req_context, RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {:?}", req_context ); @@ -507,10 +504,7 @@ mod tests { // Ensure this is an APIGWv2 request let req_context = req.request_context(); assert!( - match req_context { - RequestContext::ApiGatewayV2(_) => true, - _ => false, - }, + matches!(req_context, RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {:?}", req_context ); @@ -539,10 +533,7 @@ mod tests { // Ensure this is an APIGW request let req_context = req.request_context(); assert!( - match req_context { - RequestContext::ApiGatewayV1(_) => true, - _ => false, - }, + matches!(req_context, RequestContext::ApiGatewayV1(_)), "expected ApiGateway context, got {:?}", req_context ); @@ -570,10 +561,7 @@ mod tests { // Ensure this is an ALB request let req_context = req.request_context(); assert!( - match req_context { - RequestContext::Alb(_) => true, - _ => false, - }, + matches!(req_context, RequestContext::Alb(_)), "expected Alb context, got {:?}", req_context ); From f77b0449664461486476165e3ddc9a443be4ecb2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 22 Aug 2022 10:26:49 -0700 Subject: [PATCH 118/394] Add message in closed issues (#514) --- .github/workflows/closed-issue-message.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/closed-issue-message.yml diff --git a/.github/workflows/closed-issue-message.yml b/.github/workflows/closed-issue-message.yml new file mode 100644 index 00000000..f295d310 --- /dev/null +++ b/.github/workflows/closed-issue-message.yml @@ -0,0 +1,17 @@ +name: Closed Issue Message +on: + issues: + types: [closed] +jobs: + auto_comment: + runs-on: ubuntu-latest + steps: + - uses: aws-actions/closed-issue-message@v1 + with: + # These inputs are both required + repo-token: "${{ secrets.GITHUB_TOKEN }}" + message: | + ### ⚠️COMMENT VISIBILITY WARNING⚠️ + Comments on closed issues are hard for the maintainers of this repository to see. + If you need more assistance, please open a new issue that references this one. + If you wish to keep having a conversation with other community members under this issue feel free to do so. From fd2ea235f4adb166422ca5b612c4a1a42a626daa Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 28 Aug 2022 10:12:21 -0700 Subject: [PATCH 119/394] Fix paths with spaces in HTTP requests. (#516) * Fix paths with spaces in HTTP requests. Encode the characters not allowed in URLs before creating the request. Signed-off-by: David Calavera * Cleanup parameters and comments. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 1 + lambda-http/src/request.rs | 202 +++++++++--------- .../data/apigw_request_path_with_space.json | 57 +++++ 3 files changed, 160 insertions(+), 100 deletions(-) create mode 100644 lambda-http/tests/data/apigw_request_path_with_space.json diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 092a29d3..f919a277 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -32,6 +32,7 @@ serde_urlencoded = "0.7.0" query_map = { version = "0.5", features = ["url-query"] } mime = "0.3.16" encoding_rs = "0.8.31" +url = "2.2.2" [dependencies.aws_lambda_events] version = "^0.6.3" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index c7ba1e20..27d54cf2 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -14,12 +14,14 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; use aws_lambda_events::encodings::Body; use http::header::HeaderName; +use http::HeaderMap; use query_map::QueryMap; use serde::Deserialize; use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; use std::{io::Read, mem}; +use url::Url; /// Internal representation of an Lambda http event from /// ALB, API Gateway REST and HTTP API proxy event perspectives @@ -82,7 +84,13 @@ pub enum RequestOrigin { #[cfg(feature = "apigw_http")] fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request { let http_method = ag.request_context.http.method.clone(); + let host = ag + .headers + .get(http::header::HOST) + .and_then(|s| s.to_str().ok()) + .or(ag.request_context.domain_name.as_deref()); let raw_path = ag.raw_path.unwrap_or_default(); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); // don't use the query_string_parameters from API GW v2 to // populate the QueryStringParameters extension because @@ -95,32 +103,14 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request path, - Some(host) => { - let scheme = ag - .headers - .get(x_forwarded_proto()) - .and_then(|s| s.to_str().ok()) - .unwrap_or("https"); - format!("{}://{}{}", scheme, host, path) - } - }; - if let Some(query) = ag.raw_query_string { - url.push('?'); - url.push_str(&query); - } - url - }) + .uri(uri) .extension(RawHttpPath(raw_path)) .extension(QueryStringParameters(query_string_parameters)) .extension(PathParameters(QueryMap::from(ag.path_parameters))) @@ -154,34 +144,21 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request { let http_method = ag.http_method; + let host = ag + .headers + .get(http::header::HOST) + .and_then(|s| s.to_str().ok()) + .or(ag.request_context.domain_name.as_deref()); let raw_path = ag.path.unwrap_or_default(); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); let builder = http::Request::builder() - .uri({ - let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); - let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); - - let mut url = match host { - None => path, - Some(host) => { - let scheme = ag - .headers - .get(x_forwarded_proto()) - .and_then(|s| s.to_str().ok()) - .unwrap_or("https"); - format!("{}://{}{}", scheme, host, path) - } - }; - - if !ag.multi_value_query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&ag.multi_value_query_string_parameters.to_query_string()); - } else if !ag.query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&ag.query_string_parameters.to_query_string()); - } - url - }) + .uri(build_request_uri( + &path, + &ag.headers, + host, + Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)), + )) .extension(RawHttpPath(raw_path)) // multi-valued query string parameters are always a super // set of singly valued query string parameters, @@ -221,34 +198,16 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { #[cfg(feature = "alb")] fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let http_method = alb.http_method; + let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); let raw_path = alb.path.unwrap_or_default(); let builder = http::Request::builder() - .uri({ - let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); - - let mut url = match host { - None => raw_path.clone(), - Some(host) => { - let scheme = alb - .headers - .get(x_forwarded_proto()) - .and_then(|s| s.to_str().ok()) - .unwrap_or("https"); - format!("{}://{}{}", scheme, host, &raw_path) - } - }; - - if !alb.multi_value_query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&alb.multi_value_query_string_parameters.to_query_string()); - } else if !alb.query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&alb.query_string_parameters.to_query_string()); - } - - url - }) + .uri(build_request_uri( + &raw_path, + &alb.headers, + host, + Some((&alb.multi_value_query_string_parameters, &alb.query_string_parameters)), + )) .extension(RawHttpPath(raw_path)) // multi valued query string parameters are always a super // set of singly valued query string parameters, @@ -287,32 +246,20 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { #[cfg(feature = "apigw_websockets")] fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request { let http_method = ag.http_method; + let host = ag + .headers + .get(http::header::HOST) + .and_then(|s| s.to_str().ok()) + .or(ag.request_context.domain_name.as_deref()); + let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + let builder = http::Request::builder() - .uri({ - let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); - let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); - - let mut url = match host { - None => path, - Some(host) => { - let scheme = ag - .headers - .get(x_forwarded_proto()) - .and_then(|s| s.to_str().ok()) - .unwrap_or("https"); - format!("{}://{}{}", scheme, host, path) - } - }; - - if !ag.multi_value_query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&ag.multi_value_query_string_parameters.to_query_string()); - } else if !ag.query_string_parameters.is_empty() { - url.push('?'); - url.push_str(&ag.query_string_parameters.to_query_string()); - } - url - }) + .uri(build_request_uri( + &path, + &ag.headers, + host, + Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)), + )) // multi-valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred @@ -438,6 +385,40 @@ fn x_forwarded_proto() -> HeaderName { HeaderName::from_static("x-forwarded-proto") } +fn build_request_uri( + path: &str, + headers: &HeaderMap, + host: Option<&str>, + queries: Option<(&QueryMap, &QueryMap)>, +) -> String { + let mut url = match host { + None => { + let rel_url = Url::parse(&format!("http://localhost{}", path)).unwrap(); + rel_url.path().to_string() + } + Some(host) => { + let scheme = headers + .get(x_forwarded_proto()) + .and_then(|s| s.to_str().ok()) + .unwrap_or("https"); + let url = format!("{}://{}{}", scheme, host, path); + Url::parse(&url).unwrap().to_string() + } + }; + + if let Some((mv, sv)) = queries { + if !mv.is_empty() { + url.push('?'); + url.push_str(&mv.to_query_string()); + } else if !sv.is_empty() { + url.push('?'); + url.push_str(&sv.to_query_string()); + } + } + + url +} + #[cfg(test)] mod tests { use super::*; @@ -666,4 +647,25 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), "/v1/health/"); } + + #[test] + fn deserialize_apigw_path_with_space() { + // generated from ALB health checks + let input = include_str!("../tests/data/apigw_request_path_with_space.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {:?} given {}", + result, + input + ); + let req = result.expect("failed to parse request"); + assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path-with%20space?parameter1=value1¶meter1=value2¶meter2=value"); + } + + #[test] + fn parse_paths_with_spaces() { + let url = build_request_uri("/path with spaces/and multiple segments", &HeaderMap::new(), None, None); + assert_eq!("/path%20with%20spaces/and%20multiple%20segments", url); + } } diff --git a/lambda-http/tests/data/apigw_request_path_with_space.json b/lambda-http/tests/data/apigw_request_path_with_space.json new file mode 100644 index 00000000..53f82382 --- /dev/null +++ b/lambda-http/tests/data/apigw_request_path_with_space.json @@ -0,0 +1,57 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path-with space", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1=value1", + "cookie2=value2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/my/path-with space", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "$default", + "stage": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "body": "Hello from Lambda", + "pathParameters": { + "parameter1": "value1" + }, + "isBase64Encoded": false, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + } +} \ No newline at end of file From 756dfc4664b2418cb027f63426077755a12bba21 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 30 Aug 2022 20:28:50 -0700 Subject: [PATCH 120/394] Report error when we cannot deserialize the payload. (#520) Instead of panic, capture the error, and report it to the Lambda api. This is more friendly to operate. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 89 ++++++++++++++++++---------------- lambda-runtime/src/requests.rs | 18 +++++-- lambda-runtime/src/types.rs | 37 +++++++------- 3 files changed, 80 insertions(+), 64 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index b23c3cd2..4249bcee 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,10 +7,20 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. -use hyper::client::{connect::Connection, HttpConnector}; +use hyper::{ + client::{connect::Connection, HttpConnector}, + http::Request, + Body, +}; use lambda_runtime_api_client::Client; use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, env, fmt, future::Future, panic}; +use std::{ + convert::TryFrom, + env, + fmt::{self, Debug, Display}, + future::Future, + panic, +}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; @@ -24,7 +34,6 @@ mod simulated; mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; -use types::Diagnostic; pub use types::{Context, LambdaEvent}; /// Error type that lambdas may result in @@ -121,12 +130,20 @@ where let ctx: Context = Context::try_from(parts.headers)?; let ctx: Context = ctx.with_config(config); - let body = serde_json::from_slice(&body)?; + let request_id = &ctx.request_id.clone(); let xray_trace_id = &ctx.xray_trace_id.clone(); env::set_var("_X_AMZN_TRACE_ID", xray_trace_id); - let request_id = &ctx.request_id.clone(); + let body = match serde_json::from_slice(&body) { + Ok(body) => body, + Err(err) => { + let req = build_event_error_request(request_id, err)?; + client.call(req).await.expect("Unable to send response to Runtime APIs"); + return Ok(()); + } + }; + let req = match handler.ready().await { Ok(handler) => { let task = @@ -141,48 +158,23 @@ where } .into_req() } - Err(err) => { - error!("{:?}", err); // logs the error in CloudWatch - EventErrorRequest { - request_id, - diagnostic: Diagnostic { - error_type: type_name_of_val(&err).to_owned(), - error_message: format!("{}", err), // returns the error to the caller via Lambda API - }, - } - .into_req() - } + Err(err) => build_event_error_request(request_id, err), }, Err(err) => { error!("{:?}", err); - EventErrorRequest { - request_id, - diagnostic: Diagnostic { - error_type: type_name_of_val(&err).to_owned(), - error_message: if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {}", msg) - } else { - "Lambda panicked".to_string() - }, - }, - } - .into_req() + let error_type = type_name_of_val(&err); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {}", msg) + } else { + "Lambda panicked".to_string() + }; + EventErrorRequest::new(request_id, error_type, &msg).into_req() } } } - Err(err) => { - error!("{:?}", err); // logs the error in CloudWatch - EventErrorRequest { - request_id, - diagnostic: Diagnostic { - error_type: type_name_of_val(&err).to_owned(), - error_message: format!("{}", err), // returns the error to the caller via Lambda API - }, - } - .into_req() - } - }; - let req = req?; + Err(err) => build_event_error_request(request_id, err), + }?; + client.call(req).await.expect("Unable to send response to Runtime APIs"); } Ok(()) @@ -247,6 +239,17 @@ fn type_name_of_val(_: T) -> &'static str { std::any::type_name::() } +fn build_event_error_request(request_id: &str, err: T) -> Result, Error> +where + T: Display + Debug, +{ + error!("{:?}", err); // logs the error in CloudWatch + let error_type = type_name_of_val(&err); + let msg = format!("{}", err); + + EventErrorRequest::new(request_id, error_type, &msg).into_req() +} + #[cfg(test)] mod endpoint_tests { use crate::{ @@ -431,8 +434,8 @@ mod endpoint_tests { let req = EventErrorRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", diagnostic: Diagnostic { - error_type: "InvalidEventDataError".to_string(), - error_message: "Error parsing event data".to_string(), + error_type: "InvalidEventDataError", + error_message: "Error parsing event data", }, }; let req = req.into_req()?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 97fc7e4d..26257d20 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -104,7 +104,19 @@ fn test_event_completion_request() { // /runtime/invocation/{AwsRequestId}/error pub(crate) struct EventErrorRequest<'a> { pub(crate) request_id: &'a str, - pub(crate) diagnostic: Diagnostic, + pub(crate) diagnostic: Diagnostic<'a>, +} + +impl<'a> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, error_type: &'a str, error_message: &'a str) -> EventErrorRequest<'a> { + EventErrorRequest { + request_id, + diagnostic: Diagnostic { + error_type, + error_message, + }, + } + } } impl<'a> IntoRequest for EventErrorRequest<'a> { @@ -128,8 +140,8 @@ fn test_event_error_request() { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: "InvalidEventDataError".to_string(), - error_message: "Error parsing event data".to_string(), + error_type: "InvalidEventDataError", + error_message: "Error parsing event data", }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index b9e36702..ddf5d00b 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -5,24 +5,9 @@ use std::{collections::HashMap, convert::TryFrom}; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct Diagnostic { - pub(crate) error_type: String, - pub(crate) error_message: String, -} - -#[test] -fn round_trip_lambda_error() -> Result<(), Error> { - use serde_json::{json, Value}; - let expected = json!({ - "errorType": "InvalidEventDataError", - "errorMessage": "Error parsing event data.", - }); - - let actual: Diagnostic = serde_json::from_value(expected.clone())?; - let actual: Value = serde_json::to_value(actual)?; - assert_eq!(expected, actual); - - Ok(()) +pub(crate) struct Diagnostic<'a> { + pub(crate) error_type: &'a str, + pub(crate) error_message: &'a str, } /// The request ID, which identifies the request that triggered the function invocation. This header @@ -191,6 +176,22 @@ impl LambdaEvent { mod test { use super::*; + #[test] + fn round_trip_lambda_error() { + use serde_json::{json, Value}; + let expected = json!({ + "errorType": "InvalidEventDataError", + "errorMessage": "Error parsing event data.", + }); + + let actual = Diagnostic { + error_type: "InvalidEventDataError", + error_message: "Error parsing event data.", + }; + let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); + assert_eq!(expected, actual); + } + #[test] fn context_with_expected_values_and_types_resolves() { let mut headers = HeaderMap::new(); From 9b27da031817a7466534543ed0f67082fe1498ed Mon Sep 17 00:00:00 2001 From: sgasse <47532708+sgasse@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:00:01 +0200 Subject: [PATCH 121/394] Fix outdated docstring of some RequestExt functions (#524) The PR #253 removed some of the `#[cfg(test)]` guards for a few functions which are useful in testing outside of the library. However the docstrings still stated that the test configuration was needed. This commit fixes the docstrings. Co-authored-by: Simon Gasse --- lambda-http/src/ext.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 0cd85982..afeb9050 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -125,7 +125,7 @@ pub trait RequestExt { /// will yield an empty `QueryMap`. fn query_string_parameters(&self) -> QueryMap; - /// Configures instance with query string parameters under #[cfg(test)] configurations + /// Configures instance with query string parameters /// /// This is intended for use in mock testing contexts. fn with_query_string_parameters(self, parameters: Q) -> Self @@ -140,7 +140,7 @@ pub trait RequestExt { /// These will always be empty for ALB triggered requests fn path_parameters(&self) -> QueryMap; - /// Configures instance with path parameters under #[cfg(test)] configurations + /// Configures instance with path parameters /// /// This is intended for use in mock testing contexts. fn with_path_parameters

(self, parameters: P) -> Self From b28b8686cc6836fcaa1a7eb04b81e1ed7f7fb60d Mon Sep 17 00:00:00 2001 From: sgasse <47532708+sgasse@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:00:42 +0200 Subject: [PATCH 122/394] Add `with_request_context` to `RequestExt` (#523) The trait `RequestExt` already includes functions to add query string parameters, path parameters and stage variables to a request. This commit adds another function to allow adding a `RequestContext` to the request. This is useful in cases where a lambda function is called by the API Gateway with a custom authorizer that adds parameters to the `RequestContext`. Related to #522 Co-authored-by: Simon Gasse --- lambda-http/src/ext.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index afeb9050..15771f92 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -165,6 +165,11 @@ pub trait RequestExt { /// Return request context data assocaited with the ALB or API gateway request fn request_context(&self) -> RequestContext; + /// Configures instance with request context + /// + /// This is intended for use in mock testing contexts. + fn with_request_context(self, context: RequestContext) -> Self; + /// Return the Result of a payload parsed into a serde Deserializeable /// type /// @@ -255,6 +260,12 @@ impl RequestExt for http::Request { .expect("Request did not contain a request context") } + fn with_request_context(self, context: RequestContext) -> Self { + let mut s = self; + s.extensions_mut().insert(context); + s + } + fn lambda_context(&self) -> Context { self.extensions() .get::() From 1f8c4daffa9e1c4edbc1076002b2a100624f5537 Mon Sep 17 00:00:00 2001 From: sgasse <47532708+sgasse@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:01:15 +0200 Subject: [PATCH 123/394] Fix trivial typo in docs of RequestExt (#525) Co-authored-by: Simon Gasse --- lambda-http/src/ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 15771f92..0b79e1e7 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -162,7 +162,7 @@ pub trait RequestExt { where V: Into; - /// Return request context data assocaited with the ALB or API gateway request + /// Return request context data associated with the ALB or API gateway request fn request_context(&self) -> RequestContext; /// Configures instance with request context From 1cacdd80387aa03dce33ca7bec03274305ddebcd Mon Sep 17 00:00:00 2001 From: JJ Ferman Date: Wed, 7 Sep 2022 15:02:02 -0400 Subject: [PATCH 124/394] Adding functionality to catch panics that occur both inside the runtime handler and in the `Future` it returns (#521) Co-authored-by: = <=> --- lambda-runtime/Cargo.toml | 1 + lambda-runtime/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 44bb4218..f30b8bff 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -18,6 +18,7 @@ simulated = [] tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } # Hyper requires the `server` feature to work on nightly hyper = { version = "0.14.20", features = ["http1", "client", "stream", "server"] } +futures = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "^1" bytes = "1.0" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 4249bcee..008237e5 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,6 +7,7 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. +use futures::FutureExt; use hyper::{ client::{connect::Connection, HttpConnector}, http::Request, @@ -146,10 +147,18 @@ where let req = match handler.ready().await { Ok(handler) => { + // Catches panics outside of a `Future` let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + + let task = match task { + // Catches panics inside of the `Future` + Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, + Err(err) => Err(err), + }; + match task { - Ok(response) => match response.await { + Ok(response) => match response { Ok(response) => { trace!("Ok response from handler (run loop)"); EventCompletionRequest { @@ -261,6 +270,7 @@ mod endpoint_tests { types::Diagnostic, Error, Runtime, }; + use futures::future::BoxFuture; use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; use hyper::{server::conn::Http, service::service_fn, Body}; use lambda_runtime_api_client::Client; @@ -508,4 +518,58 @@ mod endpoint_tests { Err(_) => unreachable!("This branch shouldn't be reachable"), } } + + async fn run_panicking_handler(func: F) -> Result<(), Error> + where + F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result>, + { + let (client, server) = io::duplex(64); + let (_tx, rx) = oneshot::channel(); + let base = Uri::from_static("http://localhost:9001"); + + let server = tokio::spawn(async { + handle(server, rx).await.expect("Unable to handle request"); + }); + let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; + + let client = Client::builder() + .with_endpoint(base) + .with_connector(conn) + .build() + .expect("Unable to build client"); + + let f = crate::service_fn(func); + + let config = crate::Config { + function_name: "test_fn".to_string(), + memory: 128, + version: "1".to_string(), + log_stream: "test_stream".to_string(), + log_group: "test_log".to_string(), + }; + + let runtime = Runtime { client }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + runtime.run(incoming, f, &config).await?; + + match server.await { + Ok(_) => Ok(()), + Err(e) if e.is_panic() => Err::<(), Error>(e.into()), + Err(_) => unreachable!("This branch shouldn't be reachable"), + } + } + + #[tokio::test] + async fn panic_in_async_run() -> Result<(), Error> { + run_panicking_handler(|_| Box::pin(async { panic!("This is intentionally here") })).await + } + + #[tokio::test] + async fn panic_outside_async_run() -> Result<(), Error> { + run_panicking_handler(|_| { + panic!("This is intentionally here"); + }) + .await + } } From b1c5dfd1a09c62f77e91c03f278f73b7e2ecfd30 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 7 Sep 2022 16:08:43 -0700 Subject: [PATCH 125/394] Release 0.6.1 (#526) Fixes issues with http paths. Fixes issues with panics in async functions. Fixes issues reporting errors from serde. Add functions to set RequestContext to the RequestExt. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 5 ++++- lambda-http/Cargo.toml | 7 +++++-- lambda-runtime-api-client/Cargo.toml | 5 ++++- lambda-runtime/Cargo.toml | 7 +++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 34d4518a..838d237f 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -2,7 +2,10 @@ name = "lambda-extension" version = "0.6.0" edition = "2021" -authors = ["David Calavera "] +authors = [ + "David Calavera ", + "Harold Sun " +] description = "AWS Lambda Extension API" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index f919a277..8ef88262 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "lambda_http" -version = "0.6.0" -authors = ["Doug Tangren"] +version = "0.6.1" +authors = [ + "David Calavera ", + "Harold Sun " +] edition = "2021" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index c3bbd754..03b1863a 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -2,7 +2,10 @@ name = "lambda_runtime_api_client" version = "0.6.0" edition = "2021" -authors = ["David Calavera "] +authors = [ + "David Calavera ", + "Harold Sun " +] description = "AWS Lambda Runtime interaction API" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index f30b8bff..ee33b736 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "lambda_runtime" -version = "0.6.0" -authors = ["David Barsky "] +version = "0.6.1" +authors = [ + "David Calavera ", + "Harold Sun " +] description = "AWS Lambda Runtime" edition = "2021" license = "Apache-2.0" From bd8896a21d8bef6f1f085ec48660a5b727669dc5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Sep 2022 19:39:41 -0700 Subject: [PATCH 126/394] Treat XML MIME type responses as text (#532) Files with the following MIME types are xml files: application/vnd.apple.installer+xml image/svg+xml application/xhtml+xml application/vnd.mozilla.xul+xml application/atom+xml Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/src/response.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index adfe3528..299a60b2 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -188,10 +188,13 @@ where return convert_to_text(self, "utf-8"); }; + // See list of common MIME types: + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types if content_type.starts_with("text") || content_type.starts_with("application/json") || content_type.starts_with("application/javascript") || content_type.starts_with("application/xml") + || content_type.ends_with("+xml") { return convert_to_text(self, content_type); } From 6fd52700aff9e533b7a625cfe765d1cda18c222c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 5 Oct 2022 07:55:34 -0700 Subject: [PATCH 127/394] Add custom response header to force text encoding (#533) This allows users to force text encoding on responses that the runtime doesn't consider text and would return binary data otherwise. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/src/response.rs | 62 ++++++++++++++++++++++++++++- lambda-http/tests/data/svg_logo.svg | 49 +++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 lambda-http/tests/data/svg_logo.svg diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 299a60b2..f2bc9b9a 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -23,6 +23,8 @@ use std::borrow::Cow; use std::future::ready; use std::{fmt, future::Future, pin::Pin}; +const X_LAMBDA_HTTP_CONTENT_ENCODING: &str = "x-lambda-http-content-encoding"; + /// Representation of Lambda response #[doc(hidden)] #[derive(Serialize, Debug)] @@ -181,7 +183,7 @@ where return convert_to_binary(self); } - let content_type = if let Some(value) = headers.get(http::header::CONTENT_TYPE) { + let content_type = if let Some(value) = headers.get(CONTENT_TYPE) { value.to_str().unwrap_or_default() } else { // Content-Type and Content-Encoding not set, passthrough as utf8 text @@ -199,6 +201,12 @@ where return convert_to_text(self, content_type); } + if let Some(value) = headers.get(X_LAMBDA_HTTP_CONTENT_ENCODING) { + if value == "text" { + return convert_to_text(self, content_type); + } + } + convert_to_binary(self) } } @@ -242,7 +250,7 @@ pub type BodyFuture = Pin>>; #[cfg(test)] mod tests { - use super::{Body, IntoResponse, LambdaResponse, RequestOrigin}; + use super::{Body, IntoResponse, LambdaResponse, RequestOrigin, X_LAMBDA_HTTP_CONTENT_ENCODING}; use http::{ header::{CONTENT_ENCODING, CONTENT_TYPE}, Response, @@ -250,6 +258,8 @@ mod tests { use hyper::Body as HyperBody; use serde_json::{self, json}; + const SVG_LOGO: &str = include_str!("../tests/data/svg_logo.svg"); + #[tokio::test] async fn json_into_response() { let response = json!({ "hello": "lambda"}).into_response().await; @@ -388,4 +398,52 @@ mod tests { json ) } + + #[tokio::test] + async fn content_type_xml_as_text() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_TYPE, "image/svg+xml") + .body(HyperBody::from(SVG_LOGO.as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + + match response.body() { + Body::Text(body) => assert_eq!(SVG_LOGO, body), + _ => panic!("invalid body"), + } + assert_eq!( + response + .headers() + .get(CONTENT_TYPE) + .map(|h| h.to_str().expect("invalid header")), + Some("image/svg+xml") + ) + } + + #[tokio::test] + async fn content_type_custom_encoding_as_text() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + // this CONTENT-TYPE is not standard, and would yield a binary response + .header(CONTENT_TYPE, "image/svg") + .header(X_LAMBDA_HTTP_CONTENT_ENCODING, "text") + .body(HyperBody::from(SVG_LOGO.as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + + match response.body() { + Body::Text(body) => assert_eq!(SVG_LOGO, body), + _ => panic!("invalid body"), + } + assert_eq!( + response + .headers() + .get(CONTENT_TYPE) + .map(|h| h.to_str().expect("invalid header")), + Some("image/svg") + ) + } } diff --git a/lambda-http/tests/data/svg_logo.svg b/lambda-http/tests/data/svg_logo.svg new file mode 100644 index 00000000..3dd03b6c --- /dev/null +++ b/lambda-http/tests/data/svg_logo.svg @@ -0,0 +1,49 @@ + + + SVG logo combined with the W3C logo, set horizontally + The logo combines three entities displayed horizontally: the W3C logo with the text 'W3C'; the drawing of a flower or star shape with eight arms; and the text 'SVG'. These three entities are set horizontally. + + + + + SVG logo combined with the W3C logo + image/svg+xml + + 2007-11-01 + + + + The logo combines three entities displayed horizontally: the W3C logo with the text 'W3C'; the drawing of a flower or star shape with eight arms; and the text 'SVG'. These three entities are set horizontally. + + + + + + W3C + SVG + + + + + + + + + + + + + + + + \ No newline at end of file From d7aec43cf789984218f6d40a2932dce4f3e5b229 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 5 Oct 2022 19:26:25 -0700 Subject: [PATCH 128/394] Extract encoding conversions to make it easier to manage. (#535) Use two list of values to match content types that need to be returned as text. One list with prefixes, and another list with suffixes. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/src/response.rs | 63 +++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index f2bc9b9a..556106d3 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -25,6 +25,19 @@ use std::{fmt, future::Future, pin::Pin}; const X_LAMBDA_HTTP_CONTENT_ENCODING: &str = "x-lambda-http-content-encoding"; +// See list of common MIME types: +// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types +// - https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md +const TEXT_ENCODING_PREFIXES: [&'static str; 5] = [ + "text", + "application/json", + "application/javascript", + "application/xml", + "application/yaml", +]; + +const TEXT_ENCODING_SUFFIXES: [&'static str; 3] = ["+xml", "+yaml", "+json"]; + /// Representation of Lambda response #[doc(hidden)] #[derive(Serialize, Debug)] @@ -190,15 +203,16 @@ where return convert_to_text(self, "utf-8"); }; - // See list of common MIME types: - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types - if content_type.starts_with("text") - || content_type.starts_with("application/json") - || content_type.starts_with("application/javascript") - || content_type.starts_with("application/xml") - || content_type.ends_with("+xml") - { - return convert_to_text(self, content_type); + for prefix in TEXT_ENCODING_PREFIXES { + if content_type.starts_with(prefix) { + return convert_to_text(self, content_type); + } + } + + for suffix in TEXT_ENCODING_SUFFIXES { + if content_type.ends_with(suffix) { + return convert_to_text(self, content_type); + } } if let Some(value) = headers.get(X_LAMBDA_HTTP_CONTENT_ENCODING) { @@ -446,4 +460,35 @@ mod tests { Some("image/svg") ) } + + #[tokio::test] + async fn content_type_yaml_as_text() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let yaml = r#"--- +foo: bar + "#; + + let formats = ["application/yaml", "custom/vdn+yaml"]; + + for format in formats { + let response = Response::builder() + .header(CONTENT_TYPE, format) + .body(HyperBody::from(yaml.as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + + match response.body() { + Body::Text(body) => assert_eq!(yaml, body), + _ => panic!("invalid body"), + } + assert_eq!( + response + .headers() + .get(CONTENT_TYPE) + .map(|h| h.to_str().expect("invalid header")), + Some(format) + ) + } + } } From d7ab6ae5ac087e2dc40adfe5c7002ce489ffe4d6 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 5 Oct 2022 19:41:47 -0700 Subject: [PATCH 129/394] Release lambda_http 0.6.2 (#536) Improve text encoding conversions Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8ef88262..d9a85484 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.6.1" +version = "0.6.2" authors = [ "David Calavera ", "Harold Sun " From fc51300a75d488ddc74579babec264d357951e69 Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Thu, 6 Oct 2022 22:26:24 -0400 Subject: [PATCH 130/394] Check that `lambda_http`s inner service is ready before calling it (#538) * Check that `lambda_http`s inner service is ready before calling it This allows for service [backpressure](https://docs.rs/tower/0.4.13/tower/trait.Service.html#backpressure). Additionally: > Services are permitted to panic if `call` is invoked without obtaining `Poll::Ready(Ok(()))` from `poll_ready`. [Source](https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services) * Check that `lambda_extension`s inner service is ready before calling it This allows for service [backpressure](https://docs.rs/tower/0.4.13/tower/trait.Service.html#backpressure). Additionally: > Services are permitted to panic if `call` is invoked without obtaining `Poll::Ready(Ok(()))` from `poll_ready`. [Source](https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services) * Check that `lambda_runtime`s inner service is ready before calling it This allows for service [backpressure](https://docs.rs/tower/0.4.13/tower/trait.Service.html#backpressure). Additionally: > Services are permitted to panic if `call` is invoked without obtaining `Poll::Ready(Ok(()))` from `poll_ready`. [Source](https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services) --- lambda-extension/src/extension.rs | 25 ++++++++-- lambda-http/src/lib.rs | 4 +- .../src/bin/extension-trait.rs | 44 ++++++++++++++++- .../src/bin/http-trait.rs | 45 ++++++++++++++++- .../src/bin/runtime-trait.rs | 49 +++++++++++++++++-- 5 files changed, 153 insertions(+), 14 deletions(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 81462c24..ec83ce71 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,14 +1,16 @@ -use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent}; -use hyper::{server::conn::AddrStream, Server}; -use lambda_runtime_api_client::Client; use std::{ convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, }; + +use hyper::{server::conn::AddrStream, Server}; +use lambda_runtime_api_client::Client; use tokio::sync::Mutex; use tokio_stream::StreamExt; -use tower::{service_fn, MakeService, Service}; +use tower::{service_fn, MakeService, Service, ServiceExt}; use tracing::{error, trace}; +use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent}; + const DEFAULT_LOG_PORT_NUMBER: u16 = 9002; /// An Extension that runs event and log processors @@ -199,6 +201,21 @@ where let event = LambdaEvent::new(extension_id, event); + let ep = match ep.ready().await { + Ok(ep) => ep, + Err(error) => { + println!("Inner service is not ready: {:?}", error); + let req = if is_invoke { + requests::init_error(extension_id, &error.to_string(), None)? + } else { + requests::exit_error(extension_id, &error.to_string(), None)? + }; + + client.call(req).await?; + return Err(error.into()); + } + }; + let res = ep.call(event).await; if let Err(error) = res { println!("{:?}", error); diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 3581002f..d8d1e942 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -155,8 +155,8 @@ where type Error = E; type Future = TransformResponse<'a, R, Self::Error>; - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - core::task::Poll::Ready(Ok(())) + fn poll_ready(&mut self, cx: &mut core::task::Context<'_>) -> core::task::Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: LambdaEvent) -> Self::Future { diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index 1dc73c75..ecf46c81 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -1,13 +1,36 @@ -use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; use std::{ future::{ready, Future}, pin::Pin, + sync::atomic::{AtomicBool, Ordering}, }; + +use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; use tracing::info; -#[derive(Default)] struct MyExtension { invoke_count: usize, + ready: AtomicBool, +} + +impl Default for MyExtension { + fn default() -> Self { + Self { + invoke_count: usize::default(), + // New instances are not ready to be called until polled. + ready: false.into(), + } + } +} + +impl Clone for MyExtension { + fn clone(&self) -> Self { + Self { + invoke_count: self.invoke_count, + // Cloned instances may not be immediately ready to be called. + // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services + ready: false.into(), + } + } } impl Service for MyExtension { @@ -16,6 +39,12 @@ impl Service for MyExtension { type Response = (); fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + if self.ready.swap(true, Ordering::SeqCst) { + info!("[extension] Service was already ready"); + } else { + info!("[extension] Service is now ready"); + }; + core::task::Poll::Ready(Ok(())) } @@ -30,6 +59,17 @@ impl Service for MyExtension { } } + // After being called once, the service is no longer ready until polled again. + if self.ready.swap(false, Ordering::SeqCst) { + info!("[extension] The service is ready"); + } else { + // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure + // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services + // > Services are permitted to panic if `call` is invoked without obtaining + // > `Poll::Ready(Ok(()))` from `poll_ready`. + panic!("[extension] The service is not ready; `.poll_ready()` must be called first"); + } + Box::pin(ready(Ok(()))) } } diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs index 67cc9fc5..fffe0db0 100644 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -1,13 +1,36 @@ -use lambda_http::{Body, Error, Request, RequestExt, Response, Service}; use std::{ future::{ready, Future}, pin::Pin, + sync::atomic::{AtomicBool, Ordering}, }; + +use lambda_http::{Body, Error, Request, RequestExt, Response, Service}; use tracing::info; -#[derive(Default)] struct MyHandler { invoke_count: usize, + ready: AtomicBool, +} + +impl Default for MyHandler { + fn default() -> Self { + Self { + invoke_count: usize::default(), + // New instances are not ready to be called until polled. + ready: false.into(), + } + } +} + +impl Clone for MyHandler { + fn clone(&self) -> Self { + Self { + invoke_count: self.invoke_count, + // Cloned instances may not be immediately ready to be called. + // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services + ready: false.into(), + } + } } impl Service for MyHandler { @@ -16,6 +39,12 @@ impl Service for MyHandler { type Response = Response; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + if self.ready.swap(true, Ordering::SeqCst) { + info!("[http-trait] Service was already ready"); + } else { + info!("[http-trait] Service is now ready"); + }; + core::task::Poll::Ready(Ok(())) } @@ -23,6 +52,18 @@ impl Service for MyHandler { self.invoke_count += 1; info!("[http-trait] Received event {}: {:?}", self.invoke_count, request); info!("[http-trait] Lambda context: {:?}", request.lambda_context()); + + // After being called once, the service is no longer ready until polled again. + if self.ready.swap(false, Ordering::SeqCst) { + info!("[http-trait] The service is ready"); + } else { + // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure + // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services + // > Services are permitted to panic if `call` is invoked without obtaining + // > `Poll::Ready(Ok(()))` from `poll_ready`. + panic!("[http-trait] The service is not ready; `.poll_ready()` must be called first"); + } + Box::pin(ready(Ok(Response::builder() .status(200) .body(Body::from("Hello, World!")) diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs index d86bafdc..b925e138 100644 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -1,9 +1,11 @@ -use lambda_runtime::{Error, LambdaEvent, Service}; -use serde::{Deserialize, Serialize}; use std::{ future::{ready, Future}, pin::Pin, + sync::atomic::{AtomicBool, Ordering}, }; + +use lambda_runtime::{Error, LambdaEvent, Service}; +use serde::{Deserialize, Serialize}; use tracing::info; #[derive(Deserialize, Debug)] @@ -16,9 +18,30 @@ struct Response { message: String, } -#[derive(Default)] struct MyHandler { invoke_count: usize, + ready: AtomicBool, +} + +impl Default for MyHandler { + fn default() -> Self { + Self { + invoke_count: usize::default(), + // New instances are not ready to be called until polled. + ready: false.into(), + } + } +} + +impl Clone for MyHandler { + fn clone(&self) -> Self { + Self { + invoke_count: self.invoke_count, + // Cloned instances may not be immediately ready to be called. + // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services + ready: false.into(), + } + } } impl Service> for MyHandler { @@ -27,12 +50,30 @@ impl Service> for MyHandler { type Response = Response; fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { + if self.ready.swap(true, Ordering::SeqCst) { + info!("[runtime-trait] Service was already ready"); + } else { + info!("[runtime-trait] Service is now ready"); + }; + core::task::Poll::Ready(Ok(())) } fn call(&mut self, request: LambdaEvent) -> Self::Future { self.invoke_count += 1; - info!("[handler] Received event {}: {:?}", self.invoke_count, request); + info!("[runtime-trait] Received event {}: {:?}", self.invoke_count, request); + + // After being called once, the service is no longer ready until polled again. + if self.ready.swap(false, Ordering::SeqCst) { + info!("[runtime-trait] The service is ready"); + } else { + // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure + // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services + // > Services are permitted to panic if `call` is invoked without obtaining + // > `Poll::Ready(Ok(()))` from `poll_ready`. + panic!("[runtime-trait] The service is not ready; `.poll_ready()` must be called first"); + } + Box::pin(ready(Ok(Response { message: request.payload.command.to_uppercase(), }))) From 45211b005fd49949c4ab4deff713c0e98690d4e5 Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Sat, 8 Oct 2022 19:01:26 -0400 Subject: [PATCH 131/394] Reexport external modules of publicly used types (#542) --- lambda-http/src/lib.rs | 6 ++++++ lambda-http/src/response.rs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index d8d1e942..ff0fa60a 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -78,6 +78,12 @@ use crate::{ request::{LambdaRequest, RequestOrigin}, response::LambdaResponse, }; + +#[cfg(feature = "alb")] +pub use aws_lambda_events::alb; +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] +pub use aws_lambda_events::apigw; + pub use aws_lambda_events::encodings::Body; use std::{ future::Future, diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 556106d3..03d52380 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -1,13 +1,13 @@ //! Response types use crate::request::RequestOrigin; -use aws_lambda_events::encodings::Body; #[cfg(feature = "alb")] -use aws_lambda_events::event::alb::AlbTargetGroupResponse; +use aws_lambda_events::alb::AlbTargetGroupResponse; #[cfg(any(feature = "apigw_rest", feature = "apigw_websockets"))] -use aws_lambda_events::event::apigw::ApiGatewayProxyResponse; +use aws_lambda_events::apigw::ApiGatewayProxyResponse; #[cfg(feature = "apigw_http")] -use aws_lambda_events::event::apigw::ApiGatewayV2httpResponse; +use aws_lambda_events::apigw::ApiGatewayV2httpResponse; +use aws_lambda_events::encodings::Body; use encoding_rs::Encoding; use http::header::CONTENT_ENCODING; use http::HeaderMap; From 2201109ab97c2443e6afb267e97026b9ff0712ef Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 11 Oct 2022 23:47:44 +0800 Subject: [PATCH 132/394] Decode query paramters for ALB event source (#545) * decode ALB query strings --- lambda-http/Cargo.toml | 1 + lambda-http/src/request.rs | 68 +++++++++++++++++-- ...alue_request_encoded_query_parameters.json | 37 ++++++++++ .../alb_request_encoded_query_parameters.json | 24 +++++++ lambda-runtime-api-client/README.md | 2 +- 5 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 lambda-http/tests/data/alb_multi_value_request_encoded_query_parameters.json create mode 100644 lambda-http/tests/data/alb_request_encoded_query_parameters.json diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index d9a85484..d4b6167a 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -36,6 +36,7 @@ query_map = { version = "0.5", features = ["url-query"] } mime = "0.3.16" encoding_rs = "0.8.31" url = "2.2.2" +percent-encoding = "2.2.0" [dependencies.aws_lambda_events] version = "^0.6.3" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 27d54cf2..978c3b0c 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -20,6 +20,7 @@ use serde::Deserialize; use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; +use std::str::FromStr; use std::{io::Read, mem}; use url::Url; @@ -201,22 +202,25 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); let raw_path = alb.path.unwrap_or_default(); + let query_string_parameters = decode_query_map(alb.query_string_parameters); + let multi_value_query_string_parameters = decode_query_map(alb.multi_value_query_string_parameters); + let builder = http::Request::builder() .uri(build_request_uri( &raw_path, &alb.headers, host, - Some((&alb.multi_value_query_string_parameters, &alb.query_string_parameters)), + Some((&multi_value_query_string_parameters, &query_string_parameters)), )) .extension(RawHttpPath(raw_path)) // multi valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred .extension(QueryStringParameters( - if alb.multi_value_query_string_parameters.is_empty() { - alb.query_string_parameters + if multi_value_query_string_parameters.is_empty() { + query_string_parameters } else { - alb.multi_value_query_string_parameters + multi_value_query_string_parameters }, )) .extension(RequestContext::Alb(alb.request_context)); @@ -243,6 +247,12 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { req } +fn decode_query_map(query_map: QueryMap) -> QueryMap { + let query_string = query_map.to_query_string(); + let decoded = percent_encoding::percent_decode(query_string.as_bytes()).decode_utf8_lossy(); + QueryMap::from_str(&decoded).unwrap_or_default() +} + #[cfg(feature = "apigw_websockets")] fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request { let http_method = ag.http_method; @@ -548,6 +558,34 @@ mod tests { ); } + #[test] + fn deserializes_alb_request_encoded_query_parameters_events() { + // from the docs + // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers + let input = include_str!("../tests/data/alb_request_encoded_query_parameters.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {:?} given {}", + result, + input + ); + let req = result.expect("failed to parse request"); + assert_eq!(req.method(), "GET"); + assert_eq!( + req.uri(), + "https://lambda-846800462-us-east-2.elb.amazonaws.com/?myKey=%3FshowAll%3Dtrue" + ); + + // Ensure this is an ALB request + let req_context = req.request_context(); + assert!( + matches!(req_context, RequestContext::Alb(_)), + "expected Alb context, got {:?}", + req_context + ); + } + #[test] fn deserializes_apigw_multi_value_request_events() { // from docs @@ -593,6 +631,28 @@ mod tests { ); } + #[test] + fn deserializes_alb_multi_value_request_encoded_query_parameters_events() { + // from docs + // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + let input = include_str!("../tests/data/alb_multi_value_request_encoded_query_parameters.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event is was not parsed as expected {:?} given {}", + result, + input + ); + let request = result.expect("failed to parse request"); + assert!(!request.query_string_parameters().is_empty()); + + // test RequestExt#query_string_parameters does the right thing + assert_eq!( + request.query_string_parameters().all("myKey"), + Some(vec!["?showAll=true", "?showAll=false"]) + ); + } + #[test] fn deserialize_apigw_http_sam_local() { // manually generated from AWS SAM CLI diff --git a/lambda-http/tests/data/alb_multi_value_request_encoded_query_parameters.json b/lambda-http/tests/data/alb_multi_value_request_encoded_query_parameters.json new file mode 100644 index 00000000..246e1de8 --- /dev/null +++ b/lambda-http/tests/data/alb_multi_value_request_encoded_query_parameters.json @@ -0,0 +1,37 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { "myKey": "%3FshowAll%3Dtrue" }, + "multiValueQueryStringParameters": { "myKey": ["%3FshowAll%3Dtrue", "%3FshowAll%3Dfalse"] }, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "name1=value1", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "multiValueHeaders": { + "accept": ["text/html,application/xhtml+xml"], + "accept-language": ["en-US,en;q=0.8"], + "content-type": ["text/plain"], + "cookie": ["name1=value1", "name2=value2"], + "host": ["lambda-846800462-us-east-2.elb.amazonaws.com"], + "user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)"], + "x-amzn-trace-id": ["Root=1-5bdb40ca-556d8b0c50dc66f0511bf520"], + "x-forwarded-for": ["72.21.198.66"], + "x-forwarded-port": ["443"], + "x-forwarded-proto": ["https"] + }, + "isBase64Encoded": false, + "body": "request_body" +} \ No newline at end of file diff --git a/lambda-http/tests/data/alb_request_encoded_query_parameters.json b/lambda-http/tests/data/alb_request_encoded_query_parameters.json new file mode 100644 index 00000000..d8e1b452 --- /dev/null +++ b/lambda-http/tests/data/alb_request_encoded_query_parameters.json @@ -0,0 +1,24 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { "myKey": "%3FshowAll%3Dtrue"}, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "cookies", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "isBase64Encoded": false, + "body": "request_body" +} \ No newline at end of file diff --git a/lambda-runtime-api-client/README.md b/lambda-runtime-api-client/README.md index 530fefdd..2251cbc7 100644 --- a/lambda-runtime-api-client/README.md +++ b/lambda-runtime-api-client/README.md @@ -32,4 +32,4 @@ async fn main() -> Result<(), Error> { client.call(request).await } -``` +``` \ No newline at end of file From 03a98e24c9e49db323114b9e4ac97dd2f302de81 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 11 Oct 2022 09:28:49 -0700 Subject: [PATCH 133/394] Update MSRV to 1.62 (#544) This keeps us compatible with the AWS SDK. Signed-off-by: David Calavera Signed-off-by: David Calavera --- .github/workflows/build.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a39c9d55..a33eca21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - ubuntu-latest - macOS-latest rust: - - "1.58.0" # Current MSRV + - "1.62.0" # Current MSRV - stable - beta - nightly diff --git a/README.md b/README.md index 2b00dbe8..9ff1ebc2 100644 --- a/README.md +++ b/README.md @@ -407,7 +407,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.58, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.62, and is not guaranteed to build on compiler versions earlier than that. ## Security From bab02359e2955b6d75beb9169302c5501b2340ef Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Tue, 11 Oct 2022 13:44:57 -0400 Subject: [PATCH 134/394] Updated aws_lambda_events dependency (#541) --- lambda-http/Cargo.toml | 3 +-- lambda-http/src/ext.rs | 2 +- lambda-http/src/lib.rs | 8 ++++---- lambda-http/src/request.rs | 3 +-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index d4b6167a..541c4410 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -32,14 +32,13 @@ lambda_runtime = { path = "../lambda-runtime", version = "0.6" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" -query_map = { version = "0.5", features = ["url-query"] } mime = "0.3.16" encoding_rs = "0.8.31" url = "2.2.2" percent-encoding = "2.2.0" [dependencies.aws_lambda_events] -version = "^0.6.3" +version = "^0.7" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 0b79e1e7..2056d48a 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -1,8 +1,8 @@ //! Extension methods for `http::Request` types use crate::{request::RequestContext, Body}; +use aws_lambda_events::query_map::QueryMap; use lambda_runtime::Context; -use query_map::QueryMap; use serde::{de::value::Error as SerdeError, Deserialize}; use std::{error::Error, fmt}; diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index ff0fa60a..74fd5607 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -79,10 +79,10 @@ use crate::{ response::LambdaResponse, }; -#[cfg(feature = "alb")] -pub use aws_lambda_events::alb; -#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] -pub use aws_lambda_events::apigw; +// Reexported in its entirety, regardless of what feature flags are enabled +// because working with many of these types requires other types in, or +// reexported by, this crate. +pub use aws_lambda_events; pub use aws_lambda_events::encodings::Body; use std::{ diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 978c3b0c..4e301d10 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -12,10 +12,9 @@ use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestCon use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext}; #[cfg(feature = "apigw_websockets")] use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; -use aws_lambda_events::encodings::Body; +use aws_lambda_events::{encodings::Body, query_map::QueryMap}; use http::header::HeaderName; use http::HeaderMap; -use query_map::QueryMap; use serde::Deserialize; use serde_json::error::Error as JsonError; use std::future::Future; From 1e165027c3d49d80806bba22d698259fd303f11f Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Tue, 11 Oct 2022 13:45:59 -0400 Subject: [PATCH 135/394] Allow `Adapter` to be `.boxed()` (#540) Allowing `Adapter` to be [`.boxed()`](https://docs.rs/tower/0.4.13/tower/trait.ServiceExt.html#method.boxed) enables easier composition of `tower::Service`s that may include additional middleware prior to `Adapter` converting the `LambdaEvent` to a `Request`. --- lambda-http/src/lib.rs | 62 +++++++++++++++++++++++++++++++++++-- lambda-http/src/request.rs | 2 +- lambda-http/src/response.rs | 15 +++++---- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 74fd5607..ebb3e0c5 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -140,7 +140,7 @@ pub struct Adapter<'a, R, S> { impl<'a, R, S, E> From for Adapter<'a, R, S> where S: Service, - S::Future: 'a, + S::Future: Send + 'a, R: IntoResponse, { fn from(service: S) -> Self { @@ -154,7 +154,7 @@ where impl<'a, R, S, E> Service> for Adapter<'a, R, S> where S: Service, - S::Future: 'a, + S::Future: Send + 'a, R: IntoResponse, { type Response = LambdaResponse; @@ -182,9 +182,65 @@ where pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> where S: Service, - S::Future: 'a, + S::Future: Send + 'a, R: IntoResponse, E: std::fmt::Debug + std::fmt::Display, { 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, + }; + + // A middleware that logs requests before forwarding them to another service + struct LogService { + inner: S, + } + + impl Service> for LogService + where + S: Service>, + { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, event: LambdaEvent) -> Self::Future { + // Log the request + println!("Lambda event: {:#?}", event); + + self.inner.call(event) + } + } + + /// This tests that `Adapter` can be used in a `tower::Service` where the user + /// may require additional middleware between `lambda_runtime::run` and where + /// the `LambdaEvent` is converted into a `Request`. + #[test] + fn adapter_is_boxable() { + let _service: BoxService, LambdaResponse, http::Error> = ServiceBuilder::new() + .layer_fn(|service| { + // This could be any middleware that logs, inspects, or manipulates + // the `LambdaEvent` before it's converted to a `Request` by `Adapter`. + + LogService { inner: service } + }) + .layer_fn(Adapter::from) + .service_fn(|_event: Request| async move { Response::builder().status(StatusCode::OK).body(Body::Empty) }) + .boxed(); + } +} diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 4e301d10..9b3d63f7 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -61,7 +61,7 @@ impl LambdaRequest { } /// RequestFuture type -pub type RequestFuture<'a, R, E> = Pin> + 'a>>; +pub type RequestFuture<'a, R, E> = Pin> + Send + 'a>>; /// Represents the origin from which the lambda was requested from. #[doc(hidden)] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 03d52380..6db18f49 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -129,7 +129,7 @@ pub trait IntoResponse { impl IntoResponse for Response where - B: ConvertBody + 'static, + B: ConvertBody + Send + 'static, { fn into_response(self) -> ResponseFuture { let (parts, body) = self.into_parts(); @@ -180,7 +180,7 @@ impl IntoResponse for serde_json::Value { } } -pub type ResponseFuture = Pin>>>; +pub type ResponseFuture = Pin> + Send>>; pub trait ConvertBody { fn convert(self, parts: HeaderMap) -> BodyFuture; @@ -188,7 +188,8 @@ pub trait ConvertBody { impl ConvertBody for B where - B: HttpBody + Unpin + 'static, + B: HttpBody + Unpin + Send + 'static, + B::Data: Send, B::Error: fmt::Debug, { fn convert(self, headers: HeaderMap) -> BodyFuture { @@ -227,7 +228,8 @@ where fn convert_to_binary(body: B) -> BodyFuture where - B: HttpBody + Unpin + 'static, + B: HttpBody + Unpin + Send + 'static, + B::Data: Send, B::Error: fmt::Debug, { Box::pin(async move { Body::from(to_bytes(body).await.expect("unable to read bytes from body").to_vec()) }) @@ -235,7 +237,8 @@ where fn convert_to_text(body: B, content_type: &str) -> BodyFuture where - B: HttpBody + Unpin + 'static, + B: HttpBody + Unpin + Send + 'static, + B::Data: Send, B::Error: fmt::Debug, { let mime_type = content_type.parse::(); @@ -260,7 +263,7 @@ where }) } -pub type BodyFuture = Pin>>; +pub type BodyFuture = Pin + Send>>; #[cfg(test)] mod tests { From a0f6828ccf6a42643eef91ef48f38f0dada52218 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 12 Oct 2022 07:49:31 -0700 Subject: [PATCH 136/394] Release 0.7.0 (#546) Ensure that we don't print internal warnings on user applications. Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 4 ++-- lambda-http/src/request.rs | 9 +++++++-- lambda-http/src/response.rs | 13 ++++++------- lambda-integration-tests/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 838d237f..823e3f82 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.6.0" +version = "0.7.0" edition = "2021" authors = [ "David Calavera ", @@ -19,7 +19,7 @@ bytes = "1.0" chrono = { version = "0.4", features = ["serde"] } http = "0.2" hyper = { version = "0.14.20", features = ["http1", "client", "server", "stream", "runtime"] } -lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.7", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 541c4410..a553f871 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.6.2" +version = "0.7.0" authors = [ "David Calavera ", "Harold Sun " @@ -28,7 +28,7 @@ bytes = "1" http = "0.2" http-body = "0.4" hyper = "0.14.20" -lambda_runtime = { path = "../lambda-runtime", version = "0.6" } +lambda_runtime = { path = "../lambda-runtime", version = "0.7" } serde = { version = "^1", features = ["derive"] } serde_json = "^1" serde_urlencoded = "0.7.0" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9b3d63f7..e316a5a2 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,7 +3,9 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use crate::ext::{PathParameters, QueryStringParameters, RawHttpPath, StageVariables}; +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] +use crate::ext::{PathParameters, StageVariables}; +use crate::ext::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; #[cfg(feature = "apigw_rest")] @@ -19,7 +21,6 @@ use serde::Deserialize; use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; -use std::str::FromStr; use std::{io::Read, mem}; use url::Url; @@ -246,7 +247,10 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { req } +#[cfg(feature = "alb")] fn decode_query_map(query_map: QueryMap) -> QueryMap { + use std::str::FromStr; + let query_string = query_map.to_query_string(); let decoded = percent_encoding::percent_decode(query_string.as_bytes()).decode_utf8_lossy(); QueryMap::from_str(&decoded).unwrap_or_default() @@ -304,6 +308,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< req } +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { match stage { None => path.into(), diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 6db18f49..b174f98e 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -11,10 +11,7 @@ use aws_lambda_events::encodings::Body; use encoding_rs::Encoding; use http::header::CONTENT_ENCODING; use http::HeaderMap; -use http::{ - header::{CONTENT_TYPE, SET_COOKIE}, - Response, -}; +use http::{header::CONTENT_TYPE, Response}; use http_body::Body as HttpBody; use hyper::body::to_bytes; use mime::{Mime, CHARSET}; @@ -28,7 +25,7 @@ const X_LAMBDA_HTTP_CONTENT_ENCODING: &str = "x-lambda-http-content-encoding"; // See list of common MIME types: // - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types // - https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md -const TEXT_ENCODING_PREFIXES: [&'static str; 5] = [ +const TEXT_ENCODING_PREFIXES: [&str; 5] = [ "text", "application/json", "application/javascript", @@ -36,7 +33,7 @@ const TEXT_ENCODING_PREFIXES: [&'static str; 5] = [ "application/yaml", ]; -const TEXT_ENCODING_SUFFIXES: [&'static str; 3] = ["+xml", "+yaml", "+json"]; +const TEXT_ENCODING_SUFFIXES: [&str; 3] = ["+xml", "+yaml", "+json"]; /// Representation of Lambda response #[doc(hidden)] @@ -61,7 +58,7 @@ impl LambdaResponse { b @ Body::Binary(_) => (true, Some(b)), }; - let mut headers = parts.headers; + let headers = parts.headers; let status_code = parts.status.as_u16(); match request_origin { @@ -75,6 +72,8 @@ impl LambdaResponse { }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { + use http::header::SET_COOKIE; + let mut headers = headers; // ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute, // so remove them from the headers. let cookies = headers diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index bb4a6aea..116b2986 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -13,9 +13,9 @@ readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lambda_http = { path = "../lambda-http", version = "0.6" } -lambda_runtime = { path = "../lambda-runtime", version = "0.6" } -lambda-extension = { path = "../lambda-extension", version = "0.6" } +lambda_http = { path = "../lambda-http", version = "0.7" } +lambda_runtime = { path = "../lambda-runtime", version = "0.7" } +lambda-extension = { path = "../lambda-extension", version = "0.7" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 03b1863a..dab52a48 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.6.0" +version = "0.7.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index ee33b736..14c675b6 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.6.1" +version = "0.7.0" authors = [ "David Calavera ", "Harold Sun " @@ -30,4 +30,4 @@ async-stream = "0.3" tracing = { version = "0.1", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.6", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.7", path = "../lambda-runtime-api-client" } From f8706e332ee1732284c9b51c816df99d264bd39e Mon Sep 17 00:00:00 2001 From: Dan <78874812+ymwjbxxq@users.noreply.github.com> Date: Mon, 24 Oct 2022 19:00:33 +0200 Subject: [PATCH 137/394] Dedicated README for lambda_http (#554) * add readme * fix location of Readme in the cargo.toml file https://github.com/awslabs/aws-lambda-rust-runtime/pull/554#pullrequestreview-1152118326 * amend example based on the comments * make the code example to compile * amend example * add reference for Lambda function URLs Co-authored-by: Daniele <> --- lambda-http/Cargo.toml | 2 +- lambda-http/README.md | 233 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 lambda-http/README.md diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index a553f871..51d7194c 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -13,7 +13,7 @@ homepage = "https://github.com/awslabs/aws-lambda-rust-runtime" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" documentation = "https://docs.rs/lambda_runtime" categories = ["web-programming::http-server"] -readme = "../README.md" +readme = "README.md" [features] default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"] diff --git a/lambda-http/README.md b/lambda-http/README.md new file mode 100644 index 00000000..20e670eb --- /dev/null +++ b/lambda-http/README.md @@ -0,0 +1,233 @@ +# lambda-http for AWS Lambda in Rust + +[![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) + +**`lambda-http`** is an abstraction that takes payloads from different services and turns them into http objects, making it easy to write API Gateway proxy event focused Lambda functions in Rust. + +lambda-http handler is made of: +* Request - Represents an HTTP request +* IntoResponse - Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] + +We are able to handle requests from: +* [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST, HTTP and WebSockets API lambda integrations +* AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) +* AWS [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) + +Thanks to the Request type we can seemsly handle proxy integrations without the worry to specify the specific service type. + +There is also an Extentions for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. + +For example some handy extensions: + +* query_string_parameters - Return pre-parsed http query string parameters, parameters provided after the `?` portion of a url associated with the request +* path_parameters - Return pre-extracted path parameters, parameter provided in url placeholders `/foo/{bar}/baz/{boom}` associated with the request +* payload - Return the Result of a payload parsed into a serde Deserializeable type + +## Examples + +Here you will find a few examples to handle basic scenarions: + +* Reading a JSON from a body and deserialise into a structure +* Reading querystring parameters +* Lambda Request Authorizer +* Passing the Lambda execution context initialisation to the handler + +### Reading a JSON from a body and deserialise into a structure + +The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload. + +```rust +use http::Response; +use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_ansi(false) + .without_time() + .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .init(); + + run(service_fn(function_handler)).await +} + +pub async fn function_handler(event: Request) -> Result { + let body = event.payload::()?; + + let response = Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(json!({ + "message": "Hello World", + "payload": body, + }).to_string()) + .map_err(Box::new)?; + + Ok(response) +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct MyPayload { + pub prop1: String, + pub prop2: String, +} +``` + +### Reading querystring parameters + +```rust +use http::Response; +use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_ansi(false) + .without_time() + .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .init(); + + run(service_fn(function_handler)).await +} + +pub async fn function_handler(event: Request) -> Result { + let name = event.query_string_parameters() + .first("name") + .unwrap_or_else(|| "stranger") + .to_string(); + + // Represents an HTTP response + let response = Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(json!({ + "message": format!("Hello, {}!", name), + }).to_string()) + .map_err(Box::new)?; + + Ok(response) +} +``` + +### Lambda Request Authorizer + +Because **`lambda-http`** is an abstraction, we cannot use it for the Lambda Request Authorizer case. +If you remove the abstraction, you need to handle the request/response for your service. + + +```rust +use aws_lambda_events::apigw::{ + ApiGatewayCustomAuthorizerRequestTypeRequest, ApiGatewayCustomAuthorizerResponse, ApiGatewayCustomAuthorizerPolicy, IamPolicyStatement, +}; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_ansi(false) + .without_time() + .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .init(); + + run(service_fn(function_handler)).await +} + +pub async fn function_handler(event: LambdaEvent) -> Result { + // do something with the event payload + let method_arn = event.payload.method_arn.unwrap(); + // for example we could use the authorization header + if let Some(token) = event.payload.headers.get("authorization") { + // do something + + return Ok(custom_authorizer_response( + "ALLOW", + "some_principal", + &method_arn, + )); + } + + Ok(custom_authorizer_response( + &"DENY".to_string(), + "", + &method_arn)) +} + +pub fn custom_authorizer_response(effect: &str, principal: &str, method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { + let stmt = IamPolicyStatement { + action: vec!["execute-api:Invoke".to_string()], + resource: vec![method_arn.to_owned()], + effect: Some(effect.to_owned()), + }; + let policy = ApiGatewayCustomAuthorizerPolicy { + version: Some("2012-10-17".to_string()), + statement: vec![stmt], + }; + ApiGatewayCustomAuthorizerResponse { + principal_id: Some(principal.to_owned()), + policy_document: policy, + context: json!({ "email": principal }), // https://github.com/awslabs/aws-lambda-rust-runtime/discussions/548 + usage_identifier_key: None, + } +} +``` + +## Passing the Lambda execution context initialisation to the handler + +One of the [best practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. + +```rust +use aws_sdk_dynamodb::model::AttributeValue; +use chrono::Utc; +use http::Response; +use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_ansi(false) + .without_time() + .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .init(); + + let config = aws_config::from_env() + .load() + .await; + + let dynamodb_client = aws_sdk_dynamodb::Client::new(&config); + + run(service_fn(|event: Request| function_handler(&dynamodb_client, event))).await +} + +pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Request) -> Result { + let table = std::env::var("TABLE_NAME").expect("TABLE_NAME must be set"); + + let name = event.query_string_parameters() + .first("name") + .unwrap_or_else(|| "stranger") + .to_string(); + + dynamodb_client + .put_item() + .table_name(table) + .item("ID", AttributeValue::S(Utc::now().timestamp().to_string())) + .item("name", AttributeValue::S(name.to_owned())) + .send() + .await?; + + // Represents an HTTP response + let response = Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(json!({ + "message": format!("Hello, {}!", name), + }).to_string()) + .map_err(Box::new)?; + + Ok(response) +} +``` \ No newline at end of file From 1fc2ccbca3f487d2848c6c870e1d86867a25642b Mon Sep 17 00:00:00 2001 From: Lihu Wu <94906995+twu-AWS@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:37:06 +0800 Subject: [PATCH 138/394] add x-ray trace id header in lambda-http (#557) * fix for Github issue #552 * Github issue #552 fmt check * github issue #552 apply clippy fix * remove unnessecory comment --- lambda-http/src/request.rs | 17 ++++++++++++++--- lambda-runtime/src/lib.rs | 6 ++++-- lambda-runtime/src/types.rs | 6 ++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index e316a5a2..08a90744 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -16,12 +16,12 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; use aws_lambda_events::{encodings::Body, query_map::QueryMap}; use http::header::HeaderName; -use http::HeaderMap; +use http::{HeaderMap, HeaderValue}; use serde::Deserialize; use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; -use std::{io::Read, mem}; +use std::{env, io::Read, mem}; use url::Url; /// Internal representation of an Lambda http event from @@ -119,8 +119,9 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request http::Request { let http_method = ag.http_method; @@ -179,6 +187,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { // multi-value_headers our cannoncial source of request headers let mut headers = ag.multi_value_headers; headers.extend(ag.headers); + update_xray_trace_id_header(&mut headers); let base64 = ag.is_base64_encoded.unwrap_or_default(); let mut req = builder @@ -229,6 +238,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { // multi-value_headers our cannoncial source of request headers let mut headers = alb.multi_value_headers; headers.extend(alb.headers); + update_xray_trace_id_header(&mut headers); let base64 = alb.is_base64_encoded; @@ -291,6 +301,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< // multi-value_headers our cannoncial source of request headers let mut headers = ag.multi_value_headers; headers.extend(ag.headers); + update_xray_trace_id_header(&mut headers); let base64 = ag.is_base64_encoded.unwrap_or_default(); let mut req = builder diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 008237e5..7664a499 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -134,8 +134,10 @@ where let request_id = &ctx.request_id.clone(); let xray_trace_id = &ctx.xray_trace_id.clone(); - env::set_var("_X_AMZN_TRACE_ID", xray_trace_id); - + match xray_trace_id { + Some(trace_id) => env::set_var("_X_AMZN_TRACE_ID", trace_id), + None => env::remove_var("_X_AMZN_TRACE_ID"), + } let body = match serde_json::from_slice(&body) { Ok(body) => body, Err(err) => { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index ddf5d00b..17eb312b 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -90,7 +90,7 @@ pub struct Context { /// The ARN of the Lambda function being invoked. pub invoked_function_arn: String, /// The X-Ray trace ID for the current invocation. - pub xray_trace_id: String, + pub xray_trace_id: Option, /// The client context object sent by the AWS mobile SDK. This field is /// empty unless the function is invoked using an AWS mobile SDK. pub client_context: Option, @@ -139,9 +139,7 @@ impl TryFrom for Context { .to_owned(), xray_trace_id: headers .get("lambda-runtime-trace-id") - .unwrap_or(&HeaderValue::from_static("")) - .to_str()? - .to_owned(), + .map(|v| String::from_utf8_lossy(v.as_bytes()).to_string()), client_context, identity, ..Default::default() From 8010e4f7d798a476a7e2dcb3a91cc985e1bd1409 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Thu, 27 Oct 2022 22:37:45 -0500 Subject: [PATCH 139/394] Bump patch version of lambda-http (#556) This commit updates the version of `lambda-http` to 0.7.1. The crate now depends on `aws-lambda-events` 0.7.2 or higher whereas it previously depended on that of ^0.7. This crate currently does not compile against `aws-lambda-events` 0.7.0 or 0.7.1. In `aws-lambda-events` 0.7.0, the module `query_map` is private when it should be public for `lambda-http` to compile. Similarly, in `aws-lambda-events` 0.7.1, the trait bound `QueryMap: FromStr` is not satisfied so `lambda-http` does not compile either. Co-authored-by: Saito --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 51d7194c..387a5e2d 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.7.0" +version = "0.7.1" authors = [ "David Calavera ", "Harold Sun " @@ -38,7 +38,7 @@ url = "2.2.2" percent-encoding = "2.2.0" [dependencies.aws_lambda_events] -version = "^0.7" +version = "^0.7.2" default-features = false features = ["alb", "apigw"] From 9baeb986443bf88af1799e6bee37b606b8170d55 Mon Sep 17 00:00:00 2001 From: Damian Carrillo Date: Fri, 28 Oct 2022 09:49:20 -0500 Subject: [PATCH 140/394] Correct a misspelled word (#558) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ff1ebc2..70573d94 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ See other installation options in [the Cargo Lambda documentation](https://www.c ### Your first function -To create your first function, run Cargo Lambda with the [subcomand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: +To create your first function, run Cargo Lambda with the [subcommand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: ``` cargo lambda new YOUR_FUNCTION_NAME From 213e880dea47776f9d8ea65d368b2e464c9b1e76 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 31 Oct 2022 07:41:44 -0700 Subject: [PATCH 141/394] Add example of SQS integration (#560) Signed-off-by: David Calavera Signed-off-by: David Calavera --- examples/basic-lambda/Cargo.toml | 2 -- examples/basic-sqs/Cargo.toml | 23 +++++++++++++++++++++++ examples/basic-sqs/README.md | 13 +++++++++++++ examples/basic-sqs/src/main.rs | 32 ++++++++++++++++++++++++++++++++ examples/check-examples.sh | 5 +++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 examples/basic-sqs/Cargo.toml create mode 100644 examples/basic-sqs/README.md create mode 100644 examples/basic-sqs/src/main.rs diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index ebf36913..e9e8d635 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -16,5 +16,3 @@ serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml new file mode 100644 index 00000000..55da60e0 --- /dev/null +++ b/examples/basic-sqs/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "basic-sqs" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws_lambda_events = "0.7.2" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-sqs/README.md b/examples/basic-sqs/README.md new file mode 100644 index 00000000..16731de1 --- /dev/null +++ b/examples/basic-sqs/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function that receives events from SQS + +This example shows how to process events from a SQS queue. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` \ No newline at end of file diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs new file mode 100644 index 00000000..24fbd573 --- /dev/null +++ b/examples/basic-sqs/src/main.rs @@ -0,0 +1,32 @@ +use aws_lambda_events::event::sqs::SqsEventObj; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; + +/// Object that you send to SQS and plan to process on the function. +#[derive(Deserialize, Serialize)] +struct Data { + id: String, + text: String, +} + +/// This is the main body for the function. +/// You can use the data sent into SQS here. +async fn function_handler(event: LambdaEvent>) -> Result<(), Error> { + let data = &event.payload.records[0].body; + tracing::info!(id = ?data.id, text = ?data.text, "data received from SQS"); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + run(service_fn(function_handler)).await +} diff --git a/examples/check-examples.sh b/examples/check-examples.sh index 28824579..f55bced9 100755 --- a/examples/check-examples.sh +++ b/examples/check-examples.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash set -e +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +export CARGO_TARGET_DIR="$SCRIPT_DIR/../target" + +echo "==> Using shared target directory: $CARGO_TARGET_DIR" + for f in *; do if [ -d "$f" ]; then echo "==> Checking example: $f" From 996fb9b1966427293f6a45831453e7e4e34d4f8f Mon Sep 17 00:00:00 2001 From: Justin Knoetzke Date: Tue, 8 Nov 2022 19:53:23 +0200 Subject: [PATCH 142/394] http-dynamodb example (#562) * First commit. * Changed project name. * First set of edits from PR. * Fixed serde derserialization * Returned JSON of object commited to Dynamodb * Replaced printf with cloudwatch statement. * Added README, cleaned up comments. * Code formatting. Implemented clone on struct. * Replaced custom clone with derive. * Added 200 and 400 response codes. Co-authored-by: Justin Knoetzke --- examples/http-dynamodb/Cargo.toml | 25 ++++++++ examples/http-dynamodb/README.md | 19 ++++++ examples/http-dynamodb/src/main.rs | 94 ++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 examples/http-dynamodb/Cargo.toml create mode 100644 examples/http-dynamodb/README.md create mode 100644 examples/http-dynamodb/src/main.rs diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml new file mode 100644 index 00000000..73ff7c24 --- /dev/null +++ b/examples/http-dynamodb/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "http-dynamodb" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +simple-error = "0.2.3" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +aws-sdk-dynamodb = "0.21.0" +aws-config = "0.51.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1.37"} +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + + diff --git a/examples/http-dynamodb/README.md b/examples/http-dynamodb/README.md new file mode 100644 index 00000000..cb53c868 --- /dev/null +++ b/examples/http-dynamodb/README.md @@ -0,0 +1,19 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` + +Setting up Dynamodb + +1. Log into your account. +2. Create a Dynamodb table with the name 'lambda_dyno_example' with the partition key of "username". +3. Create IAM role with the permissions for Lambda, Cloudwatch and Dynamodb. + + diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs new file mode 100644 index 00000000..2733e65d --- /dev/null +++ b/examples/http-dynamodb/src/main.rs @@ -0,0 +1,94 @@ +use aws_sdk_dynamodb::model::AttributeValue; +use aws_sdk_dynamodb::{Client, Error as OtherError}; +use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use tracing::info; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct Item { + pub p_type: String, + pub age: String, + pub username: String, + pub first: String, + pub last: String, +} + +/// This is the main body for the function. +/// Write your code inside it. +/// You can see more examples in Runtime's repository: +/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +async fn function_handler(event: Request) -> Result, Error> { + // Extract some useful information from the request + let body = event.body(); + let s = std::str::from_utf8(&body).expect("invalid utf-8 sequence"); + //Log into Cloudwatch + info!(payload = %s, "JSON Payload received"); + + //Serialze JSON into struct. + //If JSON is incorrect, send back 400 with error. + let item = match serde_json::from_str::(s) { + Ok(item) => item, + Err(err) => { + let resp = Response::builder() + .status(400) + .header("content-type", "text/html") + .body(err.to_string().into()) + .map_err(Box::new)?; + return Ok(resp); + } + }; + + //Get config from environment. + let config = aws_config::load_from_env().await; + //Create the DynamoDB client. + let client = Client::new(&config); + + //Insert into the table. + add_item(&client, item.clone(), "lambda_dyno_example").await?; + + //Deserialize into json to return in the Response + let j = serde_json::to_string(&item)?; + + //Send back a 200 - success + let resp = Response::builder() + .status(200) + .header("content-type", "text/html") + .body(j.into()) + .map_err(Box::new)?; + Ok(resp) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + run(service_fn(function_handler)).await +} + +// Add an item to a table. +// snippet-start:[dynamodb.rust.add-item] +pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), OtherError> { + let user_av = AttributeValue::S(item.username); + let type_av = AttributeValue::S(item.p_type); + let age_av = AttributeValue::S(item.age); + let first_av = AttributeValue::S(item.first); + let last_av = AttributeValue::S(item.last); + + let request = client + .put_item() + .table_name(table) + .item("username", user_av) + .item("account_type", type_av) + .item("age", age_av) + .item("first_name", first_av) + .item("last_name", last_av); + + info!("adding item to DynamoDB"); + + let _resp = request.send().await?; + + Ok(()) +} From 1e2f7821a72fc760303f332598a54e20e4bead01 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 13 Nov 2022 11:02:45 -0800 Subject: [PATCH 143/394] Fix cognito metadata (#564) * Fix Cognito's serialization format Signed-off-by: David Calavera * Fix Identity and Client metadata Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-runtime/src/types.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 17eb312b..1cfab2fc 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -57,23 +57,31 @@ pub struct ClientContext { #[serde(rename_all = "camelCase")] pub struct ClientApplication { /// The mobile app installation id + #[serde(alias = "installation_id")] pub installation_id: String, /// The app title for the mobile app as registered with AWS' mobile services. + #[serde(alias = "app_title")] pub app_title: String, /// The version name of the application as registered with AWS' mobile services. + #[serde(alias = "app_version_name")] pub app_version_name: String, /// The app version code. + #[serde(alias = "app_version_code")] pub app_version_code: String, /// The package name for the mobile application invoking the function + #[serde(alias = "app_package_name")] pub app_package_name: String, } /// Cognito identity information sent with the event #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct CognitoIdentity { /// The unique identity id for the Cognito credentials invoking the function. + #[serde(alias = "cognitoIdentityId", alias = "identity_id")] pub identity_id: String, /// The identity pool id the caller is "registered" with. + #[serde(alias = "cognitoIdentityPoolId", alias = "identity_pool_id")] pub identity_pool_id: String, } From 826d4e607ec585436fac39b0d690f879e338766e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 14 Nov 2022 08:11:47 -0800 Subject: [PATCH 144/394] Fix warnings in GitHub Actions (#565) * Fix warnings in GitHub Actions Remove deprecated syntaxes and node versions. Signed-off-by: David Calavera * Update checkout action Signed-off-by: David Calavera Signed-off-by: David Calavera --- .github/workflows/build.yml | 49 +++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a33eca21..370d8249 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: os: - ubuntu-latest - macOS-latest - rust: + toolchain: - "1.62.0" # Current MSRV - stable - beta @@ -25,11 +25,12 @@ jobs: env: RUST_BACKTRACE: 1 steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - toolchain: ${{ matrix.rust }} - override: true + toolchain: ${{ matrix.toolchain }} + - uses: Swatinem/rust-cache@v2 + - name: Build run: cargo build --all --verbose env: @@ -40,38 +41,29 @@ jobs: env: TARGET: ${{ matrix.target }} continue-on-error: ${{ matrix.allow_failure }} - fmt: + formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@master with: toolchain: stable - components: rustfmt - override: true + - uses: Swatinem/rust-cache@v2 + - name: Run fmt check run: cargo fmt --all -- --check - clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: clippy - override: true - name: Run clippy check - run: cargo clippy + run: cargo clippy --all-features -- -D warnings check-examples: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: toolchain: stable - components: clippy - override: true + - uses: Swatinem/rust-cache@v2 + - name: Check examples working-directory: examples shell: bash @@ -84,9 +76,12 @@ jobs: runs-on: ubuntu-latest needs: [build] steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Generate Docs run: | cargo doc --no-deps From 0839125e7df7f03a2721c77912eecec987a2a25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Sumis=C5=82awski?= Date: Mon, 14 Nov 2022 17:13:43 +0100 Subject: [PATCH 145/394] Add support for Telemetry API (#563) * Add support for Telemetry API * Update lambda-extension/src/extension.rs Co-authored-by: David Calavera * derive Eq where possible * Add simple examples of using telemetry API Co-authored-by: David Calavera --- examples/extension-telemetry-basic/Cargo.toml | 20 + examples/extension-telemetry-basic/README.md | 14 + .../extension-telemetry-basic/src/main.rs | 59 +++ lambda-extension/README.md | 41 +- lambda-extension/src/extension.rs | 132 ++++- lambda-extension/src/lib.rs | 2 + lambda-extension/src/requests.rs | 28 +- lambda-extension/src/telemetry.rs | 449 ++++++++++++++++++ 8 files changed, 732 insertions(+), 13 deletions(-) create mode 100644 examples/extension-telemetry-basic/Cargo.toml create mode 100644 examples/extension-telemetry-basic/README.md create mode 100644 examples/extension-telemetry-basic/src/main.rs create mode 100644 lambda-extension/src/telemetry.rs diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml new file mode 100644 index 00000000..869b604d --- /dev/null +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "extension-telemetry-basic" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda-extension = { path = "../../lambda-extension" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros", "rt"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + + diff --git a/examples/extension-telemetry-basic/README.md b/examples/extension-telemetry-basic/README.md new file mode 100644 index 00000000..fe9c26ca --- /dev/null +++ b/examples/extension-telemetry-basic/README.md @@ -0,0 +1,14 @@ +# AWS Lambda Telemetry extension example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release --extension` +3. Deploy the extension as a layer with `cargo lambda deploy --extension` + +The last command will give you an ARN for the extension layer that you can use in your functions. + + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs new file mode 100644 index 00000000..d08bff38 --- /dev/null +++ b/examples/extension-telemetry-basic/src/main.rs @@ -0,0 +1,59 @@ +use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; +use tracing::info; + +async fn handler(events: Vec) -> Result<(), Error> { + for event in events { + match event.record { + LambdaTelemetryRecord::Function(record) => info!("[logs] [function] {}", record), + LambdaTelemetryRecord::PlatformInitStart { + initialization_type: _, + phase: _, + runtime_version: _, + runtime_version_arn: _, + } => info!("[platform] Initialization started"), + LambdaTelemetryRecord::PlatformInitRuntimeDone { + initialization_type: _, + phase: _, + status: _, + error_type: _, + spans: _, + } => info!("[platform] Initialization finished"), + LambdaTelemetryRecord::PlatformStart { + request_id, + version: _, + tracing: _, + } => info!("[platform] Handling of request {} started", request_id), + LambdaTelemetryRecord::PlatformRuntimeDone { + request_id, + status: _, + error_type: _, + metrics: _, + spans: _, + tracing: _, + } => info!("[platform] Handling of request {} finished", request_id), + _ => (), + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` + // While `tracing` is used internally, `log` can be used as well if preferred. + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let telemetry_processor = SharedService::new(service_fn(handler)); + + Extension::new() + .with_telemetry_processor(telemetry_processor) + .run() + .await?; + + Ok(()) +} diff --git a/lambda-extension/README.md b/lambda-extension/README.md index 0f132d75..ee606c58 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -8,7 +8,7 @@ ### Simple extension -The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch. +The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events. ```rust,no_run use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; @@ -75,6 +75,45 @@ async fn main() -> Result<(), Error> { ``` +### Telemetry processor extension + +```rust,no_run +use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; +use tracing::info; + +async fn handler(events: Vec) -> Result<(), Error> { + for event in events { + match event.record { + LambdaTelemetryRecord::Function(record) => { + // do something with the function log record + }, + LambdaTelemetryRecord::PlatformInitStart { + initialization_type: _, + phase: _, + runtime_version: _, + runtime_version_arn: _, + } => { + // do something with the PlatformInitStart event + }, + // more types of telemetry events are available + _ => (), + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let telemetry_processor = SharedService::new(service_fn(handler)); + + Extension::new().with_telemetry_processor(telemetry_processor).run().await?; + + Ok(()) +} + +``` + ## Deployment Lambda extensions can be added to your functions either using [Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#using-extensions-config), or adding them to [containers images](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#invocation-extensions-images). diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index ec83ce71..d5bfcbd9 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -9,12 +9,17 @@ use tokio_stream::StreamExt; use tower::{service_fn, MakeService, Service, ServiceExt}; use tracing::{error, trace}; -use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent}; +use crate::{ + logs::*, + requests::{self, Api}, + telemetry_wrapper, Error, ExtensionError, LambdaEvent, LambdaTelemetry, NextEvent, +}; const DEFAULT_LOG_PORT_NUMBER: u16 = 9002; +const DEFAULT_TELEMETRY_PORT_NUMBER: u16 = 9003; -/// An Extension that runs event and log processors -pub struct Extension<'a, E, L> { +/// An Extension that runs event, log and telemetry processors +pub struct Extension<'a, E, L, T> { extension_name: Option<&'a str>, events: Option<&'a [&'a str]>, events_processor: E, @@ -22,9 +27,13 @@ pub struct Extension<'a, E, L> { logs_processor: Option, log_buffering: Option, log_port_number: u16, + telemetry_types: Option<&'a [&'a str]>, + telemetry_processor: Option, + telemetry_buffering: Option, + telemetry_port_number: u16, } -impl<'a> Extension<'a, Identity, MakeIdentity>> { +impl<'a> Extension<'a, Identity, MakeIdentity>, MakeIdentity>> { /// Create a new base [`Extension`] with a no-op events processor pub fn new() -> Self { Extension { @@ -35,17 +44,23 @@ impl<'a> Extension<'a, Identity, MakeIdentity>> { log_buffering: None, logs_processor: None, log_port_number: DEFAULT_LOG_PORT_NUMBER, + telemetry_types: None, + telemetry_buffering: None, + telemetry_processor: None, + telemetry_port_number: DEFAULT_TELEMETRY_PORT_NUMBER, } } } -impl<'a> Default for Extension<'a, Identity, MakeIdentity>> { +impl<'a> Default + for Extension<'a, Identity, MakeIdentity>, MakeIdentity>> +{ fn default() -> Self { Self::new() } } -impl<'a, E, L> Extension<'a, E, L> +impl<'a, E, L, T> Extension<'a, E, L, T> where E: Service, E::Future: Future>, @@ -58,6 +73,14 @@ where L::Error: Into> + fmt::Debug, L::MakeError: Into> + fmt::Debug, L::Future: Send, + + // Fixme: 'static bound might be too restrictive + T: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, + T::Service: Service, Response = ()> + Send + Sync, + >>::Future: Send + 'a, + T::Error: Into> + fmt::Debug, + T::MakeError: Into> + fmt::Debug, + T::Future: Send, { /// Create a new [`Extension`] with a given extension name pub fn with_extension_name(self, extension_name: &'a str) -> Self { @@ -77,7 +100,7 @@ where } /// Create a new [`Extension`] with a service that receives Lambda events. - pub fn with_events_processor(self, ep: N) -> Extension<'a, N, L> + pub fn with_events_processor(self, ep: N) -> Extension<'a, N, L, T> where N: Service, N::Future: Future>, @@ -91,11 +114,15 @@ where log_buffering: self.log_buffering, logs_processor: self.logs_processor, log_port_number: self.log_port_number, + telemetry_types: self.telemetry_types, + telemetry_buffering: self.telemetry_buffering, + telemetry_processor: self.telemetry_processor, + telemetry_port_number: self.telemetry_port_number, } } /// Create a new [`Extension`] with a service that receives Lambda logs. - pub fn with_logs_processor(self, lp: N) -> Extension<'a, E, N> + pub fn with_logs_processor(self, lp: N) -> Extension<'a, E, N, T> where N: Service<()>, N::Future: Future>, @@ -109,6 +136,10 @@ where log_types: self.log_types, log_buffering: self.log_buffering, log_port_number: self.log_port_number, + telemetry_types: self.telemetry_types, + telemetry_buffering: self.telemetry_buffering, + telemetry_processor: self.telemetry_processor, + telemetry_port_number: self.telemetry_port_number, } } @@ -137,6 +168,53 @@ where } } + /// Create a new [`Extension`] with a service that receives Lambda telemetry data. + pub fn with_telemetry_processor(self, lp: N) -> Extension<'a, E, L, N> + where + N: Service<()>, + N::Future: Future>, + N::Error: Into> + fmt::Display, + { + Extension { + telemetry_processor: Some(lp), + events_processor: self.events_processor, + extension_name: self.extension_name, + events: self.events, + log_types: self.log_types, + log_buffering: self.log_buffering, + logs_processor: self.logs_processor, + log_port_number: self.log_port_number, + telemetry_types: self.telemetry_types, + telemetry_buffering: self.telemetry_buffering, + telemetry_port_number: self.telemetry_port_number, + } + } + + /// Create a new [`Extension`] with a list of telemetry types to subscribe. + /// The only accepted telemetry types are `function`, `platform`, and `extension`. + pub fn with_telemetry_types(self, telemetry_types: &'a [&'a str]) -> Self { + Extension { + telemetry_types: Some(telemetry_types), + ..self + } + } + + /// Create a new [`Extension`] with specific configuration to buffer telemetry. + pub fn with_telemetry_buffering(self, lb: LogBuffering) -> Self { + Extension { + telemetry_buffering: Some(lb), + ..self + } + } + + /// Create a new [`Extension`] with a different port number to listen to telemetry. + pub fn with_telemetry_port_number(self, port_number: u16) -> Self { + Extension { + telemetry_port_number: port_number, + ..self + } + } + /// Execute the given extension pub async fn run(self) -> Result<(), Error> { let client = &Client::builder().build()?; @@ -166,7 +244,8 @@ where trace!("Log processor started"); // Call Logs API to start receiving events - let req = requests::subscribe_logs_request( + let req = requests::subscribe_request( + Api::LogsApi, extension_id, self.log_types, self.log_buffering, @@ -179,6 +258,41 @@ where trace!("Registered extension with Logs API"); } + if let Some(mut telemetry_processor) = self.telemetry_processor { + trace!("Telemetry processor found"); + // Spawn task to run processor + let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number)); + let make_service = service_fn(move |_socket: &AddrStream| { + trace!("Creating new telemetry processor Service"); + let service = telemetry_processor.make_service(()); + async move { + let service = Arc::new(Mutex::new(service.await?)); + Ok::<_, T::MakeError>(service_fn(move |req| telemetry_wrapper(service.clone(), req))) + } + }); + let server = Server::bind(&addr).serve(make_service); + tokio::spawn(async move { + if let Err(e) = server.await { + error!("Error while running telemetry processor: {}", e); + } + }); + trace!("Telemetry processor started"); + + // Call Telemetry API to start receiving events + let req = requests::subscribe_request( + Api::TelemetryApi, + extension_id, + self.telemetry_types, + self.telemetry_buffering, + self.telemetry_port_number, + )?; + let res = client.call(req).await?; + if res.status() != http::StatusCode::OK { + return Err(ExtensionError::boxed("unable to initialize the telemetry api")); + } + trace!("Registered extension with Telemetry API"); + } + let incoming = async_stream::stream! { loop { trace!("Waiting for next event (incoming loop)"); diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index e3e72eba..796eb3ef 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -17,6 +17,8 @@ mod events; pub use events::*; mod logs; pub use logs::*; +mod telemetry; +pub use telemetry::*; /// Include several request builders to interact with the Extension API. pub mod requests; diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 6cff70b6..64485d35 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -29,7 +29,29 @@ pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result< Ok(req) } -pub(crate) fn subscribe_logs_request( +pub(crate) enum Api { + LogsApi, + TelemetryApi, +} + +impl Api { + pub(crate) fn schema_version(&self) -> &str { + match *self { + Api::LogsApi => "2021-03-18", + Api::TelemetryApi => "2022-07-01", + } + } + + pub(crate) fn uri(&self) -> &str { + match *self { + Api::LogsApi => "/2020-08-15/logs", + Api::TelemetryApi => "/2022-07-01/telemetry", + } + } +} + +pub(crate) fn subscribe_request( + api: Api, extension_id: &str, types: Option<&[&str]>, buffering: Option, @@ -38,7 +60,7 @@ pub(crate) fn subscribe_logs_request( let types = types.unwrap_or(&["platform", "function"]); let data = serde_json::json!({ - "schemaVersion": "2021-03-18", + "schemaVersion": api.schema_version(), "types": types, "buffering": buffering.unwrap_or_default(), "destination": { @@ -49,7 +71,7 @@ pub(crate) fn subscribe_logs_request( let req = build_request() .method(Method::PUT) - .uri("/2020-08-15/logs") + .uri(api.uri()) .header(EXTENSION_ID_HEADER, extension_id) .body(Body::from(serde_json::to_string(&data)?))?; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs new file mode 100644 index 00000000..0ccaa7fc --- /dev/null +++ b/lambda-extension/src/telemetry.rs @@ -0,0 +1,449 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; +use std::{boxed::Box, fmt, sync::Arc}; +use tokio::sync::Mutex; +use tower::Service; +use tracing::{error, trace}; + +/// Payload received from the Telemetry API +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct LambdaTelemetry { + /// Time when the telemetry was generated + pub time: DateTime, + /// Telemetry record entry + #[serde(flatten)] + pub record: LambdaTelemetryRecord, +} + +/// Record in a LambdaTelemetry entry +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(tag = "type", content = "record", rename_all = "lowercase")] +pub enum LambdaTelemetryRecord { + /// Function log records + Function(String), + + /// Extension log records + Extension(String), + + /// Platform init start record + #[serde(rename = "platform.initStart", rename_all = "camelCase")] + PlatformInitStart { + /// Type of initialization + initialization_type: InitType, + /// Phase of initialisation + phase: InitPhase, + /// Lambda runtime version + runtime_version: Option, + /// Lambda runtime version ARN + runtime_version_arn: Option, + }, + /// Platform init runtime done record + #[serde(rename = "platform.initRuntimeDone", rename_all = "camelCase")] + PlatformInitRuntimeDone { + /// Type of initialization + initialization_type: InitType, + /// Phase of initialisation + phase: Option, + /// Status of initalization + status: Status, + /// When the status = failure, the error_type describes what kind of error occurred + error_type: Option, + /// Spans + #[serde(default)] + spans: Vec, + }, + /// Platform init start record + #[serde(rename = "platform.initReport", rename_all = "camelCase")] + PlatformInitReport { + /// Type of initialization + initialization_type: InitType, + /// Phase of initialisation + phase: InitPhase, + /// Metrics + metrics: InitReportMetrics, + /// Spans + #[serde(default)] + spans: Vec, + }, + /// Record marking start of an invocation + #[serde(rename = "platform.start", rename_all = "camelCase")] + PlatformStart { + /// Request identifier + request_id: String, + /// Version of the Lambda function + version: Option, + /// Trace Context + tracing: Option, + }, + /// Record marking the completion of an invocation + #[serde(rename = "platform.runtimeDone", rename_all = "camelCase")] + PlatformRuntimeDone { + /// Request identifier + request_id: String, + /// Status of the invocation + status: Status, + /// When unsuccessful, the error_type describes what kind of error occurred + error_type: Option, + /// Metrics corresponding to the runtime + metrics: Option, + /// Spans + #[serde(default)] + spans: Vec, + /// Trace Context + tracing: Option, + }, + /// Platfor report record + #[serde(rename = "platform.report", rename_all = "camelCase")] + PlatformReport { + /// Request identifier + request_id: String, + /// Status of the invocation + status: Status, + /// When unsuccessful, the error_type describes what kind of error occurred + error_type: Option, + /// Metrics + metrics: ReportMetrics, + /// Spans + #[serde(default)] + spans: Vec, + /// Trace Context + tracing: Option, + }, + + /// Extension-specific record + #[serde(rename = "platform.extension", rename_all = "camelCase")] + PlatformExtension { + /// Name of the extension + name: String, + /// State of the extension + state: String, + /// Events sent to the extension + events: Vec, + }, + /// Telemetry processor-specific record + #[serde(rename = "platform.telemetrySubscription", rename_all = "camelCase")] + PlatformTelemetrySubscription { + /// Name of the extension + name: String, + /// State of the extensions + state: String, + /// Types of records sent to the extension + types: Vec, + }, + /// Record generated when the telemetry processor is falling behind + #[serde(rename = "platform.logsDropped", rename_all = "camelCase")] + PlatformLogsDropped { + /// Reason for dropping the logs + reason: String, + /// Number of records dropped + dropped_records: u64, + /// Total size of the dropped records + dropped_bytes: u64, + }, +} + +/// Type of Initialization +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum InitType { + /// Initialised on demand + OnDemand, + /// Initialized to meet the provisioned concurrency + ProvisionedConcurrency, + /// SnapStart + SnapStart, +} + +/// Phase in which initialization occurs +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum InitPhase { + /// Initialization phase + Init, + /// Invocation phase + Invoke, +} + +/// Status of invocation/initialization +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum Status { + /// Success + Success, + /// Error + Error, + /// Failure + Failure, + /// Timeout + Timeout, +} + +/// Span +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Span { + /// Duration of the span + pub duration_ms: f64, + /// Name of the span + pub name: String, + /// Start of the span + pub start: DateTime, +} + +/// Tracing Context +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TraceContext { + /// Span ID + pub span_id: Option, + /// Type of tracing + pub r#type: TracingType, + /// A string containing tracing information like trace_id. The contents may depend on the TracingType. + pub value: String, +} + +/// Type of tracing +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +pub enum TracingType { + /// Amazon trace type + #[serde(rename = "X-Amzn-Trace-Id")] + AmznTraceId, +} + +///Init report metrics +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InitReportMetrics { + /// Duration of initialization + pub duration_ms: f64, +} + +/// Report metrics +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ReportMetrics { + /// Duration in milliseconds + pub duration_ms: f64, + /// Billed duration in milliseconds + pub billed_duration_ms: u64, + /// Memory allocated in megabytes + #[serde(rename = "memorySizeMB")] + pub memory_size_mb: u64, + /// Maximum memory used for the invoke in megabytes + #[serde(rename = "maxMemoryUsedMB")] + pub max_memory_used_mb: u64, + /// Init duration in case of a cold start + #[serde(default = "Option::default")] + pub init_duration_ms: Option, + /// Restore duration in milliseconds + #[serde(default = "Option::default")] + pub restore_duration_ms: Option, +} + +/// Runtime done metrics +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct RuntimeDoneMetrics { + /// Duration in milliseconds + pub duration_ms: f64, + /// Number of bytes produced as a result of the invocation + pub produced_bytes: Option, +} + +/// Wrapper function that sends telemetry to the subscriber Service +/// +/// This takes an `hyper::Request` and transforms it into `Vec` for the +/// underlying `Service` to process. +pub(crate) async fn telemetry_wrapper( + service: Arc>, + req: hyper::Request, +) -> Result, Box> +where + S: Service, Response = ()>, + S::Error: Into> + fmt::Debug, + S::Future: Send, +{ + trace!("Received telemetry request"); + // Parse the request body as a Vec + let body = match hyper::body::to_bytes(req.into_body()).await { + Ok(body) => body, + Err(e) => { + error!("Error reading telemetry request body: {}", e); + return Ok(hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .body(hyper::Body::empty()) + .unwrap()); + } + }; + + let telemetry: Vec = match serde_json::from_slice(&body) { + Ok(telemetry) => telemetry, + Err(e) => { + error!("Error parsing telemetry: {}", e); + return Ok(hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .body(hyper::Body::empty()) + .unwrap()); + } + }; + + { + let mut service = service.lock().await; + match service.call(telemetry).await { + Ok(_) => (), + Err(err) => println!("{:?}", err), + } + } + + Ok(hyper::Response::new(hyper::Body::empty())) +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::TimeZone; + + macro_rules! deserialize_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let actual = serde_json::from_str::(&input).expect("unable to deserialize"); + + assert!(actual.record == expected); + } + )* + } + } + + deserialize_tests! { + // function + function: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#, + LambdaTelemetryRecord::Function("hello world".to_string()), + ), + + // extension + extension: ( + r#"{"time": "2020-08-20T12:31:32.123Z","type": "extension", "record": "hello world"}"#, + LambdaTelemetryRecord::Extension("hello world".to_string()), + ), + + // platform.start + platform_start: ( + r#"{"time":"2022-10-21T14:05:03.165Z","type":"platform.start","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","version":"$LATEST","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + LambdaTelemetryRecord::PlatformStart { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + version: Some("$LATEST".to_string()), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + ), + // platform.initStart + platform_init_start: ( + r#"{"time":"2022-10-19T13:52:15.636Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#, + LambdaTelemetryRecord::PlatformInitStart { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + runtime_version: None, + runtime_version_arn: None, + }, + ), + // platform.runtimeDone + platform_runtime_done: ( + r#"{"time":"2022-10-21T14:05:05.764Z","type":"platform.runtimeDone","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"},"spans":[{"name":"responseLatency","start":"2022-10-21T14:05:03.165Z","durationMs":2598.0},{"name":"responseDuration","start":"2022-10-21T14:05:05.763Z","durationMs":0.0}],"metrics":{"durationMs":2599.0,"producedBytes":8}}}"#, + LambdaTelemetryRecord::PlatformRuntimeDone { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: Some(RuntimeDoneMetrics { + duration_ms: 2599.0, + produced_bytes: Some(8), + }), + spans: vec!( + Span { + name:"responseLatency".to_string(), + start: Utc.ymd(2022, 10, 21).and_hms_milli(14, 05, 03, 165), + duration_ms:2598.0 + }, + Span { + name:"responseDuration".to_string(), + start:Utc.ymd(2022, 10, 21).and_hms_milli(14, 05, 05, 763), + duration_ms:0.0 + }, + ), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + ), + // platform.report + platform_report: ( + r#"{"time":"2022-10-21T14:05:05.766Z","type":"platform.report","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","metrics":{"durationMs":2599.4,"billedDurationMs":2600,"memorySizeMB":128,"maxMemoryUsedMB":94,"initDurationMs":549.04},"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"},"status":"success"}}"#, + LambdaTelemetryRecord::PlatformReport { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: ReportMetrics { + duration_ms: 2599.4, + billed_duration_ms: 2600, + memory_size_mb:128, + max_memory_used_mb:94, + init_duration_ms: Some(549.04), + restore_duration_ms: None, + }, + spans: Vec::new(), + tracing: Some(TraceContext { + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + ), + // platform.telemetrySubscription + platform_telemetry_subscription: ( + r#"{"time":"2022-10-19T13:52:15.667Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#, + LambdaTelemetryRecord::PlatformTelemetrySubscription { + name: "my-extension".to_string(), + state: "Subscribed".to_string(), + types: vec!("platform".to_string(), "function".to_string()), + }, + ), + // platform.initRuntimeDone + platform_init_runtime_done: ( + r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success"}}"#, + LambdaTelemetryRecord::PlatformInitRuntimeDone { + initialization_type: InitType::OnDemand, + status: Status::Success, + phase: None, + error_type: None, + spans: Vec::new(), + }, + ), + // platform.extension + platform_extension: ( + r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#, + LambdaTelemetryRecord::PlatformExtension { + name: "my-extension".to_string(), + state: "Ready".to_string(), + events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()), + }, + ), + // platform.initReport + platform_init_report: ( + r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initReport","record":{"initializationType":"on-demand","metrics":{"durationMs":500.0},"phase":"init"}}"#, + LambdaTelemetryRecord::PlatformInitReport { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + metrics: InitReportMetrics { duration_ms: 500.0 }, + spans: Vec::new(), + } + ), + } +} From 695340bad627681a6ddb436074735d4c52ef8569 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 15 Nov 2022 07:56:49 -0800 Subject: [PATCH 146/394] Bump release versions (#566) - Release lambda_runtime 0.7.1 - Release lambda-extension 0.8.0 Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 4 ++-- lambda-integration-tests/Cargo.toml | 8 ++++---- lambda-runtime/Cargo.toml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 823e3f82..1132c6de 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "lambda-extension" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "David Calavera ", - "Harold Sun " + "Harold Sun ", ] description = "AWS Lambda Extension API" license = "Apache-2.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 116b2986..8dd28f0a 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -13,10 +13,10 @@ readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lambda_http = { path = "../lambda-http", version = "0.7" } -lambda_runtime = { path = "../lambda-runtime", version = "0.7" } -lambda-extension = { path = "../lambda-extension", version = "0.7" } +lambda_http = { path = "../lambda-http" } +lambda_runtime = { path = "../lambda-runtime" } +lambda-extension = { path = "../lambda-extension" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = "0.3" \ No newline at end of file +tracing-subscriber = "0.3" diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 14c675b6..7b39fc5e 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "lambda_runtime" -version = "0.7.0" +version = "0.7.1" authors = [ "David Calavera ", - "Harold Sun " + "Harold Sun ", ] description = "AWS Lambda Runtime" edition = "2021" From 8d7c0eba90220055e36e8cb34cd2f90d279f336a Mon Sep 17 00:00:00 2001 From: Mustafa Zaki Assagaf Date: Mon, 5 Dec 2022 23:35:25 +0700 Subject: [PATCH 147/394] Added IntoResponse implementation for (StatusCode, IntoResponse) (#573) --- lambda-http/src/response.rs | 117 +++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index b174f98e..5a5f3e9f 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -11,7 +11,7 @@ use aws_lambda_events::encodings::Body; use encoding_rs::Encoding; use http::header::CONTENT_ENCODING; use http::HeaderMap; -use http::{header::CONTENT_TYPE, Response}; +use http::{header::CONTENT_TYPE, Response, StatusCode}; use http_body::Body as HttpBody; use hyper::body::to_bytes; use mime::{Mime, CHARSET}; @@ -179,6 +179,71 @@ impl IntoResponse for serde_json::Value { } } +impl IntoResponse for (StatusCode, String) { + fn into_response(self) -> ResponseFuture { + let (status, body) = self; + Box::pin(ready( + Response::builder() + .status(status) + .body(Body::from(body)) + .expect("unable to build http::Response"), + )) + } +} + +impl IntoResponse for (StatusCode, &str) { + fn into_response(self) -> ResponseFuture { + let (status, body) = self; + Box::pin(ready( + Response::builder() + .status(status) + .body(Body::from(body)) + .expect("unable to build http::Response"), + )) + } +} + +impl IntoResponse for (StatusCode, &[u8]) { + fn into_response(self) -> ResponseFuture { + let (status, body) = self; + Box::pin(ready( + Response::builder() + .status(status) + .body(Body::from(body)) + .expect("unable to build http::Response"), + )) + } +} + +impl IntoResponse for (StatusCode, Vec) { + fn into_response(self) -> ResponseFuture { + let (status, body) = self; + Box::pin(ready( + Response::builder() + .status(status) + .body(Body::from(body)) + .expect("unable to build http::Response"), + )) + } +} + +impl IntoResponse for (StatusCode, serde_json::Value) { + fn into_response(self) -> ResponseFuture { + let (status, body) = self; + Box::pin(async move { + Response::builder() + .status(status) + .header(CONTENT_TYPE, "application/json") + .body( + serde_json::to_string(&body) + .expect("unable to serialize serde_json::Value") + .into(), + ) + .expect("unable to build http::Response") + }) + } +} + pub type ResponseFuture = Pin> + Send>>; pub trait ConvertBody { @@ -269,7 +334,7 @@ mod tests { use super::{Body, IntoResponse, LambdaResponse, RequestOrigin, X_LAMBDA_HTTP_CONTENT_ENCODING}; use http::{ header::{CONTENT_ENCODING, CONTENT_TYPE}, - Response, + Response, StatusCode, }; use hyper::Body as HyperBody; use serde_json::{self, json}; @@ -310,6 +375,54 @@ mod tests { } } + #[tokio::test] + async fn json_with_status_code_into_response() { + let response = (StatusCode::CREATED, json!({ "hello": "lambda"})).into_response().await; + match response.body() { + Body::Text(json) => assert_eq!(json, r#"{"hello":"lambda"}"#), + _ => panic!("invalid body"), + } + match response.status() { + StatusCode::CREATED => (), + _ => panic!("invalid status code"), + } + + assert_eq!( + response + .headers() + .get(CONTENT_TYPE) + .map(|h| h.to_str().expect("invalid header")), + Some("application/json") + ) + } + + #[tokio::test] + async fn text_with_status_code_into_response() { + let response = (StatusCode::CREATED, "text").into_response().await; + + match response.status() { + StatusCode::CREATED => (), + _ => panic!("invalid status code"), + } + match response.body() { + Body::Text(text) => assert_eq!(text, "text"), + _ => panic!("invalid body"), + } + } + + #[tokio::test] + async fn bytes_with_status_code_into_response() { + let response = (StatusCode::CREATED, "text".as_bytes()).into_response().await; + match response.status() { + StatusCode::CREATED => (), + _ => panic!("invalid status code"), + } + match response.body() { + Body::Binary(data) => assert_eq!(data, "text".as_bytes()), + _ => panic!("invalid body"), + } + } + #[tokio::test] async fn content_encoding_header() { // Drive the implementation by using `hyper::Body` instead of From 181c5d95bf5e710162af4f4cb194e490922231b6 Mon Sep 17 00:00:00 2001 From: llgerard <87908777+llgerard@users.noreply.github.com> Date: Fri, 16 Dec 2022 10:21:31 -0800 Subject: [PATCH 148/394] Add tracing span with request id to the handler (#577) * Add tracing span with request id to the handler * Bump span logging to INFO Tracing filters by default trim events and *spans* according to the level. This means that entering a span with a level below the threshold will be a no-op and the current span is not updated. The lambda request span is a meaningful info one, so bump to INFO level. * Remove unused import * Span name + cargo fmt --- lambda-runtime/src/lib.rs | 120 +++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 52 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 7664a499..88e66b4d 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -26,7 +26,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; -use tracing::{error, trace}; +use tracing::{error, trace, Instrument}; mod requests; #[cfg(test)] @@ -120,15 +120,6 @@ where continue; } - let body = hyper::body::to_bytes(body).await?; - trace!("response body - {}", std::str::from_utf8(&body)?); - - #[cfg(debug_assertions)] - if parts.status.is_server_error() { - error!("Lambda Runtime server returned an unexpected error"); - return Err(parts.status.to_string().into()); - } - let ctx: Context = Context::try_from(parts.headers)?; let ctx: Context = ctx.with_config(config); let request_id = &ctx.request_id.clone(); @@ -138,55 +129,80 @@ where Some(trace_id) => env::set_var("_X_AMZN_TRACE_ID", trace_id), None => env::remove_var("_X_AMZN_TRACE_ID"), } - let body = match serde_json::from_slice(&body) { - Ok(body) => body, - Err(err) => { - let req = build_event_error_request(request_id, err)?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); - return Ok(()); - } + let request_span = match xray_trace_id { + Some(trace_id) => tracing::span!( + tracing::Level::INFO, + "Lambda runtime invoke", + requestId = request_id, + xrayTraceId = trace_id + ), + None => tracing::span!(tracing::Level::INFO, "Lambda runtime invoke", requestId = request_id), }; - let req = match handler.ready().await { - Ok(handler) => { - // Catches panics outside of a `Future` - let task = - panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); - - let task = match task { - // Catches panics inside of the `Future` - Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, - Err(err) => Err(err), - }; - - match task { - Ok(response) => match response { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionRequest { - request_id, - body: response, + // Group the handling in one future and instrument it with the span + async { + let body = hyper::body::to_bytes(body).await?; + trace!("response body - {}", std::str::from_utf8(&body)?); + + #[cfg(debug_assertions)] + if parts.status.is_server_error() { + error!("Lambda Runtime server returned an unexpected error"); + return Err(parts.status.to_string().into()); + } + + let body = match serde_json::from_slice(&body) { + Ok(body) => body, + Err(err) => { + let req = build_event_error_request(request_id, err)?; + client.call(req).await.expect("Unable to send response to Runtime APIs"); + return Ok(()); + } + }; + + let req = match handler.ready().await { + Ok(handler) => { + // Catches panics outside of a `Future` + let task = + panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + + let task = match task { + // Catches panics inside of the `Future` + Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, + Err(err) => Err(err), + }; + + match task { + Ok(response) => match response { + Ok(response) => { + trace!("Ok response from handler (run loop)"); + EventCompletionRequest { + request_id, + body: response, + } + .into_req() } - .into_req() + Err(err) => build_event_error_request(request_id, err), + }, + Err(err) => { + error!("{:?}", err); + let error_type = type_name_of_val(&err); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {}", msg) + } else { + "Lambda panicked".to_string() + }; + EventErrorRequest::new(request_id, error_type, &msg).into_req() } - Err(err) => build_event_error_request(request_id, err), - }, - Err(err) => { - error!("{:?}", err); - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {}", msg) - } else { - "Lambda panicked".to_string() - }; - EventErrorRequest::new(request_id, error_type, &msg).into_req() } } - } - Err(err) => build_event_error_request(request_id, err), - }?; + Err(err) => build_event_error_request(request_id, err), + }?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); + client.call(req).await.expect("Unable to send response to Runtime APIs"); + Ok::<(), Error>(()) + } + .instrument(request_span) + .await?; } Ok(()) } From 595028eb3d9b9c1687b8e663100c41532b122380 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sat, 17 Dec 2022 08:05:40 +0800 Subject: [PATCH 149/394] make RequestContext serializable (#578) --- lambda-http/src/request.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 08a90744..ea98112f 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -17,7 +17,7 @@ use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsoc use aws_lambda_events::{encodings::Body, query_map::QueryMap}; use http::header::HeaderName; use http::{HeaderMap, HeaderValue}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; @@ -330,7 +330,7 @@ fn apigw_path_with_stage(stage: &Option, path: &str) -> String { /// Event request context as an enumeration of request contexts /// for both ALB and API Gateway and HTTP API events -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Serialize)] #[serde(untagged)] pub enum RequestContext { /// API Gateway proxy request context From 9997b9d2efa78e73ac79d77b505eb1c66897315b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 16 Dec 2022 18:14:07 -0800 Subject: [PATCH 150/394] Release version 0.7.2 (#579) Signed-off-by: David Calavera Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 5 ++--- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 387a5e2d..140742f0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "lambda_http" -version = "0.7.1" +version = "0.7.2" authors = [ "David Calavera ", - "Harold Sun " + "Harold Sun ", ] edition = "2021" description = "Application Load Balancer and API Gateway event types for AWS Lambda" @@ -46,4 +46,3 @@ features = ["alb", "apigw"] log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } - diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 7b39fc5e..cbcdc1b5 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.7.1" +version = "0.7.2" authors = [ "David Calavera ", "Harold Sun ", From ab4c2b374ca46ddc8860db7e5bed583bb272f9f8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 30 Dec 2022 17:58:36 -0800 Subject: [PATCH 151/394] Update DynamoDB example to use a shared client. (#582) Signed-off-by: David Calavera Signed-off-by: David Calavera --- examples/http-dynamodb/src/main.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 2733e65d..4496ddf4 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -16,10 +16,10 @@ pub struct Item { /// Write your code inside it. /// You can see more examples in Runtime's repository: /// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples -async fn function_handler(event: Request) -> Result, Error> { +async fn handle_request(db_client: &Client, event: Request) -> Result, Error> { // Extract some useful information from the request let body = event.body(); - let s = std::str::from_utf8(&body).expect("invalid utf-8 sequence"); + let s = std::str::from_utf8(body).expect("invalid utf-8 sequence"); //Log into Cloudwatch info!(payload = %s, "JSON Payload received"); @@ -36,14 +36,9 @@ async fn function_handler(event: Request) -> Result, Error> { return Ok(resp); } }; - - //Get config from environment. - let config = aws_config::load_from_env().await; - //Create the DynamoDB client. - let client = Client::new(&config); //Insert into the table. - add_item(&client, item.clone(), "lambda_dyno_example").await?; + add_item(db_client, item.clone(), "lambda_dyno_example").await?; //Deserialize into json to return in the Response let j = serde_json::to_string(&item)?; @@ -65,7 +60,15 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - run(service_fn(function_handler)).await + //Get config from environment. + let config = aws_config::load_from_env().await; + //Create the DynamoDB client. + let client = Client::new(&config); + + run(service_fn(|event: Request| async { + handle_request(&client, event).await + })) + .await } // Add an item to a table. From 128aef497119eb2278561bb825d2e75a138d50df Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Fri, 6 Jan 2023 14:28:59 -0500 Subject: [PATCH 152/394] Added `Context::deadline(&self) -> SystemTime` (#583) --- lambda-runtime/src/types.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 1cfab2fc..31f4c8a2 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,7 +1,11 @@ use crate::{Config, Error}; use http::{HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, convert::TryFrom}; +use std::{ + collections::HashMap, + convert::TryFrom, + time::{Duration, SystemTime}, +}; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -375,4 +379,9 @@ impl Context { ..self } } + + /// The execution deadline for the current invocation. + pub fn deadline(&self) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) + } } From 381ac09653c8f802d63badce2cf8eaf4ef407d4b Mon Sep 17 00:00:00 2001 From: llgerard <87908777+llgerard@users.noreply.github.com> Date: Mon, 9 Jan 2023 18:20:06 -0800 Subject: [PATCH 153/394] Add SQS example implementing partial batch failure (#584) --- .../Cargo.toml | 16 ++ .../README.md | 18 ++ .../src/main.rs | 159 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 examples/advanced-sqs-partial-batch-failures/Cargo.toml create mode 100644 examples/advanced-sqs-partial-batch-failures/README.md create mode 100644 examples/advanced-sqs-partial-batch-failures/src/main.rs diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml new file mode 100644 index 00000000..04067948 --- /dev/null +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "advanced-sqs-partial-batch-failures" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "^1" +serde_derive = "^1" +serde_with = { version = "^2", features = ["json"], optional = true } +serde_json = "^1" +aws_lambda_events = "0.7.3" +lambda_runtime = "0.7" +tokio = { version = "1", features = ["macros"] } +futures = "0.3" +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/README.md b/examples/advanced-sqs-partial-batch-failures/README.md new file mode 100644 index 00000000..7b10ca50 --- /dev/null +++ b/examples/advanced-sqs-partial-batch-failures/README.md @@ -0,0 +1,18 @@ +# AWS Lambda Function that receives events from SQS + +This example shows how to process events from an SQS queue using the partial batch failure feature. + +_Important note:_ your lambda sqs trigger *needs* to be configured with partial batch response support +(the ` ReportBatchItemFailures` flag set to true), otherwise failed message will be not be reprocessed. +For more details see: +https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` \ No newline at end of file diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs new file mode 100644 index 00000000..254df031 --- /dev/null +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -0,0 +1,159 @@ +use aws_lambda_events::{ + event::sqs::SqsEventObj, + sqs::{BatchItemFailure, SqsBatchResponse, SqsMessageObj}, +}; +use futures::Future; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tracing::Instrument; + +/// [To customize] Your object definition, sent to the SQS queue triggering this lambda. +#[derive(Deserialize, Serialize)] +struct Data { + text: String, +} + +/// [To customize] Your buisness logic to handle the payload of one SQS message. +async fn data_handler(data: Data) -> Result<(), Error> { + // Some processing + tracing::info!(text = ?data.text, "processing data"); + // simulate error + if data.text == "bad request" { + Err("Processing error".into()) + } else { + Ok(()) + } +} + +/// Main function for the lambda executable. +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + run_sqs_partial_batch_failure(data_handler).await +} + +/// This function will handle the message batches from SQS. +/// It calls the provided user function `f` on every message concurrently and reports to SQS +/// which message failed to be processed so that only those are retried. +/// +/// Important note: your lambda sqs trigger *needs* to be configured with partial batch response support +/// with the ` ReportBatchItemFailures` flag set to true, otherwise failed message will be dropped, +/// for more details see: +/// https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting +/// +/// +/// Note that if you are looking for parallel processing (multithread) instead of concurrent processing, +/// you can do so by spawning a task inside your function `f`. +async fn run_sqs_partial_batch_failure(f: T) -> Result<(), Error> +where + T: Fn(D) -> R, + D: DeserializeOwned, + R: Future>, +{ + run(service_fn(|e| batch_handler(|d| f(d), e))).await +} + +/// Helper function to lift the user provided `f` function from message to batch of messages. +/// See `run_sqs` for the easier function to use. +async fn batch_handler( + f: T, + event: LambdaEvent>, +) -> Result +where + T: Fn(D) -> F, + F: Future>, + D: DeserializeOwned, +{ + tracing::trace!("Handling batch size {}", event.payload.records.len()); + let create_task = |msg| { + // We need to keep the message_id to report failures to SQS + let SqsMessageObj { + message_id, body, .. + } = msg; + let span = tracing::span!(tracing::Level::INFO, "Handling SQS msg", message_id); + let task = async { + //TODO catch panics like the `run` function from lambda_runtime + f(serde_json::from_value(body)?).await + } + .instrument(span); + (message_id.unwrap_or_default(), task) + }; + let (ids, tasks): (Vec<_>, Vec<_>) = event.payload.records.into_iter().map(create_task).unzip(); + let results = futures::future::join_all(tasks).await; // Run tasks concurrently + let failure_items = ids + .into_iter() + .zip(results) + .filter_map( + // Only keep the message_id of failed tasks + |(id, res)| match res { + Ok(()) => None, + Err(err) => { + tracing::error!("Failed to process msg {id}, {err}"); + Some(id) + } + }, + ) + .map(|id| BatchItemFailure { + item_identifier: id, + }) + .collect(); + + Ok(SqsBatchResponse { + batch_item_failures: failure_items, + }) +} + +#[cfg(test)] +mod test { + use lambda_runtime::Context; + + use super::*; + + #[derive(Serialize, Deserialize, Debug)] + struct UserData { + should_error: bool, + } + async fn user_fn(data: UserData) -> Result<(), Error> { + if data.should_error { + Err("Processing Error".into()) + } else { + Ok(()) + } + } + + #[tokio::test] + async fn test() -> () { + let msg_to_fail: SqsMessageObj = serde_json::from_str( + r#"{ + "messageId": "1", + "body": "{\"should_error\": true}" + }"#, + ) + .unwrap(); + let msg_to_succeed: SqsMessageObj = serde_json::from_str( + r#"{ + "messageId": "0", + "body": "{\"should_error\" : false}" + }"#, + ) + .unwrap(); + + let lambda_event = LambdaEvent { + payload: SqsEventObj { + records: vec![msg_to_fail, msg_to_succeed], + }, + context: Context::default(), + }; + + let r = batch_handler(user_fn, lambda_event).await.unwrap(); + assert_eq!(r.batch_item_failures.len(), 1); + assert_eq!(r.batch_item_failures[0].item_identifier, "1"); + } +} From e0421037eb8dbcd9b16e1739fd9707cc5078bef6 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 11 Jan 2023 08:09:04 -0800 Subject: [PATCH 154/394] Fix invoke span by specifying a minimum version of tracing (#585) Some versions of tracing don't implement the tracing::Value trait for &std::string::String, and the runtime doesn't compile with them. By setting a minimum required version, we guarantee that the runtime compiles. Signed-off-by: David Calavera Signed-off-by: David Calavera --- .rustfmt.toml | 4 ++-- lambda-runtime/Cargo.toml | 16 +++++++++++++--- lambda-runtime/src/lib.rs | 22 +++++++++------------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index a40f0edc..d8e9ca33 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,6 +1,6 @@ -edition = "2018" +edition = "2021" # imports_granularity is unstable # # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports # imports_granularity = "Crate" # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width -max_width = 120 \ No newline at end of file +max_width = 120 diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index cbcdc1b5..6453615b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -18,16 +18,26 @@ default = ["simulated"] simulated = [] [dependencies] -tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +tokio = { version = "1.0", features = [ + "macros", + "io-util", + "sync", + "rt-multi-thread", +] } # Hyper requires the `server` feature to work on nightly -hyper = { version = "0.14.20", features = ["http1", "client", "stream", "server"] } +hyper = { version = "0.14.20", features = [ + "http1", + "client", + "stream", + "server", +] } futures = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "^1" bytes = "1.0" http = "0.2" async-stream = "0.3" -tracing = { version = "0.1", features = ["log"] } +tracing = { version = "0.1.37", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.7", path = "../lambda-runtime-api-client" } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 88e66b4d..9663b375 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -124,19 +124,15 @@ where let ctx: Context = ctx.with_config(config); let request_id = &ctx.request_id.clone(); - let xray_trace_id = &ctx.xray_trace_id.clone(); - match xray_trace_id { - Some(trace_id) => env::set_var("_X_AMZN_TRACE_ID", trace_id), - None => env::remove_var("_X_AMZN_TRACE_ID"), - } - let request_span = match xray_trace_id { - Some(trace_id) => tracing::span!( - tracing::Level::INFO, - "Lambda runtime invoke", - requestId = request_id, - xrayTraceId = trace_id - ), - None => tracing::span!(tracing::Level::INFO, "Lambda runtime invoke", requestId = request_id), + let request_span = match &ctx.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = request_id) + } }; // Group the handling in one future and instrument it with the span From 17df82fe52d0881b553f607434a640689d2c13ee Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Fri, 13 Jan 2023 08:32:26 +0800 Subject: [PATCH 155/394] Fix double cookie header for Lambda Function URL requests (#586) --- lambda-http/src/request.rs | 38 +++++++++++++++++- .../data/lambda_function_url_request.json | 40 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 lambda-http/tests/data/lambda_function_url_request.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ea98112f..b2167857 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -122,7 +122,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request Date: Fri, 13 Jan 2023 19:44:24 +0800 Subject: [PATCH 156/394] Release version 0.7.3 (#587) --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 140742f0..a2ac6250 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.7.2" +version = "0.7.3" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6453615b..37679fbb 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.7.2" +version = "0.7.3" authors = [ "David Calavera ", "Harold Sun ", From e2d40dad0b8a86f78f5312340f33c898bd07199c Mon Sep 17 00:00:00 2001 From: Matt Bajorek <17235301+mattbajorek@users.noreply.github.com> Date: Thu, 19 Jan 2023 22:27:43 -0500 Subject: [PATCH 157/394] Fix logs processor example (#588) --- lambda-extension/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/lambda-extension/README.md b/lambda-extension/README.md index ee606c58..b3f5e529 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -56,7 +56,6 @@ async fn handler(logs: Vec) -> Result<(), Error> { LambdaLogRecord::Extension(_record) => { // do something with the extension log record }, - }, _ => (), } } From cccce9d2ce413c41308be9b8afad933b7bbb7f7b Mon Sep 17 00:00:00 2001 From: Tim McNamara Date: Tue, 31 Jan 2023 05:04:06 +1300 Subject: [PATCH 158/394] Link to Cargo Lambda on first mention (#591) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70573d94..505da34e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor ## Getting started -The easiest way to start writing Lambda functions with Rust is by using Cargo Lambda. This Cargo subcommand provides several commands to help you in your journey with Rust on AWS Lambda. +The easiest way to start writing Lambda functions with Rust is by using [Cargo Lambda](https://www.cargo-lambda.info/), a related project. Cargo Lambda is a Cargo plugin, or subcommand, that provides several commands to help you in your journey with Rust on AWS Lambda. The preferred way to install Cargo Lambda is by using a package manager. From f56c7dc6e4aa7337ecdfc0a87e5ba6a723f1cf8d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 1 Feb 2023 17:39:46 -0800 Subject: [PATCH 159/394] Improve extensions API support (#593) * Improve extensions API support - Set request content-types for JSON payloads - Print the returned status codes in case of errors Signed-off-by: David Calavera * Fix clippy warnings introduced in 1.67 Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-extension/src/extension.rs | 29 +++++++++++++++------------- lambda-extension/src/logs.rs | 2 +- lambda-extension/src/requests.rs | 20 +++++++++++-------- lambda-extension/src/telemetry.rs | 2 +- lambda-http/src/ext.rs | 5 ++--- lambda-http/src/request.rs | 6 +++--- lambda-runtime-api-client/src/lib.rs | 2 +- lambda-runtime/src/lib.rs | 4 ++-- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index d5bfcbd9..776e2fdf 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -253,7 +253,8 @@ where )?; let res = client.call(req).await?; if res.status() != http::StatusCode::OK { - return Err(ExtensionError::boxed("unable to initialize the logs api")); + let err = format!("unable to initialize the logs api: {}", res.status()); + return Err(ExtensionError::boxed(err)); } trace!("Registered extension with Logs API"); } @@ -288,7 +289,8 @@ where )?; let res = client.call(req).await?; if res.status() != http::StatusCode::OK { - return Err(ExtensionError::boxed("unable to initialize the telemetry api")); + let err = format!("unable to initialize the telemetry api: {}", res.status()); + return Err(ExtensionError::boxed(err)); } trace!("Registered extension with Telemetry API"); } @@ -317,30 +319,30 @@ where let ep = match ep.ready().await { Ok(ep) => ep, - Err(error) => { - println!("Inner service is not ready: {:?}", error); + Err(err) => { + println!("Inner service is not ready: {err:?}"); let req = if is_invoke { - requests::init_error(extension_id, &error.to_string(), None)? + requests::init_error(extension_id, &err.to_string(), None)? } else { - requests::exit_error(extension_id, &error.to_string(), None)? + requests::exit_error(extension_id, &err.to_string(), None)? }; client.call(req).await?; - return Err(error.into()); + return Err(err.into()); } }; let res = ep.call(event).await; - if let Err(error) = res { - println!("{:?}", error); + if let Err(err) = res { + println!("{err:?}"); let req = if is_invoke { - requests::init_error(extension_id, &error.to_string(), None)? + requests::init_error(extension_id, &err.to_string(), None)? } else { - requests::exit_error(extension_id, &error.to_string(), None)? + requests::exit_error(extension_id, &err.to_string(), None)? }; client.call(req).await?; - return Err(error.into()); + return Err(err.into()); } } Ok(()) @@ -422,7 +424,8 @@ async fn register<'a>( let req = requests::register_request(&name, events)?; let res = client.call(req).await?; if res.status() != http::StatusCode::OK { - return Err(ExtensionError::boxed("unable to register the extension")); + let err = format!("unable to register the extension: {}", res.status()); + return Err(ExtensionError::boxed(err)); } let header = res diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 5fd32e4f..cdb6ed31 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -174,7 +174,7 @@ where let mut service = service.lock().await; match service.call(logs).await { Ok(_) => (), - Err(err) => println!("{:?}", err), + Err(err) => println!("{err:?}"), } } diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 64485d35..75c24a0f 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -7,6 +7,8 @@ use serde::Serialize; const EXTENSION_NAME_HEADER: &str = "Lambda-Extension-Name"; pub(crate) const EXTENSION_ID_HEADER: &str = "Lambda-Extension-Identifier"; const EXTENSION_ERROR_TYPE_HEADER: &str = "Lambda-Extension-Function-Error-Type"; +const CONTENT_TYPE_HEADER_NAME: &str = "Content-Type"; +const CONTENT_TYPE_HEADER_VALUE: &str = "application/json"; pub(crate) fn next_event_request(extension_id: &str) -> Result, Error> { let req = build_request() @@ -24,6 +26,7 @@ pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result< .method(Method::POST) .uri("/2020-01-01/extension/register") .header(EXTENSION_NAME_HEADER, extension_name) + .header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE) .body(Body::from(serde_json::to_string(&events)?))?; Ok(req) @@ -65,7 +68,7 @@ pub(crate) fn subscribe_request( "buffering": buffering.unwrap_or_default(), "destination": { "protocol": "HTTP", - "URI": format!("http://sandbox.localdomain:{}", port_number), + "URI": format!("http://sandbox.localdomain:{port_number}"), } }); @@ -73,6 +76,7 @@ pub(crate) fn subscribe_request( .method(Method::PUT) .uri(api.uri()) .header(EXTENSION_ID_HEADER, extension_id) + .header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE) .body(Body::from(serde_json::to_string(&data)?))?; Ok(req) @@ -91,30 +95,30 @@ pub struct ErrorRequest<'a> { } /// Create a new init error request to send to the Extensions API -pub fn init_error<'a>( +pub fn init_error( extension_id: &str, error_type: &str, - request: Option>, + request: Option>, ) -> Result, Error> { error_request("init", extension_id, error_type, request) } /// Create a new exit error request to send to the Extensions API -pub fn exit_error<'a>( +pub fn exit_error( extension_id: &str, error_type: &str, - request: Option>, + request: Option>, ) -> Result, Error> { error_request("exit", extension_id, error_type, request) } -fn error_request<'a>( +fn error_request( error_type: &str, extension_id: &str, error_str: &str, - request: Option>, + request: Option>, ) -> Result, Error> { - let uri = format!("/2020-01-01/extension/{}/error", error_type); + let uri = format!("/2020-01-01/extension/{error_type}/error"); let body = match request { None => Body::empty(), diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index 0ccaa7fc..e8b66bd3 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -291,7 +291,7 @@ where let mut service = service.lock().await; match service.call(telemetry).await { Ok(_) => (), - Err(err) => println!("{:?}", err), + Err(err) => println!("{err:?}"), } } diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 2056d48a..09551d0e 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -37,11 +37,10 @@ pub enum PayloadError { impl fmt::Display for PayloadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PayloadError::Json(json) => writeln!(f, "failed to parse payload from application/json {}", json), + PayloadError::Json(json) => writeln!(f, "failed to parse payload from application/json {json}"), PayloadError::WwwFormUrlEncoded(form) => writeln!( f, - "failed to parse payload from application/x-www-form-urlencoded {}", - form + "failed to parse payload from application/x-www-form-urlencoded {form}" ), } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index b2167857..3bb94872 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -324,7 +324,7 @@ fn apigw_path_with_stage(stage: &Option, path: &str) -> String { match stage { None => path.into(), Some(stage) if stage == "$default" => path.into(), - Some(stage) => format!("/{}{}", stage, path), + Some(stage) => format!("/{stage}{path}"), } } @@ -418,7 +418,7 @@ fn build_request_uri( ) -> String { let mut url = match host { None => { - let rel_url = Url::parse(&format!("http://localhost{}", path)).unwrap(); + let rel_url = Url::parse(&format!("http://localhost{path}")).unwrap(); rel_url.path().to_string() } Some(host) => { @@ -426,7 +426,7 @@ fn build_request_uri( .get(x_forwarded_proto()) .and_then(|s| s.to_str().ok()) .unwrap_or("https"); - let url = format!("{}://{}{}", scheme, host, path); + let url = format!("{scheme}://{host}{path}"); Url::parse(&url).unwrap().to_string() } }; diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 253d561c..01667da5 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -66,7 +66,7 @@ where (scheme, authority, base_path) }; let path = parts.uri.path_and_query().expect("PathAndQuery not found"); - let pq: PathAndQuery = format!("{}{}", base_path, path).parse().expect("PathAndQuery invalid"); + let pq: PathAndQuery = format!("{base_path}{path}").parse().expect("PathAndQuery invalid"); let uri = Uri::builder() .scheme(scheme.as_ref()) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 9663b375..7c6b71dd 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -183,7 +183,7 @@ where error!("{:?}", err); let error_type = type_name_of_val(&err); let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {}", msg) + format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; @@ -268,7 +268,7 @@ where { error!("{:?}", err); // logs the error in CloudWatch let error_type = type_name_of_val(&err); - let msg = format!("{}", err); + let msg = format!("{err}"); EventErrorRequest::new(request_id, error_type, &msg).into_req() } From a2386b9768974f4647582cdf6ec332cb090e440f Mon Sep 17 00:00:00 2001 From: greenwoodcm Date: Wed, 8 Feb 2023 10:07:13 -0800 Subject: [PATCH 160/394] add example for Lambda + HTTP + Axum (#598) this example bridges Lambda's HTTP support, which is based on the `tower::Service` trait, and the axum web framework, which builds applications that are also based on the `tower::Service` trait. --- examples/http-axum/Cargo.toml | 21 ++++++++++++++ examples/http-axum/README.md | 11 +++++++ examples/http-axum/src/main.rs | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 examples/http-axum/Cargo.toml create mode 100644 examples/http-axum/README.md create mode 100644 examples/http-axum/src/main.rs diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml new file mode 100644 index 00000000..ebc44cbd --- /dev/null +++ b/examples/http-axum/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "http-basic-lambda" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + +axum = "0.6.4" +serde_json = "1.0" diff --git a/examples/http-axum/README.md b/examples/http-axum/README.md new file mode 100644 index 00000000..5cae85fb --- /dev/null +++ b/examples/http-axum/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs new file mode 100644 index 00000000..07b024b0 --- /dev/null +++ b/examples/http-axum/src/main.rs @@ -0,0 +1,52 @@ +//! This is an example function that leverages the Lambda Rust runtime's HTTP support +//! and the [axum](https://docs.rs/axum/latest/axum/index.html) web framework. The +//! runtime HTTP support is backed by the [tower::Service](https://docs.rs/tower-service/0.3.2/tower_service/trait.Service.html) +//! trait. Axum applications are also backed by the `tower::Service` trait. That means +//! that it is fairly easy to build an Axum application and pass the resulting `Service` +//! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead +//! of a basic `tower::Service` you get web framework niceties like routing, request component +//! extraction, validation, etc. + +use lambda_http::{ + run, + Error, +}; +use axum::{ + extract::Path, + response::Json, + Router, + routing::{get, post}, +}; +use serde_json::{Value, json}; + +async fn root() -> Json { + Json(json!({ "msg": "I am GET /" })) +} + +async fn get_foo() -> Json { + Json(json!({ "msg": "I am GET /foo" })) +} + +async fn post_foo() -> Json { + Json(json!({ "msg": "I am POST /foo" })) +} + +async fn post_foo_name(Path(name): Path) -> Json { + Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let app = Router::new() + .route("/", get(root)) + .route("/foo", get(get_foo).post(post_foo)) + .route("/foo/:name", post(post_foo_name)); + + run(app).await +} From 26b330dac135eca26bb476bc40c2b69d377c8c4a Mon Sep 17 00:00:00 2001 From: Winston H <56998716+winstxnhdw@users.noreply.github.com> Date: Sat, 18 Feb 2023 16:50:47 +0800 Subject: [PATCH 161/394] docs/refactor: add documentation for invoking HTTP functions locally (#601) * docs: fix missing specified language on codeblock * docs: concise examples to perform local testing - use `cargo lambda watch` instead of `start` because `start` is not listed in `cargo lambda --help` - fix incorrect redirect to `cargo lambda watch` instead of `cargo lambda invoke` * docs: fix all non-visual markdown lint errors * refactor: use non-deprecated `with_ymd_and_hms` * docs: add example of the error received * docs: fix incorrect detail on the `invoke` command --- .github/PULL_REQUEST_TEMPLATE.md | 1 - CODE_OF_CONDUCT.md | 5 ++- CONTRIBUTING.md | 10 ++--- README.md | 44 +++++++++++++++---- .../README.md | 8 ++-- examples/basic-error-handling/README.md | 4 +- examples/basic-lambda/README.md | 4 +- examples/basic-shared-resource/README.md | 4 +- examples/extension-basic/README.md | 1 - examples/extension-combined/README.md | 1 - examples/extension-custom-events/README.md | 1 - examples/extension-custom-service/README.md | 1 - examples/extension-logs-basic/README.md | 1 - .../extension-logs-custom-service/README.md | 1 - .../extension-logs-kinesis-firehose/README.md | 1 - examples/extension-telemetry-basic/README.md | 1 - examples/http-axum/README.md | 4 +- examples/http-basic-lambda/README.md | 4 +- examples/http-cors/README.md | 4 +- examples/http-dynamodb/README.md | 6 +-- examples/http-query-parameters/README.md | 4 +- examples/http-raw-path/README.md | 4 +- examples/http-shared-resource/README.md | 4 +- lambda-extension/README.md | 9 ++-- lambda-extension/src/logs.rs | 8 +++- lambda-extension/src/telemetry.rs | 18 +++++--- lambda-http/README.md | 13 +++--- lambda-runtime-api-client/README.md | 2 +- 28 files changed, 96 insertions(+), 72 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e265aad2..31b151e5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,6 @@ *Description of changes:* - By submitting this pull request - [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3b644668..ec98f2b7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,5 @@ ## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact + +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09a50434..9b87d31f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,6 @@ documentation, we greatly value feedback and contributions from our community. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. - ## Reporting Bugs/Feature Requests We welcome you to use the GitHub issue tracker to report bugs or suggest features. @@ -19,8 +18,8 @@ reported the issue. Please try to include as much information as you can. Detail * Any modifications you've made relevant to the bug * Anything unusual about your environment or deployment - ## Contributing via Pull Requests + Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the *main* branch. @@ -39,20 +38,19 @@ To send us a pull request, please: GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). - ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-lambda-rust-runtime/labels/help%20wanted) issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-lambda-rust-runtime/labels/help%20wanted) issues is a great place to start. ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. - ## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Licensing diff --git a/README.md b/README.md index 505da34e..f4befe1d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ See other installation options in [the Cargo Lambda documentation](https://www.c To create your first function, run Cargo Lambda with the [subcommand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: -``` +```bash cargo lambda new YOUR_FUNCTION_NAME ``` @@ -72,13 +72,12 @@ async fn func(event: LambdaEvent) -> Result { If you already have Cargo Lambda installed in your machine, run the next command to build your function: -``` +```bash cargo lambda build --release ``` There are other ways of building your function: manually with the AWS CLI, with [AWS SAM](https://github.com/aws/aws-sam-cli), and with the [Serverless framework](https://serverless.com/framework/). - ### 1. Cross-compiling your Lambda functions By default, Cargo Lambda builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. @@ -99,7 +98,7 @@ Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2 If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: -``` +```bash # Note: replace "aarch64" with "x86_64" if you are building for x86_64 cargo lambda build --release --target aarch64-unknown-linux-gnu.2.17 ``` @@ -125,7 +124,6 @@ cargo lambda deploy \ This command will create a Lambda function with the same name of your rust package. You can change the name of the function by adding the argument at the end of the command: - ```bash cargo lambda deploy \ --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ @@ -154,7 +152,6 @@ cargo lambda build --release --arm64 --output-format zip You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip`. Use that file path to deploy your function with the [AWS CLI](https://aws.amazon.com/cli/): - ```bash $ aws lambda create-function --function-name rustTest \ --handler bootstrap \ @@ -257,7 +254,7 @@ $ npx serverless deploy Invoke it using serverless framework or a configured AWS integrated trigger source: ```bash -$ npx serverless invoke -f hello -d '{"foo":"bar"}' +npx serverless invoke -f hello -d '{"foo":"bar"}' ``` #### 2.5. Docker @@ -331,9 +328,38 @@ fn test_my_lambda_handler() { ### Cargo Lambda -[Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project, run the subcommand `cargo lambda start` to start the server. Your function will be compiled when the server receives the first request to process. Use the subcommand `cargo lambda invoke` to send requests to your function. The `start` subcommand will watch your function's code for changes, and it will compile it every time you save the source after making changes. +[Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project. You can run the following subcommand to compile your function(s) and start the server. + +```bash +cargo lambda watch -a 127.0.0.1 -p 9001 +``` + +Now you can use the `cargo lambda invoke` to send requests to your function. For example: + +```bash +cargo lambda invoke --data-ascii '{ "command": "hi" }' +``` + +Running the command on a HTTP function (Function URL, API Gateway, etc) will require you to use the appropriate scheme. You can find examples of these schemes [here](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/lambda-http/tests/data). Otherwise, you will be presented with the following error. + +```rust,no_run +Error: serde_json::error::Error + + × data did not match any variant of untagged enum LambdaRequest +``` + +An simpler alternative is to cURL the following endpoint based on the address and port you defined. For example: + +```bash +curl -v -X POST \ + 'http://127.0.0.1:9001/lambda-url/' \ + -H 'content-type: application/json' \ + -d '{ "command": "hi" }' +``` + +> **warning** Do not remove the `content-type` header. It is necessary to instruct the function how to deserialize the request body. -You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/watch.html) work on the [project's documentation page](https://www.cargo-lambda.info). +You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/invoke.html) work on the project's [documentation page](https://www.cargo-lambda.info). ### Lambda Debug Proxy diff --git a/examples/advanced-sqs-partial-batch-failures/README.md b/examples/advanced-sqs-partial-batch-failures/README.md index 7b10ca50..9711db8b 100644 --- a/examples/advanced-sqs-partial-batch-failures/README.md +++ b/examples/advanced-sqs-partial-batch-failures/README.md @@ -2,10 +2,8 @@ This example shows how to process events from an SQS queue using the partial batch failure feature. -_Important note:_ your lambda sqs trigger *needs* to be configured with partial batch response support -(the ` ReportBatchItemFailures` flag set to true), otherwise failed message will be not be reprocessed. -For more details see: -https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting +_Important note:_ your lambda sqs trigger _needs_ to be configured with partial batch response support +(the `ReportBatchItemFailures` flag set to true), otherwise failed message will be not be reprocessed. You may see more details [here](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting). ## Build & Deploy @@ -15,4 +13,4 @@ https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfai ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` \ No newline at end of file +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-error-handling/README.md b/examples/basic-error-handling/README.md index 5cae85fb..498f8a50 100644 --- a/examples/basic-error-handling/README.md +++ b/examples/basic-error-handling/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-lambda/README.md b/examples/basic-lambda/README.md index 5cae85fb..498f8a50 100644 --- a/examples/basic-lambda/README.md +++ b/examples/basic-lambda/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-shared-resource/README.md b/examples/basic-shared-resource/README.md index 5cae85fb..498f8a50 100644 --- a/examples/basic-shared-resource/README.md +++ b/examples/basic-shared-resource/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/extension-basic/README.md b/examples/extension-basic/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-basic/README.md +++ b/examples/extension-basic/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-combined/README.md b/examples/extension-combined/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-combined/README.md +++ b/examples/extension-combined/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-custom-events/README.md b/examples/extension-custom-events/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-custom-events/README.md +++ b/examples/extension-custom-events/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-custom-service/README.md b/examples/extension-custom-service/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-custom-service/README.md +++ b/examples/extension-custom-service/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-logs-basic/README.md b/examples/extension-logs-basic/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-logs-basic/README.md +++ b/examples/extension-logs-basic/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-logs-custom-service/README.md b/examples/extension-logs-custom-service/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-logs-custom-service/README.md +++ b/examples/extension-logs-custom-service/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-logs-kinesis-firehose/README.md b/examples/extension-logs-kinesis-firehose/README.md index 6f9636c9..b566774e 100644 --- a/examples/extension-logs-kinesis-firehose/README.md +++ b/examples/extension-logs-kinesis-firehose/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/extension-telemetry-basic/README.md b/examples/extension-telemetry-basic/README.md index fe9c26ca..337e045c 100644 --- a/examples/extension-telemetry-basic/README.md +++ b/examples/extension-telemetry-basic/README.md @@ -8,7 +8,6 @@ The last command will give you an ARN for the extension layer that you can use in your functions. - ## Build for ARM 64 Build the extension with `cargo lambda build --release --extension --arm64` diff --git a/examples/http-axum/README.md b/examples/http-axum/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-axum/README.md +++ b/examples/http-axum/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-basic-lambda/README.md b/examples/http-basic-lambda/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-basic-lambda/README.md +++ b/examples/http-basic-lambda/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-cors/README.md b/examples/http-cors/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-cors/README.md +++ b/examples/http-cors/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-dynamodb/README.md b/examples/http-dynamodb/README.md index cb53c868..072f2bf9 100644 --- a/examples/http-dynamodb/README.md +++ b/examples/http-dynamodb/README.md @@ -3,17 +3,15 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` Setting up Dynamodb 1. Log into your account. 2. Create a Dynamodb table with the name 'lambda_dyno_example' with the partition key of "username". 3. Create IAM role with the permissions for Lambda, Cloudwatch and Dynamodb. - - diff --git a/examples/http-query-parameters/README.md b/examples/http-query-parameters/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-query-parameters/README.md +++ b/examples/http-query-parameters/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-raw-path/README.md b/examples/http-raw-path/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-raw-path/README.md +++ b/examples/http-raw-path/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-shared-resource/README.md b/examples/http-shared-resource/README.md index 5cae85fb..498f8a50 100644 --- a/examples/http-shared-resource/README.md +++ b/examples/http-shared-resource/README.md @@ -3,9 +3,9 @@ ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the function with `cargo lambda build --release` +2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` ## Build for ARM 64 -Build the function with `cargo lambda build --release --arm64` +Build the function with `cargo lambda build --release --arm64` diff --git a/lambda-extension/README.md b/lambda-extension/README.md index b3f5e529..00cd77c4 100644 --- a/lambda-extension/README.md +++ b/lambda-extension/README.md @@ -125,13 +125,13 @@ Regardless of how you deploy them, the extensions MUST be compiled against the s - Build the extension with: -``` +```bash cargo lambda build --release --extension ``` If you want to run the extension in ARM processors, add the `--arm64` flag to the previous command: -``` +```bash cargo lambda build --release --extension --arm64 ``` @@ -141,13 +141,12 @@ This previous command will generate a binary file in `target/lambda/extensions` - Make sure you have the right credentials in your terminal by running the AWS CLI configure command: -``` +```bash aws configure ``` - Deploy the extension as a layer with: -``` +```bash cargo lambda deploy --extension ``` - diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index cdb6ed31..cf894100 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -184,13 +184,17 @@ where #[cfg(test)] mod tests { use super::*; - use chrono::TimeZone; + use chrono::{Duration, TimeZone}; #[test] fn deserialize_full() { let data = r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#; let expected = LambdaLog { - time: Utc.ymd(2020, 8, 20).and_hms_milli(12, 31, 32, 123), + time: Utc + .with_ymd_and_hms(2020, 8, 20, 12, 31, 32) + .unwrap() + .checked_add_signed(Duration::milliseconds(123)) + .unwrap(), record: LambdaLogRecord::Function("hello world".to_string()), }; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index e8b66bd3..b3131338 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -301,7 +301,7 @@ where #[cfg(test)] mod tests { use super::*; - use chrono::TimeZone; + use chrono::{Duration, TimeZone}; macro_rules! deserialize_tests { ($($name:ident: $value:expr,)*) => { @@ -367,13 +367,21 @@ mod tests { spans: vec!( Span { name:"responseLatency".to_string(), - start: Utc.ymd(2022, 10, 21).and_hms_milli(14, 05, 03, 165), - duration_ms:2598.0 + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) + .unwrap() + .checked_add_signed(Duration::milliseconds(165)) + .unwrap(), + duration_ms: 2598.0 }, Span { name:"responseDuration".to_string(), - start:Utc.ymd(2022, 10, 21).and_hms_milli(14, 05, 05, 763), - duration_ms:0.0 + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) + .unwrap() + .checked_add_signed(Duration::milliseconds(763)) + .unwrap(), + duration_ms: 0.0 }, ), tracing: Some(TraceContext{ diff --git a/lambda-http/README.md b/lambda-http/README.md index 20e670eb..1eef90c6 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -1,14 +1,16 @@ # lambda-http for AWS Lambda in Rust -[![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) +[![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is an abstraction that takes payloads from different services and turns them into http objects, making it easy to write API Gateway proxy event focused Lambda functions in Rust. lambda-http handler is made of: + * Request - Represents an HTTP request * IntoResponse - Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] We are able to handle requests from: + * [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST, HTTP and WebSockets API lambda integrations * AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) * AWS [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) @@ -61,7 +63,7 @@ pub async fn function_handler(event: Request) -> Result Result Result<(), Error> { client.call(request).await } -``` \ No newline at end of file +``` From 42489f828238a3db9299f1eb60cee3b176e0a5ce Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 19 Feb 2023 23:34:33 +0800 Subject: [PATCH 162/394] Fix clippy warnings (#603) --- lambda-http/src/lib.rs | 2 +- lambda-http/src/request.rs | 72 ++++++++-------------------- lambda-runtime-api-client/src/lib.rs | 2 +- lambda-runtime/src/lib.rs | 4 +- lambda-runtime/src/simulated.rs | 2 +- 5 files changed, 25 insertions(+), 57 deletions(-) diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index ebb3e0c5..8d030a75 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -221,7 +221,7 @@ mod test_adapter { fn call(&mut self, event: LambdaEvent) -> Self::Future { // Log the request - println!("Lambda event: {:#?}", event); + println!("Lambda event: {event:#?}"); self.inner.call(event) } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 3bb94872..95cf6a52 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -456,7 +456,7 @@ mod tests { // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request // note: file paths are relative to the directory of the crate at runtime let result = from_reader(File::open("tests/data/apigw_proxy_request.json").expect("expected file")); - assert!(result.is_ok(), "event was not parsed as expected {:?}", result); + assert!(result.is_ok(), "event was not parsed as expected {result:?}"); } #[test] @@ -467,9 +467,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -479,8 +477,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::ApiGatewayV2(_)), - "expected ApiGatewayV2 context, got {:?}", - req_context + "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -492,9 +489,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); let cookie_header = req @@ -511,8 +506,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::ApiGatewayV2(_)), - "expected ApiGatewayV2 context, got {:?}", - req_context + "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -525,9 +519,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -540,8 +532,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::ApiGatewayV1(_)), - "expected ApiGateway context, got {:?}", - req_context + "expected ApiGateway context, got {req_context:?}" ); } @@ -553,9 +544,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); let cookie_header = req @@ -576,8 +565,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::ApiGatewayV2(_)), - "expected ApiGatewayV2 context, got {:?}", - req_context + "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -589,9 +577,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -604,8 +590,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::Alb(_)), - "expected Alb context, got {:?}", - req_context + "expected Alb context, got {req_context:?}" ); } @@ -617,9 +602,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -632,8 +615,7 @@ mod tests { let req_context = req.request_context(); assert!( matches!(req_context, RequestContext::Alb(_)), - "expected Alb context, got {:?}", - req_context + "expected Alb context, got {req_context:?}" ); } @@ -645,9 +627,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event is was not parsed as expected {:?} given {}", - result, - input + "event is was not parsed as expected {result:?} given {input}" ); let request = result.expect("failed to parse request"); @@ -668,9 +648,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event is was not parsed as expected {:?} given {}", - result, - input + "event is was not parsed as expected {result:?} given {input}" ); let request = result.expect("failed to parse request"); assert!(!request.query_string_parameters().is_empty()); @@ -690,9 +668,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event is was not parsed as expected {:?} given {}", - result, - input + "event is was not parsed as expected {result:?} given {input}" ); let request = result.expect("failed to parse request"); assert!(!request.query_string_parameters().is_empty()); @@ -718,9 +694,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -734,9 +708,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -750,9 +722,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); @@ -766,9 +736,7 @@ mod tests { let result = from_str(input); assert!( result.is_ok(), - "event was not parsed as expected {:?} given {}", - result, - input + "event was not parsed as expected {result:?} given {input}" ); let req = result.expect("failed to parse request"); assert_eq!(req.uri(), "https://id.execute-api.us-east-1.amazonaws.com/my/path-with%20space?parameter1=value1¶meter1=value2¶meter2=value"); diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 01667da5..42a4c54b 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -1,6 +1,6 @@ #![deny(clippy::all, clippy::cargo)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] -#![warn(clippy::multiple_crate_versions)] +#![allow(clippy::multiple_crate_versions)] //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 7c6b71dd..cf03664e 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -323,7 +323,7 @@ mod endpoint_tests { .body(Body::empty()) .expect("Unable to construct response"); - let expected = format!("/2018-06-01/runtime/invocation/{}/response", id); + let expected = format!("/2018-06-01/runtime/invocation/{id}/response"); assert_eq!(expected, req.uri().path()); Ok(rsp) @@ -331,7 +331,7 @@ mod endpoint_tests { #[cfg(test)] async fn event_err(req: &Request, id: &str) -> Result, Error> { - let expected = format!("/2018-06-01/runtime/invocation/{}/error", id); + let expected = format!("/2018-06-01/runtime/invocation/{id}/error"); assert_eq!(expected, req.uri().path()); assert_eq!(req.method(), Method::POST); diff --git a/lambda-runtime/src/simulated.rs b/lambda-runtime/src/simulated.rs index 4fcc3106..f6a06bca 100644 --- a/lambda-runtime/src/simulated.rs +++ b/lambda-runtime/src/simulated.rs @@ -66,7 +66,7 @@ impl hyper::service::Service for Connector { fn call(&mut self, uri: Uri) -> Self::Future { let res = match self.inner.lock() { Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), - Ok(_) => Err(format!("Uri {} is not in map", uri).into()), + Ok(_) => Err(format!("Uri {uri} is not in map").into()), Err(_) => Err("mutex was poisoned".into()), }; Box::pin(async move { res }) From fcb0b8f3e7c1b33a63da55977ce35acdf4b0da57 Mon Sep 17 00:00:00 2001 From: greenwoodcm Date: Mon, 20 Feb 2023 18:47:35 -0800 Subject: [PATCH 163/394] fix http-axum example crate name (#605) just for consistency and clarity --- examples/http-axum/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index ebc44cbd..50db3ebf 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "http-basic-lambda" +name = "http-axum" version = "0.1.0" edition = "2021" From 17ef2a29fa89cba5ee0e923179dcef4fa848e99d Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Tue, 21 Feb 2023 00:08:29 -0500 Subject: [PATCH 164/394] lambda_http: Add convenience methods to get references to data in the request (#602) * Fixed some types in docs and tests * Add convenience methods to get references to data in the request There are existing methods to get owned clones of various pieces of data in the `Request`. This adds methods to get references where owned data is not needed. --- lambda-http/src/ext.rs | 123 +++++++++++++++++++++++++++++-------- lambda-http/src/request.rs | 2 +- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 09551d0e..f034c686 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -55,7 +55,7 @@ impl Error for PayloadError { } } -/// Extentions for `lambda_http::Request` structs that +/// Extensions for `lambda_http::Request` structs that /// provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) /// and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) /// features. @@ -109,6 +109,9 @@ pub trait RequestExt { /// Return the raw http path for a request without any stage information. fn raw_http_path(&self) -> String; + /// Return the raw http path for a request without any stage information. + fn raw_http_path_str(&self) -> &str; + /// Configures instance with the raw http path. fn with_raw_http_path(self, path: &str) -> Self; @@ -118,12 +121,24 @@ pub trait RequestExt { /// /// The yielded value represents both single and multi-valued /// parameters alike. When multiple query string parameters with the same - /// name are expected, `query_string_parameters().get_all("many")` to retrieve them all. + /// name are expected, `query_string_parameters().all("many")` to retrieve them all. /// - /// No query parameters - /// will yield an empty `QueryMap`. + /// No query parameters will yield an empty `QueryMap`. fn query_string_parameters(&self) -> QueryMap; + /// Return pre-parsed http query string parameters, parameters + /// provided after the `?` portion of a url, + /// associated with the API gateway request. + /// + /// The yielded value represents both single and multi-valued + /// parameters alike. When multiple query string parameters with the same + /// name are expected, + /// `query_string_parameters_ref().and_then(|params| params.all("many"))` to + /// retrieve them all. + /// + /// No query parameters will yield `None`. + fn query_string_parameters_ref(&self) -> Option<&QueryMap>; + /// Configures instance with query string parameters /// /// This is intended for use in mock testing contexts. @@ -139,6 +154,14 @@ pub trait RequestExt { /// These will always be empty for ALB triggered requests fn path_parameters(&self) -> QueryMap; + /// Return pre-extracted path parameters, parameter provided in url placeholders + /// `/foo/{bar}/baz/{boom}`, + /// associated with the API gateway request. No path parameters + /// will yield `None` + /// + /// These will always be `None` for ALB triggered requests + fn path_parameters_ref(&self) -> Option<&QueryMap>; + /// Configures instance with path parameters /// /// This is intended for use in mock testing contexts. @@ -153,6 +176,13 @@ pub trait RequestExt { /// These will always be empty for ALB triggered requests fn stage_variables(&self) -> QueryMap; + /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) + /// associated with the API gateway request. No stage parameters + /// will yield `None` + /// + /// These will always be `None` for ALB triggered requests + fn stage_variables_ref(&self) -> Option<&QueryMap>; + /// Configures instance with stage variables under #[cfg(test)] configurations /// /// This is intended for use in mock testing contexts. @@ -164,6 +194,10 @@ pub trait RequestExt { /// Return request context data associated with the ALB or API gateway request fn request_context(&self) -> RequestContext; + /// Return a reference to the request context data associated with the ALB or + /// API gateway request + fn request_context_ref(&self) -> Option<&RequestContext>; + /// Configures instance with request context /// /// This is intended for use in mock testing contexts. @@ -185,15 +219,23 @@ pub trait RequestExt { /// Return the Lambda function context associated with the request fn lambda_context(&self) -> Context; + /// Return a reference to the the Lambda function context associated with the + /// request + fn lambda_context_ref(&self) -> Option<&Context>; + /// Configures instance with lambda context fn with_lambda_context(self, context: Context) -> Self; } impl RequestExt for http::Request { fn raw_http_path(&self) -> String { + self.raw_http_path_str().to_string() + } + + fn raw_http_path_str(&self) -> &str { self.extensions() .get::() - .map(|ext| ext.0.clone()) + .map(|RawHttpPath(path)| path.as_str()) .unwrap_or_default() } @@ -204,10 +246,19 @@ impl RequestExt for http::Request { } fn query_string_parameters(&self) -> QueryMap { - self.extensions() - .get::() - .map(|ext| ext.0.clone()) - .unwrap_or_default() + self.query_string_parameters_ref().cloned().unwrap_or_default() + } + + fn query_string_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions().get::().and_then( + |QueryStringParameters(params)| { + if params.is_empty() { + None + } else { + Some(params) + } + }, + ) } fn with_query_string_parameters(self, parameters: Q) -> Self @@ -220,10 +271,19 @@ impl RequestExt for http::Request { } fn path_parameters(&self) -> QueryMap { - self.extensions() - .get::() - .map(|ext| ext.0.clone()) - .unwrap_or_default() + self.path_parameters_ref().cloned().unwrap_or_default() + } + + fn path_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions().get::().and_then( + |PathParameters(params)| { + if params.is_empty() { + None + } else { + Some(params) + } + }, + ) } fn with_path_parameters

(self, parameters: P) -> Self @@ -236,10 +296,19 @@ impl RequestExt for http::Request { } fn stage_variables(&self) -> QueryMap { - self.extensions() - .get::() - .map(|ext| ext.0.clone()) - .unwrap_or_default() + self.stage_variables_ref().cloned().unwrap_or_default() + } + + fn stage_variables_ref(&self) -> Option<&QueryMap> { + self.extensions().get::().and_then( + |StageVariables(vars)| { + if vars.is_empty() { + None + } else { + Some(vars) + } + }, + ) } #[cfg(test)] @@ -253,12 +322,15 @@ impl RequestExt for http::Request { } fn request_context(&self) -> RequestContext { - self.extensions() - .get::() + self.request_context_ref() .cloned() .expect("Request did not contain a request context") } + fn request_context_ref(&self) -> Option<&RequestContext> { + self.extensions().get::() + } + fn with_request_context(self, context: RequestContext) -> Self { let mut s = self; s.extensions_mut().insert(context); @@ -266,12 +338,15 @@ impl RequestExt for http::Request { } fn lambda_context(&self) -> Context { - self.extensions() - .get::() + self.lambda_context_ref() .cloned() .expect("Request did not contain a lambda context") } + fn lambda_context_ref(&self) -> Option<&Context> { + self.extensions().get::() + } + fn with_lambda_context(self, context: Context) -> Self { let mut s = self; s.extensions_mut().insert(context); @@ -358,7 +433,7 @@ mod tests { } #[test] - fn requests_have_json_parseable_payloads() { + fn requests_have_json_parsable_payloads() { #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, @@ -421,7 +496,7 @@ mod tests { } #[test] - fn requests_omiting_content_types_do_not_support_parseable_payloads() { + fn requests_omitting_content_types_do_not_support_parsable_payloads() { #[derive(Deserialize, Eq, PartialEq, Debug)] struct Payload { foo: String, @@ -429,7 +504,7 @@ mod tests { } let request = http::Request::builder() .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) - .expect("failed to bulid request"); + .expect("failed to build request"); let payload: Option = request.payload().unwrap_or_default(); assert_eq!(payload, None); } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 95cf6a52..c31398c5 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -298,7 +298,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< .extension(RequestContext::WebSocket(ag.request_context)); // merge headers into multi_value_headers and make - // multi-value_headers our cannoncial source of request headers + // multi-value_headers our canonical source of request headers let mut headers = ag.multi_value_headers; headers.extend(ag.headers); update_xray_trace_id_header(&mut headers); From 54a3d930af4176adfcebed216dce6fc3b85dc859 Mon Sep 17 00:00:00 2001 From: Ed Muthiah Date: Tue, 21 Feb 2023 22:00:06 -0800 Subject: [PATCH 165/394] Fix invoke example for Windows CMD (#604) * Fix invoke example for Windows * Fix invoke example for Windows CMD * Fix invoke example for Windows CMD * Add note on handling quotes in different shells * Add shorter note on handling quotes in different shells * Moved note closer to first invoke * moved note closer to invoke --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f4befe1d..2cc04fb3 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,9 @@ cargo lambda invoke --remote \ my-first-lambda-function ``` +> **Note** +> CLI commands in the examples use Linux/MacOS syntax. For different shells like Windows CMD or PowerShell, modify syntax when using nested quotation marks like `'{"command": "hi"}'`. Escaping with a backslash may be necessary. See [AWS CLI Reference](https://docs.amazonaws.cn/en_us/cli/latest/userguide/cli-usage-parameters-quoting-strings.html#cli-usage-parameters-quoting-strings-containing) for more information. + #### 2.2. Deploying with the AWS CLI You can also use the AWS CLI to deploy your Rust functions. First, you will need to create a ZIP archive of your function. Cargo Lambda can do that for you automatically when it builds your binary if you add the `output-format` flag: From 23b665f3abdd3d32836d63e64121ef83e9df0ea0 Mon Sep 17 00:00:00 2001 From: Ed Muthiah Date: Tue, 21 Feb 2023 23:53:44 -0800 Subject: [PATCH 166/394] Consistent notes and warnings (#608) * Consistent notes and warnings * Consistent notes and warnings --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2cc04fb3..781ef364 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,10 @@ Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2 If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: ```bash -# Note: replace "aarch64" with "x86_64" if you are building for x86_64 cargo lambda build --release --target aarch64-unknown-linux-gnu.2.17 ``` - +> **Note** +> Replace "aarch64" with "x86_64" if you are building for x86_64 ### 2. Deploying the binary to AWS Lambda For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated executable to `bootstrap` and add it to a zip archive. @@ -118,7 +118,7 @@ cargo lambda deploy \ --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role ``` -> **warning** +> **Warning** > Make sure to replace the execution role with an existing role in your account! This command will create a Lambda function with the same name of your rust package. You can change the name @@ -130,7 +130,7 @@ cargo lambda deploy \ my-first-lambda-function ``` -> **info** +> **Note** > See other deployment options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/commands/deploy.html). You can test the function with the [invoke subcommand](https://www.cargo-lambda.info/commands/invoke.html): @@ -165,7 +165,7 @@ $ aws lambda create-function --function-name rustTest \ --tracing-config Mode=Active ``` -> **warning** +> **Warning** > Make sure to replace the execution role with an existing role in your account! You can now test the function using the AWS CLI or the AWS Lambda console @@ -179,8 +179,8 @@ $ aws lambda invoke $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} ``` -**Note:** `--cli-binary-format raw-in-base64-out` is a required - argument when using the AWS CLI version 2. [More Information](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html#cliv2-migration-binaryparam) +> **Note** +> `--cli-binary-format raw-in-base64-out` is a required argument when using the AWS CLI version 2. [More Information](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html#cliv2-migration-binaryparam) #### 2.3. AWS Serverless Application Model (SAM) @@ -360,7 +360,8 @@ curl -v -X POST \ -d '{ "command": "hi" }' ``` -> **warning** Do not remove the `content-type` header. It is necessary to instruct the function how to deserialize the request body. +> **Warning** +> Do not remove the `content-type` header. It is necessary to instruct the function how to deserialize the request body. You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/invoke.html) work on the project's [documentation page](https://www.cargo-lambda.info). From 847b060c3947eec14c264fcaab5da24dccb4ceea Mon Sep 17 00:00:00 2001 From: greenwoodcm Date: Wed, 1 Mar 2023 07:03:58 -0800 Subject: [PATCH 167/394] make tracing init consistent across examples (#609) --- .../advanced-sqs-partial-batch-failures/Cargo.toml | 2 +- .../advanced-sqs-partial-batch-failures/src/main.rs | 4 ++++ examples/basic-error-handling/Cargo.toml | 2 +- examples/basic-error-handling/src/main.rs | 8 ++++++-- examples/basic-lambda/Cargo.toml | 2 +- examples/basic-lambda/src/main.rs | 6 ++++++ examples/basic-shared-resource/Cargo.toml | 2 +- examples/basic-shared-resource/src/main.rs | 5 +++++ examples/basic-sqs/Cargo.toml | 2 +- examples/basic-sqs/src/main.rs | 4 ++++ examples/extension-basic/Cargo.toml | 2 +- examples/extension-basic/src/main.rs | 8 ++++++-- examples/extension-combined/Cargo.toml | 2 +- examples/extension-combined/src/main.rs | 8 ++++++-- examples/extension-custom-events/Cargo.toml | 2 +- examples/extension-custom-events/src/main.rs | 8 ++++++-- examples/extension-custom-service/Cargo.toml | 2 +- examples/extension-custom-service/src/main.rs | 8 ++++++-- examples/extension-logs-basic/Cargo.toml | 2 +- examples/extension-logs-basic/src/main.rs | 8 ++++++-- examples/extension-logs-custom-service/Cargo.toml | 2 +- examples/extension-logs-custom-service/src/main.rs | 8 ++++++-- examples/extension-logs-kinesis-firehose/Cargo.toml | 2 ++ examples/extension-logs-kinesis-firehose/src/main.rs | 12 ++++++++++++ examples/extension-telemetry-basic/Cargo.toml | 2 +- examples/extension-telemetry-basic/src/main.rs | 8 ++++++-- examples/http-axum/Cargo.toml | 2 +- examples/http-axum/src/main.rs | 6 ++++++ examples/http-basic-lambda/Cargo.toml | 2 +- examples/http-basic-lambda/src/main.rs | 6 ++++++ examples/http-cors/Cargo.toml | 2 +- examples/http-cors/src/main.rs | 8 ++++++-- examples/http-dynamodb/Cargo.toml | 4 ++-- examples/http-dynamodb/src/main.rs | 6 ++++++ examples/http-query-parameters/Cargo.toml | 2 +- examples/http-query-parameters/src/main.rs | 6 ++++++ examples/http-raw-path/Cargo.toml | 2 +- examples/http-raw-path/src/main.rs | 8 ++++++-- examples/http-shared-resource/Cargo.toml | 2 +- examples/http-shared-resource/src/main.rs | 8 ++++++-- examples/http-tower-trace/Cargo.toml | 2 +- examples/http-tower-trace/src/main.rs | 12 +++++++++++- 42 files changed, 155 insertions(+), 44 deletions(-) diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 04067948..06e56053 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -13,4 +13,4 @@ lambda_runtime = "0.7" tokio = { version = "1", features = ["macros"] } futures = "0.3" tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 254df031..2923d2e4 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -28,10 +28,14 @@ async fn data_handler(data: Data) -> Result<(), Error> { /// Main function for the lambda executable. #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index e8699141..325b08e1 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -17,6 +17,6 @@ serde_json = "1.0.81" simple-error = "0.2.3" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 8d274849..8d317a24 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -49,10 +49,14 @@ impl std::fmt::Display for CustomError { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index e9e8d635..c2c8b429 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -15,4 +15,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index 30a74851..bdf39112 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -24,8 +24,14 @@ struct Response { #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index a26bf3cc..25637976 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -15,6 +15,6 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/basic-shared-resource/src/main.rs b/examples/basic-shared-resource/src/main.rs index a157e5ea..15c38741 100644 --- a/examples/basic-shared-resource/src/main.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -47,6 +47,11 @@ async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 55da60e0..086a22dc 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -20,4 +20,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs index 24fbd573..319e4519 100644 --- a/examples/basic-sqs/src/main.rs +++ b/examples/basic-sqs/src/main.rs @@ -20,10 +20,14 @@ async fn function_handler(event: LambdaEvent>) -> Result<(), E #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index caf0818c..94ee4926 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-basic/src/main.rs b/examples/extension-basic/src/main.rs index 54784b03..4af6a47f 100644 --- a/examples/extension-basic/src/main.rs +++ b/examples/extension-basic/src/main.rs @@ -14,10 +14,14 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index d776f488..e585516a 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-combined/src/main.rs b/examples/extension-combined/src/main.rs index bc6cd5e0..60d0f9e1 100644 --- a/examples/extension-combined/src/main.rs +++ b/examples/extension-combined/src/main.rs @@ -29,10 +29,14 @@ async fn my_log_processor(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index a826a137..90c5d322 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-custom-events/src/main.rs b/examples/extension-custom-events/src/main.rs index 7717fc2e..b7574642 100644 --- a/examples/extension-custom-events/src/main.rs +++ b/examples/extension-custom-events/src/main.rs @@ -16,10 +16,14 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index c9ff789a..5396c137 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-custom-service/src/main.rs b/examples/extension-custom-service/src/main.rs index 968dd904..fd85c91d 100644 --- a/examples/extension-custom-service/src/main.rs +++ b/examples/extension-custom-service/src/main.rs @@ -33,10 +33,14 @@ impl Service for MyExtension { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index d1983db8..30c09117 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-logs-basic/src/main.rs b/examples/extension-logs-basic/src/main.rs index f9ea845a..5543dec9 100644 --- a/examples/extension-logs-basic/src/main.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -15,10 +15,14 @@ async fn handler(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index cbbe20f6..35a9e05d 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-logs-custom-service/src/main.rs b/examples/extension-logs-custom-service/src/main.rs index bcdf660d..9137c017 100644 --- a/examples/extension-logs-custom-service/src/main.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -56,10 +56,14 @@ impl Service> for MyLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index 942d0a93..547ad48e 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -12,6 +12,8 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } tokio = { version = "1.17.0", features = ["full"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } aws-config = "0.13.0" aws-sdk-firehose = "0.13.0" diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index d771cf4b..68c9421c 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -53,6 +53,18 @@ impl Service> for FirehoseLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + let config = aws_config::load_from_env().await; let logs_processor = SharedService::new(FirehoseLogsProcessor::new(Client::new(&config))); diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index 869b604d..bc426b68 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs index d08bff38..f522808c 100644 --- a/examples/extension-telemetry-basic/src/main.rs +++ b/examples/extension-telemetry-basic/src/main.rs @@ -40,10 +40,14 @@ async fn handler(events: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 50db3ebf..6a0e8905 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -15,7 +15,7 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } axum = "0.6.4" serde_json = "1.0" diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 07b024b0..7770d861 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -37,8 +37,14 @@ async fn post_foo_name(Path(name): Path) -> Json { #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index 1a218330..ad53161d 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index df15ae6c..88db4886 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -19,8 +19,14 @@ async fn function_handler(_event: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 9fd7f25b..65df64d4 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -16,6 +16,6 @@ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.3", features = ["cors"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index 7e1b21fa..f1ee0955 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -5,10 +5,14 @@ use tower_http::cors::{Any, CorsLayer}; #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index 73ff7c24..6b6a0205 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -19,7 +19,7 @@ lambda_runtime = { path = "../../lambda-runtime" } aws-sdk-dynamodb = "0.21.0" aws-config = "0.51.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1.37"} -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 4496ddf4..6a0c8947 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -54,8 +54,14 @@ async fn handle_request(db_client: &Client, event: Request) -> Result Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 7aeb1189..5089a85e 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 03e4b939..2a33829c 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -17,8 +17,14 @@ async fn function_handler(event: Request) -> Result { #[tokio::main] async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index f4060428..4cb3e23f 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index f88b7b64..3694e61b 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -2,10 +2,14 @@ use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 207f253b..4d655489 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index 48bab471..e8d65e80 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -12,10 +12,14 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 2b8f7a60..bf4a5b0c 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -16,4 +16,4 @@ lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.4", features = ["trace"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index ec7020c1..678d79cd 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -9,7 +9,17 @@ async fn handler(_req: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt().without_time().init(); + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); let layer = TraceLayer::new_for_http() .on_request(DefaultOnRequest::new().level(Level::INFO)) From 0f6e2a285677c00c35d65250166fa99cd56d3d68 Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Mon, 6 Mar 2023 19:50:17 +0100 Subject: [PATCH 168/394] add test for basic-lambda (#551) (#612) --- examples/basic-lambda/Cargo.toml | 1 + examples/basic-lambda/src/main.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index c2c8b429..fd6bd5b2 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -16,3 +16,4 @@ serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index bdf39112..2bb4aeb3 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -54,3 +54,27 @@ pub(crate) async fn my_handler(event: LambdaEvent) -> Result Date: Tue, 21 Mar 2023 19:05:12 -0700 Subject: [PATCH 169/394] basic-sdk example (#619) adds an example for a basic Lambda function that calls S3. this demonstrates the best practice of instantiating an SDK client during function initialization (main method) and reusing that client across multiple invokes. --- examples/basic-sdk/Cargo.toml | 20 +++++ examples/basic-sdk/README.md | 21 +++++ examples/basic-sdk/src/main.rs | 140 +++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 examples/basic-sdk/Cargo.toml create mode 100644 examples/basic-sdk/README.md create mode 100644 examples/basic-sdk/src/main.rs diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml new file mode 100644 index 00000000..0ffea2da --- /dev/null +++ b/examples/basic-sdk/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "basic-sdk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1" +aws-config = "0.54" +aws-sdk-s3 = "0.24" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } + +[dev-dependencies] +mockall = "0.11.3" +tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-sdk/README.md b/examples/basic-sdk/README.md new file mode 100644 index 00000000..483f6951 --- /dev/null +++ b/examples/basic-sdk/README.md @@ -0,0 +1,21 @@ + +## basic-sdk example + +This is an sample function that uses the [AWS SDK](https://github.com/awslabs/aws-sdk-rust) to +list the contents of an S3 bucket specified by the invoker. It uses standard credentials as defined +in the function's execution role to make calls against S3. + +### Running Locally +You can use `cargo lambda watch` to spin up a local version of the function. This will automatically re-compile and restart +itself when it observes changes to the code. If you invoke `watch` with no other context then the function will not have +the environment variables necessary to supply on SDK calls. To get around this you can manually supply a credentials file +profile for the SDK to resolve and use in your function: +``` +AWS_PROFILE=my-profile cargo lambda watch +``` + +### Invoking +You can invoke by simply leveraging `cargo lambda invoke` with the payload expected by the function handler. +``` +cargo lambda invoke --data-ascii '{"bucket":"my-bucket"}' +``` diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs new file mode 100644 index 00000000..5838d7c8 --- /dev/null +++ b/examples/basic-sdk/src/main.rs @@ -0,0 +1,140 @@ +use async_trait::async_trait; +use aws_sdk_s3::{output::ListObjectsV2Output, Client as S3Client}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; + +/// The request defines what bucket to list +#[derive(Deserialize)] +struct Request { + bucket: String, +} + +/// The response contains a Lambda-generated request ID and +/// the list of objects in the bucket. +#[derive(Serialize)] +struct Response { + req_id: String, + bucket: String, + objects: Vec, +} + +#[cfg_attr(test, mockall::automock)] +#[async_trait] +trait ListObjects { + async fn list_objects(&self, bucket: &str) -> Result; +} + +#[async_trait] +impl ListObjects for S3Client { + async fn list_objects(&self, bucket: &str) -> Result { + self.list_objects_v2().bucket(bucket).send().await.map_err(|e| e.into()) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let shared_config = aws_config::load_from_env().await; + let client = S3Client::new(&shared_config); + let client_ref = &client; + + let func = service_fn(move |event| async move { my_handler(event, client_ref).await }); + lambda_runtime::run(func).await?; + + Ok(()) +} + +async fn my_handler(event: LambdaEvent, client: &T) -> Result { + let bucket = event.payload.bucket; + + let objects_rsp = client.list_objects(&bucket).await?; + let objects: Vec<_> = objects_rsp + .contents() + .ok_or("missing objects in list-objects-v2 response")? + .into_iter() + .filter_map(|o| o.key().map(|k| k.to_string())) + .collect(); + + // prepare the response + let rsp = Response { + req_id: event.context.request_id, + bucket: bucket.clone(), + objects, + }; + + // return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(rsp) +} + +#[cfg(test)] +mod tests { + use super::*; + use aws_sdk_s3::model::Object; + use lambda_runtime::{Context, LambdaEvent}; + use mockall::predicate::eq; + + #[tokio::test] + async fn response_is_good_for_good_bucket() { + let mut context = Context::default(); + context.request_id = "test-request-id".to_string(); + + let mut mock_client = MockListObjects::default(); + mock_client + .expect_list_objects() + .with(eq("test-bucket")) + .returning(|_| { + Ok(ListObjectsV2Output::builder() + .contents(Object::builder().key("test-key-0").build()) + .contents(Object::builder().key("test-key-1").build()) + .contents(Object::builder().key("test-key-2").build()) + .build()) + }); + + let payload = Request { + bucket: "test-bucket".to_string(), + }; + let event = LambdaEvent { payload, context }; + + let result = my_handler(event, &mock_client).await.unwrap(); + + let expected_keys = vec![ + "test-key-0".to_string(), + "test-key-1".to_string(), + "test-key-2".to_string(), + ]; + assert_eq!(result.req_id, "test-request-id".to_string()); + assert_eq!(result.bucket, "test-bucket".to_string()); + assert_eq!(result.objects, expected_keys); + } + + #[tokio::test] + async fn response_is_bad_for_bad_bucket() { + let mut context = Context::default(); + context.request_id = "test-request-id".to_string(); + + let mut mock_client = MockListObjects::default(); + mock_client + .expect_list_objects() + .with(eq("unknown-bucket")) + .returning(|_| Err(Error::from("test-sdk-error"))); + + let payload = Request { + bucket: "unknown-bucket".to_string(), + }; + let event = LambdaEvent { payload, context }; + + let result = my_handler(event, &mock_client).await; + assert!(result.is_err()); + } +} From 3888a1b38827e0c592bf1585eda4ed659fd80c6e Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Sat, 1 Apr 2023 16:27:46 +0200 Subject: [PATCH 170/394] s3 example - thumbnail creator (#613) (#621) --- examples/basic-s3-thumbnail/Cargo.toml | 32 +++ examples/basic-s3-thumbnail/README.md | 16 ++ examples/basic-s3-thumbnail/src/main.rs | 248 ++++++++++++++++++ examples/basic-s3-thumbnail/src/s3.rs | 49 ++++ .../basic-s3-thumbnail/testdata/image.png | Bin 0 -> 282 bytes .../basic-s3-thumbnail/testdata/thumbnail.png | Bin 0 -> 82 bytes 6 files changed, 345 insertions(+) create mode 100644 examples/basic-s3-thumbnail/Cargo.toml create mode 100644 examples/basic-s3-thumbnail/README.md create mode 100644 examples/basic-s3-thumbnail/src/main.rs create mode 100644 examples/basic-s3-thumbnail/src/s3.rs create mode 100644 examples/basic-s3-thumbnail/testdata/image.png create mode 100644 examples/basic-s3-thumbnail/testdata/thumbnail.png diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml new file mode 100644 index 00000000..dfa6d69b --- /dev/null +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "basic-s3-thumbnail" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws_lambda_events = "0.7.2" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +aws-config = "0.54.1" +aws-sdk-s3 = "0.24.0" +thumbnailer = "0.4.0" +mime = "0.3.16" +async-trait = "0.1.66" + +[dev-dependencies] +mockall = "0.11.3" +tokio-test = "0.4.2" diff --git a/examples/basic-s3-thumbnail/README.md b/examples/basic-s3-thumbnail/README.md new file mode 100644 index 00000000..de2d56f8 --- /dev/null +++ b/examples/basic-s3-thumbnail/README.md @@ -0,0 +1,16 @@ +# AWS Lambda Function that uses S3 + +This example processes S3 events. If the event is a CREATE event, +it downloads the created file, generates a thumbnail from it +(it assumes that the file is an image) and uploads it to S3 into a bucket named +[original-bucket-name]-thumbs. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` \ No newline at end of file diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs new file mode 100644 index 00000000..4ed7249f --- /dev/null +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -0,0 +1,248 @@ +use std::io::Cursor; + +use aws_lambda_events::{event::s3::S3Event, s3::S3EventRecord}; +use aws_sdk_s3::Client as S3Client; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use s3::{GetFile, PutFile}; +use thumbnailer::{create_thumbnails, ThumbnailSize}; + +mod s3; + +/** +This lambda handler + * listen to file creation events + * downloads the created file + * creates a thumbnail from it + * uploads the thumbnail to bucket "[original bucket name]-thumbs". + +Make sure that + * the created png file has no strange characters in the name + * there is another bucket with "-thumbs" suffix in the name + * this lambda only gets event from png file creation + * this lambda has permission to put file into the "-thumbs" bucket +*/ +pub(crate) async fn function_handler( + event: LambdaEvent, + size: u32, + client: &T, +) -> Result<(), Error> { + let records = event.payload.records; + + for record in records.into_iter() { + let (bucket, key) = match get_file_props(record) { + Ok(touple) => touple, + Err(msg) => { + tracing::info!("Record skipped with reason: {}", msg); + continue; + } + }; + + let image = match client.get_file(&bucket, &key).await { + Ok(vec) => vec, + Err(msg) => { + tracing::info!("Can not get file from S3: {}", msg); + continue; + } + }; + + let thumbnail = match get_thumbnail(image, size) { + Ok(vec) => vec, + Err(msg) => { + tracing::info!("Can not create thumbnail: {}", msg); + continue; + } + }; + + let mut thumbs_bucket = bucket.to_owned(); + thumbs_bucket.push_str("-thumbs"); + + // It uploads the thumbnail into a bucket name suffixed with "-thumbs" + // So it needs file creation permission into that bucket + + match client.put_file(&thumbs_bucket, &key, thumbnail).await { + Ok(msg) => tracing::info!(msg), + Err(msg) => tracing::info!("Can not upload thumbnail: {}", msg), + } + } + + Ok(()) +} + +fn get_file_props(record: S3EventRecord) -> Result<(String, String), String> { + record + .event_name + .filter(|s| s.starts_with("ObjectCreated")) + .ok_or("Wrong event")?; + + let bucket = record + .s3 + .bucket + .name + .filter(|s| !s.is_empty()) + .ok_or("No bucket name")?; + + let key = record.s3.object.key.filter(|s| !s.is_empty()).ok_or("No object key")?; + + Ok((bucket, key)) +} + +fn get_thumbnail(vec: Vec, size: u32) -> Result, String> { + let reader = Cursor::new(vec); + let mime = mime::IMAGE_PNG; + let sizes = [ThumbnailSize::Custom((size, size))]; + + let thumbnail = match create_thumbnails(reader, mime, sizes) { + Ok(mut thumbnails) => thumbnails.pop().ok_or("No thumbnail created")?, + Err(thumb_error) => return Err(thumb_error.to_string()), + }; + + let mut buf = Cursor::new(Vec::new()); + + match thumbnail.write_png(&mut buf) { + Ok(_) => Ok(buf.into_inner()), + Err(_) => Err("Unknown error when Thumbnail::write_png".to_string()), + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let shared_config = aws_config::load_from_env().await; + let client = S3Client::new(&shared_config); + let client_ref = &client; + + let func = service_fn(move |event| async move { function_handler(event, 128, client_ref).await }); + + run(func).await?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::fs::File; + use std::io::BufReader; + use std::io::Read; + + use super::*; + use async_trait::async_trait; + use aws_lambda_events::chrono::DateTime; + use aws_lambda_events::s3::S3Bucket; + use aws_lambda_events::s3::S3Entity; + use aws_lambda_events::s3::S3Object; + use aws_lambda_events::s3::S3RequestParameters; + use aws_lambda_events::s3::S3UserIdentity; + use aws_sdk_s3::error::GetObjectError; + use lambda_runtime::{Context, LambdaEvent}; + use mockall::mock; + use s3::GetFile; + use s3::PutFile; + + #[tokio::test] + async fn response_is_good() { + let mut context = Context::default(); + context.request_id = "test-request-id".to_string(); + + let bucket = "test-bucket"; + let key = "test-key"; + + mock! { + FakeS3Client {} + + #[async_trait] + impl GetFile for FakeS3Client { + pub async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; + } + #[async_trait] + impl PutFile for FakeS3Client { + pub async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; + } + } + + let mut mock = MockFakeS3Client::new(); + + mock.expect_get_file() + .withf(|b: &str, k: &str| b.eq(bucket) && k.eq(key)) + .returning(|_1, _2| Ok(get_file("testdata/image.png"))); + + mock.expect_put_file() + .withf(|bu: &str, ke: &str, by| { + let thumbnail = get_file("testdata/thumbnail.png"); + return bu.eq("test-bucket-thumbs") && ke.eq(key) && by == &thumbnail; + }) + .returning(|_1, _2, _3| Ok("Done".to_string())); + + let payload = get_s3_event("ObjectCreated", bucket, key); + let event = LambdaEvent { payload, context }; + + let result = function_handler(event, 10, &mock).await.unwrap(); + + assert_eq!((), result); + } + + fn get_file(name: &str) -> Vec { + let f = File::open(name); + let mut reader = BufReader::new(f.unwrap()); + let mut buffer = Vec::new(); + + reader.read_to_end(&mut buffer).unwrap(); + + return buffer; + } + + fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { + return S3Event { + records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), + }; + } + + fn get_s3_event_record(event_name: &str, bucket_name: &str, object_key: &str) -> S3EventRecord { + let s3_entity = S3Entity { + schema_version: (Some(String::default())), + configuration_id: (Some(String::default())), + bucket: (S3Bucket { + name: (Some(bucket_name.to_string())), + owner_identity: (S3UserIdentity { + principal_id: (Some(String::default())), + }), + arn: (Some(String::default())), + }), + object: (S3Object { + key: (Some(object_key.to_string())), + size: (Some(1)), + url_decoded_key: (Some(String::default())), + version_id: (Some(String::default())), + e_tag: (Some(String::default())), + sequencer: (Some(String::default())), + }), + }; + + return S3EventRecord { + event_version: (Some(String::default())), + event_source: (Some(String::default())), + aws_region: (Some(String::default())), + event_time: (DateTime::default()), + event_name: (Some(event_name.to_string())), + principal_id: (S3UserIdentity { + principal_id: (Some("X".to_string())), + }), + request_parameters: (S3RequestParameters { + source_ip_address: (Some(String::default())), + }), + response_elements: (HashMap::new()), + s3: (s3_entity), + }; + } +} diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs new file mode 100644 index 00000000..83ef7bc7 --- /dev/null +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; +use aws_sdk_s3::{error::GetObjectError, types::ByteStream, Client as S3Client}; + +#[async_trait] +pub trait GetFile { + async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; +} + +#[async_trait] +pub trait PutFile { + async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; +} + +#[async_trait] +impl GetFile for S3Client { + async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError> { + tracing::info!("get file bucket {}, key {}", bucket, key); + + let output = self.get_object().bucket(bucket).key(key).send().await; + + return match output { + Ok(response) => { + let bytes = response.body.collect().await.unwrap().to_vec(); + tracing::info!("Object is downloaded, size is {}", bytes.len()); + Ok(bytes) + } + Err(err) => { + let service_err = err.into_service_error(); + let meta = service_err.meta(); + tracing::info!("Error from aws when downloding: {}", meta.to_string()); + Err(service_err) + } + }; + } +} + +#[async_trait] +impl PutFile for S3Client { + async fn put_file(&self, bucket: &str, key: &str, vec: Vec) -> Result { + tracing::info!("put file bucket {}, key {}", bucket, key); + let bytes = ByteStream::from(vec); + let result = self.put_object().bucket(bucket).key(key).body(bytes).send().await; + + match result { + Ok(_) => Ok(format!("Uploaded a file with key {} into {}", key, bucket)), + Err(err) => Err(err.into_service_error().meta().message().unwrap().to_string()), + } + } +} diff --git a/examples/basic-s3-thumbnail/testdata/image.png b/examples/basic-s3-thumbnail/testdata/image.png new file mode 100644 index 0000000000000000000000000000000000000000..078d155f6bf6735eb087eb0195b3e35f9f424d04 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-UBp4!QuJ{S0SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+uenM@oty!5+IMg#M9T6{W-Ikn3DL(-uF5{ArVg(#}JFt$q5pyixWh8ngSjC85meA z7#KARcm4s&tCqM%l%ynf4NqV|ChEy=Vy|59;VQAR!XXWJ= d863$M85tR8F)&*H Date: Sat, 8 Apr 2023 13:29:01 -0700 Subject: [PATCH 171/394] Add Axum+Diesel example (#629) --- examples/http-axum-diesel/Cargo.toml | 23 ++++ examples/http-axum-diesel/README.md | 13 ++ .../2023-04-07-231632_create_posts/down.sql | 2 + .../2023-04-07-231632_create_posts/up.sql | 7 + examples/http-axum-diesel/src/main.rs | 122 ++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 examples/http-axum-diesel/Cargo.toml create mode 100644 examples/http-axum-diesel/README.md create mode 100644 examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/down.sql create mode 100644 examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/up.sql create mode 100644 examples/http-axum-diesel/src/main.rs diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml new file mode 100644 index 00000000..dd37346f --- /dev/null +++ b/examples/http-axum-diesel/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "http-axum-diesel" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +axum = "0.6.4" +bb8 = "0.8.0" +diesel = "2.0.3" +diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.159" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } diff --git a/examples/http-axum-diesel/README.md b/examples/http-axum-diesel/README.md new file mode 100644 index 00000000..8b2330f5 --- /dev/null +++ b/examples/http-axum-diesel/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function example + +This example shows how to develop a REST API with Axum and Diesel that connects to a Postgres database. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/down.sql b/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/down.sql new file mode 100644 index 00000000..e00da655 --- /dev/null +++ b/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE posts \ No newline at end of file diff --git a/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/up.sql b/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/up.sql new file mode 100644 index 00000000..aa684de6 --- /dev/null +++ b/examples/http-axum-diesel/migrations/2023-04-07-231632_create_posts/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR NOT NULL, + content TEXT NOT NULL, + published BOOLEAN NOT NULL DEFAULT FALSE +) \ No newline at end of file diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs new file mode 100644 index 00000000..227e23dd --- /dev/null +++ b/examples/http-axum-diesel/src/main.rs @@ -0,0 +1,122 @@ +use axum::{ + extract::{Path, State}, + response::Json, + routing::get, + Router, +}; +use bb8::Pool; +use diesel::prelude::*; +use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use lambda_http::{http::StatusCode, run, Error}; +use serde::{Deserialize, Serialize}; + +table! { + posts (id) { + id -> Integer, + title -> Text, + content -> Text, + published -> Bool, + } +} + +#[derive(Default, Queryable, Selectable, Serialize)] +struct Post { + id: i32, + title: String, + content: String, + published: bool, +} + +#[derive(Deserialize, Insertable)] +#[diesel(table_name = posts)] +struct NewPost { + title: String, + content: String, + published: bool, +} + +type AsyncPool = Pool>; +type ServerError = (StatusCode, String); + +async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = diesel::insert_into(posts::table) + .values(post) + .returning(Post::as_returning()) + .get_result(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn list_posts(State(pool): State) -> Result>, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let posts = posts::table + .filter(posts::dsl::published.eq(true)) + .load(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(posts)) +} + +async fn get_post(State(pool): State, Path(post_id): Path) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = posts::table + .find(post_id) + .first(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn delete_post(State(pool): State, Path(post_id): Path) -> Result<(), ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + diesel::delete(posts::table.find(post_id)) + .execute(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(()) +} + +fn internal_server_error(err: E) -> ServerError { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + // Set up the database connection + let db_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable"); + let config = AsyncDieselConnectionManager::::new(db_url); + let connection = Pool::builder() + .build(config) + .await + .expect("unable to establish the database connection"); + + // Set up the API routes + let posts_api = Router::new() + .route("/", get(list_posts).post(create_post)) + .route("/:id", get(get_post).delete(delete_post)); + let app = Router::new().nest("/posts", posts_api).with_state(connection); + + run(app).await +} From 3ee4389df868fa5365eb800ac3eb3969a7d1320f Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 9 Apr 2023 05:06:54 +0000 Subject: [PATCH 172/394] Add support for Lambda streaming response (#628) --- examples/basic-streaming-response/Cargo.toml | 18 ++ examples/basic-streaming-response/README.md | 13 + examples/basic-streaming-response/src/main.rs | 42 +++ lambda-http/Cargo.toml | 21 +- lambda-http/src/lib.rs | 3 + lambda-http/src/streaming.rs | 34 +++ lambda-runtime-api-client/src/lib.rs | 4 +- lambda-runtime/src/lib.rs | 3 + lambda-runtime/src/streaming.rs | 258 ++++++++++++++++++ 9 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 examples/basic-streaming-response/Cargo.toml create mode 100644 examples/basic-streaming-response/README.md create mode 100644 examples/basic-streaming-response/src/main.rs create mode 100644 lambda-http/src/streaming.rs create mode 100644 lambda-runtime/src/streaming.rs diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml new file mode 100644 index 00000000..fc284674 --- /dev/null +++ b/examples/basic-streaming-response/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "basic-streaming-response" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = { version = "0.14", features = [ + "http1", + "client", + "stream", +] } +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +serde_json = "1.0" \ No newline at end of file diff --git a/examples/basic-streaming-response/README.md b/examples/basic-streaming-response/README.md new file mode 100644 index 00000000..3b68f518 --- /dev/null +++ b/examples/basic-streaming-response/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` +4. Enable Lambda streaming response on Lambda console: change the function url's invoke mode to `RESPONSE_STREAM` +5. Verify the function works: `curl `. The results should be streamed back with 0.5 second pause between each word. + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs new file mode 100644 index 00000000..04c7f8ec --- /dev/null +++ b/examples/basic-streaming-response/src/main.rs @@ -0,0 +1,42 @@ +use hyper::{body::Body, Response}; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde_json::Value; +use std::{thread, time::Duration}; + +async fn func(_event: LambdaEvent) -> Result, Error> { + let messages = vec!["Hello", "world", "from", "Lambda!"]; + + let (mut tx, rx) = Body::channel(); + + tokio::spawn(async move { + for message in messages.iter() { + tx.send_data((message.to_string() + "\n").into()).await.unwrap(); + thread::sleep(Duration::from_millis(500)); + } + }); + + let resp = Response::builder() + .header("content-type", "text/html") + .header("CustomHeader", "outerspace") + .body(rx)?; + + Ok(resp) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + lambda_runtime::run_with_streaming_response(service_fn(func)).await?; + Ok(()) +} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index a2ac6250..aacf739b 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -23,19 +23,20 @@ apigw_websockets = [] alb = [] [dependencies] -base64 = "0.13.0" -bytes = "1" +base64 = "0.21" +bytes = "1.4" +futures = "0.3" http = "0.2" http-body = "0.4" -hyper = "0.14.20" +hyper = "0.14" lambda_runtime = { path = "../lambda-runtime", version = "0.7" } -serde = { version = "^1", features = ["derive"] } -serde_json = "^1" -serde_urlencoded = "0.7.0" -mime = "0.3.16" -encoding_rs = "0.8.31" -url = "2.2.2" -percent-encoding = "2.2.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_urlencoded = "0.7" +mime = "0.3" +encoding_rs = "0.8" +url = "2.2" +percent-encoding = "2.2" [dependencies.aws_lambda_events] version = "^0.7.2" diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 8d030a75..b4d9c5bd 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -92,6 +92,9 @@ use std::{ task::{Context as TaskContext, Poll}, }; +mod streaming; +pub use streaming::run_with_streaming_response; + /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs new file mode 100644 index 00000000..150002be --- /dev/null +++ b/lambda-http/src/streaming.rs @@ -0,0 +1,34 @@ +use crate::request::LambdaRequest; +use crate::tower::ServiceBuilder; +use crate::{Request, RequestExt}; +pub use aws_lambda_events::encodings::Body as LambdaEventBody; +use bytes::Bytes; +pub use http::{self, Response}; +use http_body::Body; +use lambda_runtime::LambdaEvent; +pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +use std::fmt::{Debug, Display}; + +/// Starts the Lambda Rust runtime and stream response back [Configure Lambda +/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). +/// +/// This takes care of transforming the LambdaEvent into a [`Request`] and +/// accepts [`http::Response`] as response. +pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), Error> +where + S: Service, Error = E>, + S::Future: Send + 'a, + E: Debug + Display, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + let svc = ServiceBuilder::new() + .map_request(|req: LambdaEvent| { + let event: Request = req.payload.into(); + event.with_lambda_context(req.context) + }) + .service(handler); + + lambda_runtime::run_with_streaming_response(svc).await +} diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 42a4c54b..4b082aba 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -53,7 +53,9 @@ where /// Create a new client with a given base URI and HTTP connector. pub fn with(base: Uri, connector: C) -> Self { - let client = hyper::Client::builder().build(connector); + let client = hyper::Client::builder() + .http1_max_buf_size(1024 * 1024) + .build(connector); Self { base, client } } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index cf03664e..31c9297c 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -34,6 +34,9 @@ mod simulated; /// Types available to a Lambda function. mod types; +mod streaming; +pub use streaming::run_with_streaming_response; + use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; pub use types::{Context, LambdaEvent}; diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs new file mode 100644 index 00000000..85af784e --- /dev/null +++ b/lambda-runtime/src/streaming.rs @@ -0,0 +1,258 @@ +use crate::{ + build_event_error_request, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, IntoRequest, + LambdaEvent, Runtime, +}; +use bytes::Bytes; +use futures::FutureExt; +use http::header::{CONTENT_TYPE, SET_COOKIE}; +use http::{Method, Request, Response, Uri}; +use hyper::body::HttpBody; +use hyper::{client::connect::Connection, Body}; +use lambda_runtime_api_client::{build_request, Client}; +use serde::Deserialize; +use serde_json::json; +use std::collections::HashMap; +use std::str::FromStr; +use std::{ + env, + fmt::{self, Debug, Display}, + future::Future, + panic, +}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_stream::{Stream, StreamExt}; +use tower::{Service, ServiceExt}; +use tracing::{error, trace, Instrument}; + +/// Starts the Lambda Rust runtime and stream response back [Configure Lambda +/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). +/// +/// # Example +/// ```no_run +/// use hyper::{body::Body, Response}; +/// use lambda_runtime::{service_fn, Error, LambdaEvent}; +/// use std::{thread, time::Duration}; +/// use serde_json::Value; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// lambda_runtime::run_with_streaming_response(service_fn(func)).await?; +/// Ok(()) +/// } +/// async fn func(_event: LambdaEvent) -> Result, Error> { +/// let messages = vec!["Hello ", "world ", "from ", "Lambda!"]; +/// +/// let (mut tx, rx) = Body::channel(); +/// +/// tokio::spawn(async move { +/// for message in messages.iter() { +/// tx.send_data((*message).into()).await.unwrap(); +/// thread::sleep(Duration::from_millis(500)); +/// } +/// }); +/// +/// let resp = Response::builder() +/// .header("content-type", "text/plain") +/// .header("CustomHeader", "outerspace") +/// .body(rx)?; +/// +/// Ok(resp) +/// } +/// ``` +pub async fn run_with_streaming_response(handler: F) -> Result<(), Error> +where + F: Service>, + F::Future: Future, F::Error>>, + F::Error: Debug + Display, + A: for<'de> Deserialize<'de>, + B: HttpBody + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + trace!("Loading config from env"); + let config = Config::from_env()?; + let client = Client::builder().build().expect("Unable to create a runtime client"); + let runtime = Runtime { client }; + + let client = &runtime.client; + let incoming = incoming(client); + runtime.run_with_streaming_response(incoming, handler, &config).await +} + +impl Runtime +where + C: Service + Clone + Send + Sync + Unpin + 'static, + C::Future: Unpin + Send, + C::Error: Into>, + C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, +{ + pub async fn run_with_streaming_response( + &self, + incoming: impl Stream, Error>> + Send, + mut handler: F, + config: &Config, + ) -> Result<(), Error> + where + F: Service>, + F::Future: Future, F::Error>>, + F::Error: fmt::Debug + fmt::Display, + A: for<'de> Deserialize<'de>, + B: HttpBody + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, + { + let client = &self.client; + tokio::pin!(incoming); + while let Some(next_event_response) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = next_event_response?; + let (parts, body) = event.into_parts(); + + let ctx: Context = Context::try_from(parts.headers)?; + let ctx: Context = ctx.with_config(config); + let request_id = &ctx.request_id.clone(); + + let request_span = match &ctx.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = request_id) + } + }; + + // Group the handling in one future and instrument it with the span + async { + let body = hyper::body::to_bytes(body).await?; + trace!("incoming request payload - {}", std::str::from_utf8(&body)?); + + let body = match serde_json::from_slice(&body) { + Ok(body) => body, + Err(err) => { + let req = build_event_error_request(request_id, err)?; + client.call(req).await.expect("Unable to send response to Runtime APIs"); + return Ok(()); + } + }; + + let req = match handler.ready().await { + Ok(handler) => { + // Catches panics outside of a `Future` + let task = + panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + + let task = match task { + // Catches panics inside of the `Future` + Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, + Err(err) => Err(err), + }; + + match task { + Ok(response) => match response { + Ok(response) => { + trace!("Ok response from handler (run loop)"); + EventCompletionStreamingRequest { + request_id, + body: response, + } + .into_req() + } + Err(err) => build_event_error_request(request_id, err), + }, + Err(err) => { + error!("{:?}", err); + let error_type = type_name_of_val(&err); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {msg}") + } else { + "Lambda panicked".to_string() + }; + EventErrorRequest::new(request_id, error_type, &msg).into_req() + } + } + } + Err(err) => build_event_error_request(request_id, err), + }?; + + client.call(req).await.expect("Unable to send response to Runtime APIs"); + Ok::<(), Error>(()) + } + .instrument(request_span) + .await?; + } + Ok(()) + } +} + +pub(crate) struct EventCompletionStreamingRequest<'a, B> { + pub(crate) request_id: &'a str, + pub(crate) body: Response, +} + +impl<'a, B> EventCompletionStreamingRequest<'a, B> +where + B: HttpBody + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + fn into_req(self) -> Result, Error> { + let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); + let uri = Uri::from_str(&uri)?; + + let (parts, mut body) = self.body.into_parts(); + + let mut builder = build_request().method(Method::POST).uri(uri); + let headers = builder.headers_mut().unwrap(); + + headers.insert("Transfer-Encoding", "chunked".parse()?); + headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); + headers.insert( + "Content-Type", + "application/vnd.awslambda.http-integration-response".parse()?, + ); + + let (mut tx, rx) = Body::channel(); + + tokio::spawn(async move { + let mut header_map = parts.headers; + // default Content-Type + header_map + .entry(CONTENT_TYPE) + .or_insert("application/octet-stream".parse().unwrap()); + + let cookies = header_map.get_all(SET_COOKIE); + let cookies = cookies + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + + let headers = header_map + .iter() + .filter(|(k, _)| *k != SET_COOKIE) + .map(|(k, v)| (k.as_str(), String::from_utf8_lossy(v.as_bytes()).to_string())) + .collect::>(); + + let metadata_prelude = json!({ + "statusCode": parts.status.as_u16(), + "headers": headers, + "cookies": cookies, + }) + .to_string(); + + trace!("metadata_prelude: {}", metadata_prelude); + + tx.send_data(metadata_prelude.into()).await.unwrap(); + tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); + + while let Some(chunk) = body.data().await { + let chunk = chunk.unwrap(); + tx.send_data(chunk.into()).await.unwrap(); + } + }); + + let req = builder.body(rx)?; + Ok(req) + } +} From 52e75e223ef8f9cab3ed5ba58f20a23fea6ffd10 Mon Sep 17 00:00:00 2001 From: Daniel Cormier Date: Mon, 10 Apr 2023 17:51:27 -0400 Subject: [PATCH 173/394] Lambda-related helper methods for `http::request::Parts` and `http::Extensions` (#607) --- examples/http-cors/src/main.rs | 19 +- examples/http-query-parameters/src/main.rs | 19 +- examples/http-shared-resource/src/main.rs | 35 +- lambda-http/README.md | 49 +- lambda-http/src/ext.rs | 517 --------------- lambda-http/src/ext/extensions.rs | 627 ++++++++++++++++++ lambda-http/src/ext/mod.rs | 7 + lambda-http/src/ext/request.rs | 220 ++++++ lambda-http/src/lib.rs | 17 +- lambda-http/src/request.rs | 70 +- lambda-http/src/streaming.rs | 4 +- lambda-integration-tests/src/bin/http-fn.rs | 2 +- .../src/bin/http-trait.rs | 2 +- lambda-runtime/src/types.rs | 2 +- 14 files changed, 985 insertions(+), 605 deletions(-) delete mode 100644 lambda-http/src/ext.rs create mode 100644 lambda-http/src/ext/extensions.rs create mode 100644 lambda-http/src/ext/mod.rs create mode 100644 lambda-http/src/ext/request.rs diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index f1ee0955..ea1f0372 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -32,11 +32,16 @@ async fn main() -> Result<(), Error> { } async fn func(event: Request) -> Result, Error> { - Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) + Ok( + match event + .query_string_parameters_ref() + .and_then(|params| params.first("first_name")) + { + Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + None => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }, + ) } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 2a33829c..0300df37 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -6,13 +6,18 @@ use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Res /// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples async fn function_handler(event: Request) -> Result { // Extract some useful information from the request - Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) + Ok( + match event + .query_string_parameters_ref() + .and_then(|params| params.first("first_name")) + { + Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + None => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }, + ) } #[tokio::main] diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index e8d65e80..16493452 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -32,18 +32,29 @@ async fn main() -> Result<(), Error> { // Define a closure here that makes use of the shared client. let handler_func_closure = move |event: Request| async move { - Result::, Error>::Ok(match event.query_string_parameters().first("first_name") { - Some(first_name) => { - shared_client_ref - .response(event.lambda_context().request_id, first_name) - .into_response() - .await - } - _ => Response::builder() - .status(400) - .body("Empty first name".into()) - .expect("failed to render response"), - }) + Result::, Error>::Ok( + match event + .query_string_parameters_ref() + .and_then(|params| params.first("first_name")) + { + Some(first_name) => { + shared_client_ref + .response( + event + .lambda_context_ref() + .map(|ctx| ctx.request_id.clone()) + .unwrap_or_default(), + first_name, + ) + .into_response() + .await + } + None => Response::builder() + .status(400) + .body("Empty first name".into()) + .expect("failed to render response"), + }, + ) }; // Pass the closure to the runtime here. diff --git a/lambda-http/README.md b/lambda-http/README.md index 1eef90c6..161a5576 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -6,8 +6,8 @@ lambda-http handler is made of: -* Request - Represents an HTTP request -* IntoResponse - Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] +* `Request` - Represents an HTTP request +* `IntoResponse` - Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] We are able to handle requests from: @@ -15,32 +15,35 @@ We are able to handle requests from: * AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) * AWS [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) -Thanks to the Request type we can seemsly handle proxy integrations without the worry to specify the specific service type. +Thanks to the `Request` type we can seamlessly handle proxy integrations without the worry to specify the specific service type. -There is also an Extentions for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. +There is also an extension for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. For example some handy extensions: -* query_string_parameters - Return pre-parsed http query string parameters, parameters provided after the `?` portion of a url associated with the request -* path_parameters - Return pre-extracted path parameters, parameter provided in url placeholders `/foo/{bar}/baz/{boom}` associated with the request -* payload - Return the Result of a payload parsed into a serde Deserializeable type +* `query_string_parameters` - Return pre-parsed http query string parameters, parameters provided after the `?` portion of a url associated with the request +* `path_parameters` - Return pre-extracted path parameters, parameter provided in url placeholders `/foo/{bar}/baz/{qux}` associated with the request +* `lambda_context` - Return the Lambda context for the invocation; see the [runtime docs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next) +* `request_context` - Return the ALB/API Gateway request context +* payload - Return the Result of a payload parsed into a type that implements `serde::Deserialize` + +See the `lambda_http::RequestPayloadExt` and `lambda_http::RequestExt` traits for more extensions. ## Examples -Here you will find a few examples to handle basic scenarions: +Here you will find a few examples to handle basic scenarios: -* Reading a JSON from a body and deserialise into a structure -* Reading querystring parameters +* Reading a JSON from a body and deserialize into a structure +* Reading query string parameters * Lambda Request Authorizer -* Passing the Lambda execution context initialisation to the handler +* Passing the Lambda execution context initialization to the handler -### Reading a JSON from a body and deserialise into a structure +### Reading a JSON from a body and deserialize into a structure The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload. ```rust -use http::Response; -use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, IntoResponse, Request, RequestPayloadExt}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -77,11 +80,10 @@ pub struct MyPayload { } ``` -### Reading querystring parameters +### Reading query string parameters ```rust -use http::Response; -use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, RequestExt, IntoResponse, Request}; use serde_json::json; #[tokio::main] @@ -96,8 +98,8 @@ async fn main() -> Result<(), Error> { } pub async fn function_handler(event: Request) -> Result { - let name = event.query_string_parameters() - .first("name") + let name = event.query_string_parameters_ref() + .and_then(|params| params.first("name")) .unwrap_or_else(|| "stranger") .to_string(); @@ -176,15 +178,14 @@ pub fn custom_authorizer_response(effect: &str, principal: &str, method_arn: &st } ``` -## Passing the Lambda execution context initialisation to the handler +## Passing the Lambda execution context initialization to the handler One of the [best practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. ```rust use aws_sdk_dynamodb::model::AttributeValue; use chrono::Utc; -use http::Response; -use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt}; +use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, RequestExt, IntoResponse, Request}; use serde_json::json; #[tokio::main] @@ -207,8 +208,8 @@ async fn main() -> Result<(), Error> { pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Request) -> Result { let table = std::env::var("TABLE_NAME").expect("TABLE_NAME must be set"); - let name = event.query_string_parameters() - .first("name") + let name = event.query_string_parameters_ref() + .and_then(|params| params.first("name")) .unwrap_or_else(|| "stranger") .to_string(); diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs deleted file mode 100644 index f034c686..00000000 --- a/lambda-http/src/ext.rs +++ /dev/null @@ -1,517 +0,0 @@ -//! Extension methods for `http::Request` types - -use crate::{request::RequestContext, Body}; -use aws_lambda_events::query_map::QueryMap; -use lambda_runtime::Context; -use serde::{de::value::Error as SerdeError, Deserialize}; -use std::{error::Error, fmt}; - -/// ALB/API gateway pre-parsed http query string parameters -pub(crate) struct QueryStringParameters(pub(crate) QueryMap); - -/// API gateway pre-extracted url path parameters -/// -/// These will always be empty for ALB requests -pub(crate) struct PathParameters(pub(crate) QueryMap); - -/// API gateway configured -/// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) -/// -/// These will always be empty for ALB requests -pub(crate) struct StageVariables(pub(crate) QueryMap); - -/// ALB/API gateway raw http path without any stage information -pub(crate) struct RawHttpPath(pub(crate) String); - -/// Request payload deserialization errors -/// -/// Returned by [`RequestExt#payload()`](trait.RequestExt.html#tymethod.payload) -#[derive(Debug)] -pub enum PayloadError { - /// Returned when `application/json` bodies fail to deserialize a payload - Json(serde_json::Error), - /// Returned when `application/x-www-form-urlencoded` bodies fail to deserialize a payload - WwwFormUrlEncoded(SerdeError), -} - -impl fmt::Display for PayloadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PayloadError::Json(json) => writeln!(f, "failed to parse payload from application/json {json}"), - PayloadError::WwwFormUrlEncoded(form) => writeln!( - f, - "failed to parse payload from application/x-www-form-urlencoded {form}" - ), - } - } -} - -impl Error for PayloadError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - PayloadError::Json(json) => Some(json), - PayloadError::WwwFormUrlEncoded(form) => Some(form), - } - } -} - -/// Extensions for `lambda_http::Request` structs that -/// provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) -/// and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) -/// features. -/// -/// # Examples -/// -/// A request's body can be deserialized if its correctly encoded as per -/// the request's `Content-Type` header. The two supported content types are -/// `application/x-www-form-urlencoded` and `application/json`. -/// -/// The following handler will work an http request body of `x=1&y=2` -/// as well as `{"x":1, "y":2}` respectively. -/// -/// ```rust,no_run -/// use lambda_http::{service_fn, Error, Context, Body, IntoResponse, Request, Response, RequestExt}; -/// use serde::Deserialize; -/// -/// #[derive(Debug,Deserialize,Default)] -/// struct Args { -/// #[serde(default)] -/// x: usize, -/// #[serde(default)] -/// y: usize -/// } -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Error> { -/// lambda_http::run(service_fn(add)).await?; -/// Ok(()) -/// } -/// -/// async fn add( -/// request: Request -/// ) -> Result, Error> { -/// let args: Args = request.payload() -/// .unwrap_or_else(|_parse_err| None) -/// .unwrap_or_default(); -/// Ok( -/// Response::new( -/// format!( -/// "{} + {} = {}", -/// args.x, -/// args.y, -/// args.x + args.y -/// ).into() -/// ) -/// ) -/// } -/// ``` -pub trait RequestExt { - /// Return the raw http path for a request without any stage information. - fn raw_http_path(&self) -> String; - - /// Return the raw http path for a request without any stage information. - fn raw_http_path_str(&self) -> &str; - - /// Configures instance with the raw http path. - fn with_raw_http_path(self, path: &str) -> Self; - - /// Return pre-parsed http query string parameters, parameters - /// provided after the `?` portion of a url, - /// associated with the API gateway request. - /// - /// The yielded value represents both single and multi-valued - /// parameters alike. When multiple query string parameters with the same - /// name are expected, `query_string_parameters().all("many")` to retrieve them all. - /// - /// No query parameters will yield an empty `QueryMap`. - fn query_string_parameters(&self) -> QueryMap; - - /// Return pre-parsed http query string parameters, parameters - /// provided after the `?` portion of a url, - /// associated with the API gateway request. - /// - /// The yielded value represents both single and multi-valued - /// parameters alike. When multiple query string parameters with the same - /// name are expected, - /// `query_string_parameters_ref().and_then(|params| params.all("many"))` to - /// retrieve them all. - /// - /// No query parameters will yield `None`. - fn query_string_parameters_ref(&self) -> Option<&QueryMap>; - - /// Configures instance with query string parameters - /// - /// This is intended for use in mock testing contexts. - fn with_query_string_parameters(self, parameters: Q) -> Self - where - Q: Into; - - /// Return pre-extracted path parameters, parameter provided in url placeholders - /// `/foo/{bar}/baz/{boom}`, - /// associated with the API gateway request. No path parameters - /// will yield an empty `QueryMap` - /// - /// These will always be empty for ALB triggered requests - fn path_parameters(&self) -> QueryMap; - - /// Return pre-extracted path parameters, parameter provided in url placeholders - /// `/foo/{bar}/baz/{boom}`, - /// associated with the API gateway request. No path parameters - /// will yield `None` - /// - /// These will always be `None` for ALB triggered requests - fn path_parameters_ref(&self) -> Option<&QueryMap>; - - /// Configures instance with path parameters - /// - /// This is intended for use in mock testing contexts. - fn with_path_parameters

(self, parameters: P) -> Self - where - P: Into; - - /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) - /// associated with the API gateway request. No stage parameters - /// will yield an empty `QueryMap` - /// - /// These will always be empty for ALB triggered requests - fn stage_variables(&self) -> QueryMap; - - /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) - /// associated with the API gateway request. No stage parameters - /// will yield `None` - /// - /// These will always be `None` for ALB triggered requests - fn stage_variables_ref(&self) -> Option<&QueryMap>; - - /// Configures instance with stage variables under #[cfg(test)] configurations - /// - /// This is intended for use in mock testing contexts. - #[cfg(test)] - fn with_stage_variables(self, variables: V) -> Self - where - V: Into; - - /// Return request context data associated with the ALB or API gateway request - fn request_context(&self) -> RequestContext; - - /// Return a reference to the request context data associated with the ALB or - /// API gateway request - fn request_context_ref(&self) -> Option<&RequestContext>; - - /// Configures instance with request context - /// - /// This is intended for use in mock testing contexts. - fn with_request_context(self, context: RequestContext) -> Self; - - /// Return the Result of a payload parsed into a serde Deserializeable - /// type - /// - /// Currently only `application/x-www-form-urlencoded` - /// and `application/json` flavors of content type - /// are supported - /// - /// A [PayloadError](enum.PayloadError.html) will be returned for undeserializable - /// payloads. If no body is provided, `Ok(None)` will be returned. - fn payload(&self) -> Result, PayloadError> - where - for<'de> D: Deserialize<'de>; - - /// Return the Lambda function context associated with the request - fn lambda_context(&self) -> Context; - - /// Return a reference to the the Lambda function context associated with the - /// request - fn lambda_context_ref(&self) -> Option<&Context>; - - /// Configures instance with lambda context - fn with_lambda_context(self, context: Context) -> Self; -} - -impl RequestExt for http::Request { - fn raw_http_path(&self) -> String { - self.raw_http_path_str().to_string() - } - - fn raw_http_path_str(&self) -> &str { - self.extensions() - .get::() - .map(|RawHttpPath(path)| path.as_str()) - .unwrap_or_default() - } - - fn with_raw_http_path(self, path: &str) -> Self { - let mut s = self; - s.extensions_mut().insert(RawHttpPath(path.into())); - s - } - - fn query_string_parameters(&self) -> QueryMap { - self.query_string_parameters_ref().cloned().unwrap_or_default() - } - - fn query_string_parameters_ref(&self) -> Option<&QueryMap> { - self.extensions().get::().and_then( - |QueryStringParameters(params)| { - if params.is_empty() { - None - } else { - Some(params) - } - }, - ) - } - - fn with_query_string_parameters(self, parameters: Q) -> Self - where - Q: Into, - { - let mut s = self; - s.extensions_mut().insert(QueryStringParameters(parameters.into())); - s - } - - fn path_parameters(&self) -> QueryMap { - self.path_parameters_ref().cloned().unwrap_or_default() - } - - fn path_parameters_ref(&self) -> Option<&QueryMap> { - self.extensions().get::().and_then( - |PathParameters(params)| { - if params.is_empty() { - None - } else { - Some(params) - } - }, - ) - } - - fn with_path_parameters

(self, parameters: P) -> Self - where - P: Into, - { - let mut s = self; - s.extensions_mut().insert(PathParameters(parameters.into())); - s - } - - fn stage_variables(&self) -> QueryMap { - self.stage_variables_ref().cloned().unwrap_or_default() - } - - fn stage_variables_ref(&self) -> Option<&QueryMap> { - self.extensions().get::().and_then( - |StageVariables(vars)| { - if vars.is_empty() { - None - } else { - Some(vars) - } - }, - ) - } - - #[cfg(test)] - fn with_stage_variables(self, variables: V) -> Self - where - V: Into, - { - let mut s = self; - s.extensions_mut().insert(StageVariables(variables.into())); - s - } - - fn request_context(&self) -> RequestContext { - self.request_context_ref() - .cloned() - .expect("Request did not contain a request context") - } - - fn request_context_ref(&self) -> Option<&RequestContext> { - self.extensions().get::() - } - - fn with_request_context(self, context: RequestContext) -> Self { - let mut s = self; - s.extensions_mut().insert(context); - s - } - - fn lambda_context(&self) -> Context { - self.lambda_context_ref() - .cloned() - .expect("Request did not contain a lambda context") - } - - fn lambda_context_ref(&self) -> Option<&Context> { - self.extensions().get::() - } - - fn with_lambda_context(self, context: Context) -> Self { - let mut s = self; - s.extensions_mut().insert(context); - s - } - - fn payload(&self) -> Result, PayloadError> - where - for<'de> D: Deserialize<'de>, - { - self.headers() - .get(http::header::CONTENT_TYPE) - .map(|ct| match ct.to_str() { - Ok(content_type) => { - if content_type.starts_with("application/x-www-form-urlencoded") { - return serde_urlencoded::from_bytes::(self.body().as_ref()) - .map_err(PayloadError::WwwFormUrlEncoded) - .map(Some); - } else if content_type.starts_with("application/json") { - return serde_json::from_slice::(self.body().as_ref()) - .map_err(PayloadError::Json) - .map(Some); - } - - Ok(None) - } - _ => Ok(None), - }) - .unwrap_or_else(|| Ok(None)) - } -} - -#[cfg(test)] -mod tests { - use crate::{Body, Request, RequestExt}; - use serde::Deserialize; - - #[test] - fn requests_can_mock_query_string_parameters_ext() { - let mocked = hashmap! { - "foo".into() => vec!["bar".into()] - }; - let request = Request::default().with_query_string_parameters(mocked.clone()); - assert_eq!(request.query_string_parameters(), mocked.into()); - } - - #[test] - fn requests_can_mock_path_parameters_ext() { - let mocked = hashmap! { - "foo".into() => vec!["bar".into()] - }; - let request = Request::default().with_path_parameters(mocked.clone()); - assert_eq!(request.path_parameters(), mocked.into()); - } - - #[test] - fn requests_can_mock_stage_variables_ext() { - let mocked = hashmap! { - "foo".into() => vec!["bar".into()] - }; - let request = Request::default().with_stage_variables(mocked.clone()); - assert_eq!(request.stage_variables(), mocked.into()); - } - - #[test] - fn requests_have_form_post_parsable_payloads() { - #[derive(Deserialize, Eq, PartialEq, Debug)] - struct Payload { - foo: String, - baz: usize, - } - let request = http::Request::builder() - .header("Content-Type", "application/x-www-form-urlencoded") - .body(Body::from("foo=bar&baz=2")) - .expect("failed to build request"); - let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); - } - - #[test] - fn requests_have_json_parsable_payloads() { - #[derive(Deserialize, Eq, PartialEq, Debug)] - struct Payload { - foo: String, - baz: usize, - } - let request = http::Request::builder() - .header("Content-Type", "application/json") - .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) - .expect("failed to build request"); - let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); - } - - #[test] - fn requests_match_form_post_content_type_with_charset() { - #[derive(Deserialize, Eq, PartialEq, Debug)] - struct Payload { - foo: String, - baz: usize, - } - let request = http::Request::builder() - .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - .body(Body::from("foo=bar&baz=2")) - .expect("failed to build request"); - let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); - } - - #[test] - fn requests_match_json_content_type_with_charset() { - #[derive(Deserialize, Eq, PartialEq, Debug)] - struct Payload { - foo: String, - baz: usize, - } - let request = http::Request::builder() - .header("Content-Type", "application/json; charset=UTF-8") - .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) - .expect("failed to build request"); - let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); - } - - #[test] - fn requests_omitting_content_types_do_not_support_parsable_payloads() { - #[derive(Deserialize, Eq, PartialEq, Debug)] - struct Payload { - foo: String, - baz: usize, - } - let request = http::Request::builder() - .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) - .expect("failed to build request"); - let payload: Option = request.payload().unwrap_or_default(); - assert_eq!(payload, None); - } - - #[test] - fn requests_can_mock_raw_http_path_ext() { - let request = Request::default().with_raw_http_path("/raw-path"); - assert_eq!("/raw-path", request.raw_http_path().as_str()); - } -} diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs new file mode 100644 index 00000000..e002d0ea --- /dev/null +++ b/lambda-http/src/ext/extensions.rs @@ -0,0 +1,627 @@ +//! Extension methods for `http::Extensions` and `http::Request` types + +use aws_lambda_events::query_map::QueryMap; +use http::request::Parts; +use lambda_runtime::Context; + +use crate::request::RequestContext; + +/// ALB/API gateway pre-parsed http query string parameters +pub(crate) struct QueryStringParameters(pub(crate) QueryMap); + +/// API gateway pre-extracted url path parameters +/// +/// These will always be empty for ALB requests +pub(crate) struct PathParameters(pub(crate) QueryMap); + +/// API gateway configured +/// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) +/// +/// These will always be empty for ALB requests +pub(crate) struct StageVariables(pub(crate) QueryMap); + +/// ALB/API gateway raw http path without any stage information +pub(crate) struct RawHttpPath(pub(crate) String); + +/// Extensions for [`lambda_http::Request`], `http::request::Parts`, and `http::Extensions` structs +/// that provide access to +/// [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) +/// and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) +/// features. +/// +/// [`lambda_http::Request`]: crate::Request +pub trait RequestExt { + /// Return the raw http path for a request without any stage information. + fn raw_http_path(&self) -> &str; + + /// Configures instance with the raw http path. + fn with_raw_http_path(self, path: S) -> Self + where + S: Into; + + /// Return pre-parsed HTTP query string parameters, parameters + /// provided after the `?` portion of a URL, + /// associated with the API gateway request. + /// + /// The yielded value represents both single and multi-valued + /// parameters alike. When multiple query string parameters with the same + /// name are expected, use `query_string_parameters().all("many")` to + /// retrieve them all. + /// + /// Having no query parameters will yield an empty `QueryMap`. + fn query_string_parameters(&self) -> QueryMap; + + /// Return pre-parsed HTTP query string parameters, parameters + /// provided after the `?` portion of a URL, + /// associated with the API gateway request. + /// + /// The yielded value represents both single and multi-valued + /// parameters alike. When multiple query string parameters with the same + /// name are expected, use + /// `query_string_parameters_ref().and_then(|params| params.all("many"))` to + /// retrieve them all. + /// + /// Having no query parameters will yield `None`. + fn query_string_parameters_ref(&self) -> Option<&QueryMap>; + + /// Configures instance with query string parameters + /// + /// This is intended for use in mock testing contexts. + fn with_query_string_parameters(self, parameters: Q) -> Self + where + Q: Into; + + /// Return pre-extracted path parameters, parameter provided in URL placeholders + /// `/foo/{bar}/baz/{qux}`, + /// associated with the API gateway request. Having no path parameters + /// will yield an empty `QueryMap`. + /// + /// These will always be empty for ALB triggered requests. + fn path_parameters(&self) -> QueryMap; + + /// Return pre-extracted path parameters, parameter provided in URL placeholders + /// `/foo/{bar}/baz/{qux}`, + /// associated with the API gateway request. Having no path parameters + /// will yield `None`. + /// + /// These will always be `None` for ALB triggered requests. + fn path_parameters_ref(&self) -> Option<&QueryMap>; + + /// Configures instance with path parameters + /// + /// This is intended for use in mock testing contexts. + fn with_path_parameters

(self, parameters: P) -> Self + where + P: Into; + + /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) + /// associated with the API gateway request. Having no stage parameters + /// will yield an empty `QueryMap`. + /// + /// These will always be empty for ALB triggered requests. + fn stage_variables(&self) -> QueryMap; + + /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) + /// associated with the API gateway request. Having no stage parameters + /// will yield `None`. + /// + /// These will always be `None` for ALB triggered requests. + fn stage_variables_ref(&self) -> Option<&QueryMap>; + + /// Configures instance with stage variables under `#[cfg(test)]` configurations + /// + /// This is intended for use in mock testing contexts. + #[cfg(test)] + fn with_stage_variables(self, variables: V) -> Self + where + V: Into; + + /// Return request context data associated with the ALB or + /// API gateway request + fn request_context(&self) -> RequestContext; + + /// Return a reference to the request context data associated with the ALB or + /// API gateway request + fn request_context_ref(&self) -> Option<&RequestContext>; + + /// Configures instance with request context + /// + /// This is intended for use in mock testing contexts. + fn with_request_context(self, context: RequestContext) -> Self; + + /// Return Lambda function context data associated with the + /// request + fn lambda_context(&self) -> Context; + + /// Return a reference to the Lambda function context data associated with the + /// request + fn lambda_context_ref(&self) -> Option<&Context>; + + /// Configures instance with lambda context + fn with_lambda_context(self, context: Context) -> Self; +} + +impl RequestExt for http::Extensions { + fn raw_http_path(&self) -> &str { + self.get::() + .map(|RawHttpPath(path)| path.as_str()) + .unwrap_or_default() + } + + fn with_raw_http_path(self, path: S) -> Self + where + S: Into, + { + let mut s = self; + s.insert(RawHttpPath(path.into())); + s + } + + fn query_string_parameters(&self) -> QueryMap { + self.query_string_parameters_ref().cloned().unwrap_or_default() + } + + fn query_string_parameters_ref(&self) -> Option<&QueryMap> { + self.get::().and_then( + |QueryStringParameters(params)| { + if params.is_empty() { + None + } else { + Some(params) + } + }, + ) + } + + fn with_query_string_parameters(self, parameters: Q) -> Self + where + Q: Into, + { + let mut s = self; + s.insert(QueryStringParameters(parameters.into())); + s + } + + fn path_parameters(&self) -> QueryMap { + self.path_parameters_ref().cloned().unwrap_or_default() + } + + fn path_parameters_ref(&self) -> Option<&QueryMap> { + self.get::().and_then( + |PathParameters(params)| { + if params.is_empty() { + None + } else { + Some(params) + } + }, + ) + } + + fn with_path_parameters

(self, parameters: P) -> Self + where + P: Into, + { + let mut s = self; + s.insert(PathParameters(parameters.into())); + s + } + + fn stage_variables(&self) -> QueryMap { + self.stage_variables_ref().cloned().unwrap_or_default() + } + + fn stage_variables_ref(&self) -> Option<&QueryMap> { + self.get::() + .and_then(|StageVariables(vars)| if vars.is_empty() { None } else { Some(vars) }) + } + + #[cfg(test)] + fn with_stage_variables(self, variables: V) -> Self + where + V: Into, + { + let mut s = self; + s.insert(StageVariables(variables.into())); + s + } + + fn request_context(&self) -> RequestContext { + self.request_context_ref() + .cloned() + .expect("Request did not contain a request context") + } + + fn request_context_ref(&self) -> Option<&RequestContext> { + self.get::() + } + + fn with_request_context(self, context: RequestContext) -> Self { + let mut s = self; + s.insert(context); + s + } + + fn lambda_context(&self) -> Context { + self.lambda_context_ref() + .cloned() + .expect("Request did not contain a lambda context") + } + + fn lambda_context_ref(&self) -> Option<&Context> { + self.get::() + } + + fn with_lambda_context(self, context: Context) -> Self { + let mut s = self; + s.insert(context); + s + } +} + +impl RequestExt for Parts { + fn raw_http_path(&self) -> &str { + self.extensions.raw_http_path() + } + + fn with_raw_http_path(self, path: S) -> Self + where + S: Into, + { + let mut s = self; + s.extensions = s.extensions.with_raw_http_path(path); + + s + } + + fn query_string_parameters(&self) -> QueryMap { + self.extensions.query_string_parameters() + } + + fn query_string_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions.query_string_parameters_ref() + } + + fn with_query_string_parameters(self, parameters: Q) -> Self + where + Q: Into, + { + let mut s = self; + s.extensions = s.extensions.with_query_string_parameters(parameters); + + s + } + + fn path_parameters(&self) -> QueryMap { + self.extensions.path_parameters() + } + + fn path_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions.path_parameters_ref() + } + + fn with_path_parameters

(self, parameters: P) -> Self + where + P: Into, + { + let mut s = self; + s.extensions = s.extensions.with_path_parameters(parameters); + + s + } + + fn stage_variables(&self) -> QueryMap { + self.extensions.stage_variables() + } + + fn stage_variables_ref(&self) -> Option<&QueryMap> { + self.extensions.stage_variables_ref() + } + + #[cfg(test)] + fn with_stage_variables(self, variables: V) -> Self + where + V: Into, + { + let mut s = self; + s.extensions = s.extensions.with_stage_variables(variables); + + s + } + + fn request_context(&self) -> RequestContext { + self.extensions.request_context() + } + + fn request_context_ref(&self) -> Option<&RequestContext> { + self.extensions.request_context_ref() + } + + fn with_request_context(self, context: RequestContext) -> Self { + let mut s = self; + s.extensions = s.extensions.with_request_context(context); + + s + } + + fn lambda_context(&self) -> Context { + self.extensions.lambda_context() + } + + fn lambda_context_ref(&self) -> Option<&Context> { + self.extensions.lambda_context_ref() + } + + fn with_lambda_context(self, context: Context) -> Self { + let mut s = self; + s.extensions = s.extensions.with_lambda_context(context); + + s + } +} + +fn map_req_ext(req: http::Request, f: F) -> http::Request +where + F: FnOnce(http::Extensions) -> http::Extensions, +{ + let (mut parts, body) = req.into_parts(); + parts.extensions = (f)(parts.extensions); + + http::Request::from_parts(parts, body) +} + +impl RequestExt for http::Request { + fn raw_http_path(&self) -> &str { + self.extensions().raw_http_path() + } + + fn with_raw_http_path(self, path: S) -> Self + where + S: Into, + { + map_req_ext(self, |ext| ext.with_raw_http_path(path)) + } + + fn query_string_parameters(&self) -> QueryMap { + self.extensions().query_string_parameters() + } + + fn query_string_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions().query_string_parameters_ref() + } + + fn with_query_string_parameters(self, parameters: Q) -> Self + where + Q: Into, + { + map_req_ext(self, |ext| ext.with_query_string_parameters(parameters)) + } + + fn path_parameters(&self) -> QueryMap { + self.extensions().path_parameters() + } + + fn path_parameters_ref(&self) -> Option<&QueryMap> { + self.extensions().path_parameters_ref() + } + + fn with_path_parameters

(self, parameters: P) -> Self + where + P: Into, + { + map_req_ext(self, |ext| ext.with_path_parameters(parameters)) + } + + fn stage_variables(&self) -> QueryMap { + self.extensions().stage_variables() + } + + fn stage_variables_ref(&self) -> Option<&QueryMap> { + self.extensions().stage_variables_ref() + } + + #[cfg(test)] + fn with_stage_variables(self, variables: V) -> Self + where + V: Into, + { + map_req_ext(self, |ext| ext.with_stage_variables(variables)) + } + + fn request_context(&self) -> RequestContext { + self.extensions().request_context() + } + + fn request_context_ref(&self) -> Option<&RequestContext> { + self.extensions().request_context_ref() + } + + fn with_request_context(self, context: RequestContext) -> Self { + map_req_ext(self, |ext| ext.with_request_context(context)) + } + + fn lambda_context(&self) -> Context { + self.extensions().lambda_context() + } + + fn lambda_context_ref(&self) -> Option<&Context> { + self.extensions().lambda_context_ref() + } + + fn with_lambda_context(self, context: Context) -> Self { + map_req_ext(self, |ext| ext.with_lambda_context(context)) + } +} + +#[cfg(test)] +mod tests { + use aws_lambda_events::query_map::QueryMap; + use http::Extensions; + + use crate::Request; + + use super::RequestExt; + + #[test] + fn extensions_can_mock_query_string_parameters_ext() { + let ext = Extensions::default(); + assert_eq!(ext.query_string_parameters_ref(), None); + assert_eq!(ext.query_string_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let ext = ext.with_query_string_parameters(mocked.clone()); + assert_eq!(ext.query_string_parameters_ref(), Some(&mocked)); + assert_eq!(ext.query_string_parameters(), mocked); + } + + #[test] + fn parts_can_mock_query_string_parameters_ext() { + let (parts, _) = Request::default().into_parts(); + assert_eq!(parts.query_string_parameters_ref(), None); + assert_eq!(parts.query_string_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let parts = parts.with_query_string_parameters(mocked.clone()); + assert_eq!(parts.query_string_parameters_ref(), Some(&mocked)); + assert_eq!(parts.query_string_parameters(), mocked); + } + + #[test] + fn requests_can_mock_query_string_parameters_ext() { + let request = Request::default(); + assert_eq!(request.query_string_parameters_ref(), None); + assert_eq!(request.query_string_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let request = request.with_query_string_parameters(mocked.clone()); + assert_eq!(request.query_string_parameters_ref(), Some(&mocked)); + assert_eq!(request.query_string_parameters(), mocked); + } + + #[test] + fn extensions_can_mock_path_parameters_ext() { + let ext = Extensions::default(); + assert_eq!(ext.path_parameters_ref(), None); + assert_eq!(ext.path_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let ext = ext.with_path_parameters(mocked.clone()); + assert_eq!(ext.path_parameters_ref(), Some(&mocked)); + assert_eq!(ext.path_parameters(), mocked); + } + + #[test] + fn parts_can_mock_path_parameters_ext() { + let (parts, _) = Request::default().into_parts(); + assert_eq!(parts.path_parameters_ref(), None); + assert_eq!(parts.path_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let parts = parts.with_path_parameters(mocked.clone()); + assert_eq!(parts.path_parameters_ref(), Some(&mocked)); + assert_eq!(parts.path_parameters(), mocked); + } + + #[test] + fn requests_can_mock_path_parameters_ext() { + let request = Request::default(); + assert_eq!(request.path_parameters_ref(), None); + assert_eq!(request.path_parameters(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let request = request.with_path_parameters(mocked.clone()); + assert_eq!(request.path_parameters_ref(), Some(&mocked)); + assert_eq!(request.path_parameters(), mocked); + } + + #[test] + fn extensions_can_mock_stage_variables_ext() { + let ext = Extensions::default(); + assert_eq!(ext.stage_variables_ref(), None); + assert_eq!(ext.stage_variables(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let ext = ext.with_stage_variables(mocked.clone()); + assert_eq!(ext.stage_variables_ref(), Some(&mocked)); + assert_eq!(ext.stage_variables(), mocked); + } + + #[test] + fn parts_can_mock_stage_variables_ext() { + let (parts, _) = Request::default().into_parts(); + assert_eq!(parts.stage_variables_ref(), None); + assert_eq!(parts.stage_variables(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let parts = parts.with_stage_variables(mocked.clone()); + assert_eq!(parts.stage_variables_ref(), Some(&mocked)); + assert_eq!(parts.stage_variables(), mocked); + } + + #[test] + fn requests_can_mock_stage_variables_ext() { + let request = Request::default(); + assert_eq!(request.stage_variables_ref(), None); + assert_eq!(request.stage_variables(), QueryMap::default()); + + let mocked: QueryMap = hashmap! { + "foo".into() => vec!["bar".into()] + } + .into(); + + let request = request.with_stage_variables(mocked.clone()); + assert_eq!(request.stage_variables_ref(), Some(&mocked)); + assert_eq!(request.stage_variables(), mocked); + } + + #[test] + fn extensions_can_mock_raw_http_path_ext() { + let ext = Extensions::default().with_raw_http_path("/raw-path"); + assert_eq!("/raw-path", ext.raw_http_path()); + } + + #[test] + fn parts_can_mock_raw_http_path_ext() { + let (parts, _) = Request::default().into_parts(); + let parts = parts.with_raw_http_path("/raw-path"); + assert_eq!("/raw-path", parts.raw_http_path()); + } + + #[test] + fn requests_can_mock_raw_http_path_ext() { + let request = Request::default().with_raw_http_path("/raw-path"); + assert_eq!("/raw-path", request.raw_http_path()); + } +} diff --git a/lambda-http/src/ext/mod.rs b/lambda-http/src/ext/mod.rs new file mode 100644 index 00000000..81c64daa --- /dev/null +++ b/lambda-http/src/ext/mod.rs @@ -0,0 +1,7 @@ +//! Extension methods for `Request` types + +pub mod extensions; +pub mod request; + +pub use extensions::RequestExt; +pub use request::{PayloadError, RequestPayloadExt}; diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs new file mode 100644 index 00000000..11c49012 --- /dev/null +++ b/lambda-http/src/ext/request.rs @@ -0,0 +1,220 @@ +//! Extension methods for `Request` types + +use std::{error::Error, fmt}; + +use serde::{ + de::{value::Error as SerdeError, DeserializeOwned}, + Deserialize, +}; + +use crate::Body; + +/// Request payload deserialization errors +/// +/// Returned by [`RequestPayloadExt::payload()`] +#[derive(Debug)] +pub enum PayloadError { + /// Returned when `application/json` bodies fail to deserialize a payload + Json(serde_json::Error), + /// Returned when `application/x-www-form-urlencoded` bodies fail to deserialize a payload + WwwFormUrlEncoded(SerdeError), +} + +impl fmt::Display for PayloadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PayloadError::Json(json) => writeln!(f, "failed to parse payload from application/json {json}"), + PayloadError::WwwFormUrlEncoded(form) => writeln!( + f, + "failed to parse payload from application/x-www-form-urlencoded {form}" + ), + } + } +} + +impl Error for PayloadError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + PayloadError::Json(json) => Some(json), + PayloadError::WwwFormUrlEncoded(form) => Some(form), + } + } +} + +/// Extensions for `lambda_http::Request` structs. +/// +/// # Examples +/// +/// A request's body can be deserialized if its correctly encoded as per +/// the request's `Content-Type` header. The two supported content types are +/// `application/x-www-form-urlencoded` and `application/json`. +/// +/// The following handler will work an http request body of `x=1&y=2` +/// as well as `{"x":1, "y":2}` respectively. +/// +/// ```rust,no_run +/// use lambda_http::{ +/// service_fn, Body, Context, Error, IntoResponse, Request, RequestPayloadExt, Response, +/// }; +/// use serde::Deserialize; +/// +/// #[derive(Debug, Default, Deserialize)] +/// struct Args { +/// #[serde(default)] +/// x: usize, +/// #[serde(default)] +/// y: usize +/// } +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// lambda_http::run(service_fn(add)).await?; +/// Ok(()) +/// } +/// +/// async fn add( +/// request: Request +/// ) -> Result, Error> { +/// let args: Args = request.payload() +/// .unwrap_or_else(|_parse_err| None) +/// .unwrap_or_default(); +/// Ok( +/// Response::new( +/// format!( +/// "{} + {} = {}", +/// args.x, +/// args.y, +/// args.x + args.y +/// ).into() +/// ) +/// ) +/// } +/// ``` +pub trait RequestPayloadExt { + /// Return the result of a payload parsed into a type that implements [`serde::Deserialize`] + /// + /// Currently only `application/x-www-form-urlencoded` + /// and `application/json` flavors of content type + /// are supported + /// + /// A [`PayloadError`] will be returned for undeserializable + /// payloads. If no body is provided, `Ok(None)` will be returned. + fn payload(&self) -> Result, PayloadError> + where + D: DeserializeOwned; +} + +impl RequestPayloadExt for http::Request { + fn payload(&self) -> Result, PayloadError> + where + for<'de> D: Deserialize<'de>, + { + self.headers() + .get(http::header::CONTENT_TYPE) + .map(|ct| match ct.to_str() { + Ok(content_type) => { + if content_type.starts_with("application/x-www-form-urlencoded") { + return serde_urlencoded::from_bytes::(self.body().as_ref()) + .map_err(PayloadError::WwwFormUrlEncoded) + .map(Some); + } else if content_type.starts_with("application/json") { + return serde_json::from_slice::(self.body().as_ref()) + .map_err(PayloadError::Json) + .map(Some); + } + + Ok(None) + } + _ => Ok(None), + }) + .unwrap_or_else(|| Ok(None)) + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use super::RequestPayloadExt; + + use crate::Body; + + #[derive(Deserialize, Eq, PartialEq, Debug)] + struct Payload { + foo: String, + baz: usize, + } + + #[test] + fn requests_have_form_post_parsable_payloads() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded") + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + + #[test] + fn requests_have_json_parsable_payloads() { + let request = http::Request::builder() + .header("Content-Type", "application/json") + .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + + #[test] + fn requests_match_form_post_content_type_with_charset() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + + #[test] + fn requests_match_json_content_type_with_charset() { + let request = http::Request::builder() + .header("Content-Type", "application/json; charset=UTF-8") + .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + + #[test] + fn requests_omitting_content_types_do_not_support_parsable_payloads() { + let request = http::Request::builder() + .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap_or_default(); + assert_eq!(payload, None); + } +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index b4d9c5bd..37c167a0 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -32,11 +32,11 @@ //! //! ## Leveraging trigger provided data //! -//! You can also access information provided directly from the underlying trigger events, like query string parameters, -//! or Lambda function context, with the [`RequestExt`](trait.RequestExt.html) trait. +//! You can also access information provided directly from the underlying trigger events, +//! like query string parameters, or Lambda function context, with the [`RequestExt`] trait. //! //! ```rust,no_run -//! use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; +//! use lambda_http::{service_fn, Error, RequestExt, IntoResponse, Request}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { @@ -47,13 +47,13 @@ //! async fn hello( //! request: Request //! ) -> Result { -//! let _context = request.lambda_context(); +//! let _context = request.lambda_context_ref(); //! //! Ok(format!( //! "hello {}", //! request -//! .query_string_parameters() -//! .first("name") +//! .query_string_parameters_ref() +//! .and_then(|params| params.first("name")) //! .unwrap_or_else(|| "stranger") //! )) //! } @@ -73,7 +73,10 @@ use response::ResponseFuture; pub mod ext; pub mod request; mod response; -pub use crate::{ext::RequestExt, response::IntoResponse}; +pub use crate::{ + ext::{RequestExt, RequestPayloadExt}, + response::IntoResponse, +}; use crate::{ request::{LambdaRequest, RequestOrigin}, response::LambdaResponse, diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index c31398c5..2711a343 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -1,11 +1,14 @@ //! ALB and API Gateway request adaptations //! -//! Typically these are exposed via the `request_context` -//! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) +//! Typically these are exposed via the [`request_context()`] or [`request_context_ref()`] +//! request extension methods provided by the [`RequestExt`] trait. //! +//! [`request_context()`]: crate::RequestExt::request_context() +//! [`request_context_ref()`]: crate::RequestExt::request_context_ref() +//! [`RequestExt`]: crate::RequestExt #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] -use crate::ext::{PathParameters, StageVariables}; -use crate::ext::{QueryStringParameters, RawHttpPath}; +use crate::ext::extensions::{PathParameters, StageVariables}; +use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; #[cfg(feature = "apigw_rest")] @@ -447,7 +450,7 @@ fn build_request_uri( #[cfg(test)] mod tests { use super::*; - use crate::RequestExt; + use crate::ext::RequestExt; use std::fs::File; #[test] @@ -474,9 +477,9 @@ mod tests { assert_eq!(req.uri(), "https://xxx.execute-api.us-east-1.amazonaws.com/"); // Ensure this is an APIGWv2 request - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::ApiGatewayV2(_)), + matches!(req_context, &RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -503,9 +506,9 @@ mod tests { assert_eq!(cookie_header, Ok("cookie1=value1;cookie2=value2")); // Ensure this is an APIGWv2 request - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::ApiGatewayV2(_)), + matches!(req_context, &RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -529,9 +532,9 @@ mod tests { ); // Ensure this is an APIGW request - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::ApiGatewayV1(_)), + matches!(req_context, &RequestContext::ApiGatewayV1(_)), "expected ApiGateway context, got {req_context:?}" ); } @@ -562,9 +565,9 @@ mod tests { assert_eq!(cookie_header, Some("test=hi".to_string())); // Ensure this is an APIGWv2 request (Lambda Function URL requests confirm to API GW v2 Request format) - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::ApiGatewayV2(_)), + matches!(req_context, &RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {req_context:?}" ); } @@ -587,9 +590,9 @@ mod tests { ); // Ensure this is an ALB request - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::Alb(_)), + matches!(req_context, &RequestContext::Alb(_)), "expected Alb context, got {req_context:?}" ); } @@ -612,9 +615,9 @@ mod tests { ); // Ensure this is an ALB request - let req_context = req.request_context(); + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); assert!( - matches!(req_context, RequestContext::Alb(_)), + matches!(req_context, &RequestContext::Alb(_)), "expected Alb context, got {req_context:?}" ); } @@ -631,11 +634,16 @@ mod tests { ); let request = result.expect("failed to parse request"); - assert!(!request.query_string_parameters().is_empty()); + assert!(!request + .query_string_parameters_ref() + .expect("Request is missing query parameters") + .is_empty()); - // test RequestExt#query_string_parameters does the right thing + // test RequestExt#query_string_parameters_ref does the right thing assert_eq!( - request.query_string_parameters().all("multivalueName"), + request + .query_string_parameters_ref() + .and_then(|params| params.all("multivalueName")), Some(vec!["you", "me"]) ); } @@ -651,11 +659,16 @@ mod tests { "event is was not parsed as expected {result:?} given {input}" ); let request = result.expect("failed to parse request"); - assert!(!request.query_string_parameters().is_empty()); + assert!(!request + .query_string_parameters_ref() + .expect("Request is missing query parameters") + .is_empty()); - // test RequestExt#query_string_parameters does the right thing + // test RequestExt#query_string_parameters_ref does the right thing assert_eq!( - request.query_string_parameters().all("myKey"), + request + .query_string_parameters_ref() + .and_then(|params| params.all("myKey")), Some(vec!["val1", "val2"]) ); } @@ -671,11 +684,16 @@ mod tests { "event is was not parsed as expected {result:?} given {input}" ); let request = result.expect("failed to parse request"); - assert!(!request.query_string_parameters().is_empty()); + assert!(!request + .query_string_parameters_ref() + .expect("Request is missing query parameters") + .is_empty()); - // test RequestExt#query_string_parameters does the right thing + // test RequestExt#query_string_parameters_ref does the right thing assert_eq!( - request.query_string_parameters().all("myKey"), + request + .query_string_parameters_ref() + .and_then(|params| params.all("myKey")), Some(vec!["?showAll=true", "?showAll=false"]) ); } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 150002be..9a27d915 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,6 +1,6 @@ -use crate::request::LambdaRequest; use crate::tower::ServiceBuilder; -use crate::{Request, RequestExt}; +use crate::Request; +use crate::{request::LambdaRequest, RequestExt}; pub use aws_lambda_events::encodings::Body as LambdaEventBody; use bytes::Bytes; pub use http::{self, Response}; diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index 4170d29f..cd252280 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -1,4 +1,4 @@ -use lambda_http::{service_fn, Body, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{ext::RequestExt, service_fn, Body, Error, IntoResponse, Request, Response}; use tracing::info; async fn handler(event: Request) -> Result { diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs index fffe0db0..765b0d66 100644 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -4,7 +4,7 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use lambda_http::{Body, Error, Request, RequestExt, Response, Service}; +use lambda_http::{ext::RequestExt, Body, Error, Request, Response, Service}; use tracing::info; struct MyHandler { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 31f4c8a2..87d6ded5 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -91,7 +91,7 @@ pub struct CognitoIdentity { /// The Lambda function execution context. The values in this struct /// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) -/// and the headers returned by the poll request to the Runtime APIs. +/// and [the headers returned by the poll request to the Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next). #[non_exhaustive] #[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] pub struct Context { From 2654433a95d5a75fe4cfa6d12401f93801382ac9 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 10 Apr 2023 20:47:27 -0700 Subject: [PATCH 174/394] Remove extension_id from LambdaEvent (#633) This id is auto-generated when an extension is registered. The runtime uses it to interact with the extension via HTTP, but it doesn't have any meaning outside of that because it's always aleatory. Signed-off-by: David Calavera --- lambda-extension/src/events.rs | 9 ++------- lambda-extension/src/extension.rs | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lambda-extension/src/events.rs b/lambda-extension/src/events.rs index 87fd62a4..c61fc9f0 100644 --- a/lambda-extension/src/events.rs +++ b/lambda-extension/src/events.rs @@ -55,17 +55,12 @@ impl NextEvent { /// Wrapper with information about the next /// event that the Lambda Runtime is going to process pub struct LambdaEvent { - /// ID assigned to this extension by the Lambda Runtime - pub extension_id: String, /// Next incoming event pub next: NextEvent, } impl LambdaEvent { - pub(crate) fn new(ex_id: &str, next: NextEvent) -> LambdaEvent { - LambdaEvent { - extension_id: ex_id.into(), - next, - } + pub(crate) fn new(next: NextEvent) -> LambdaEvent { + LambdaEvent { next } } } diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 776e2fdf..52fe5c31 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -315,7 +315,7 @@ where let event: NextEvent = serde_json::from_slice(&body)?; let is_invoke = event.is_invoke(); - let event = LambdaEvent::new(extension_id, event); + let event = LambdaEvent::new(event); let ep = match ep.ready().await { Ok(ep) => ep, From 4c4a2b0a123beb4bd1e23dd4a15b43dd39a8407e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 10 Apr 2023 20:47:42 -0700 Subject: [PATCH 175/394] Add debug assertions check to streaming (#635) This is used by Cargo Lambda's local server to control req/response. Signed-off-by: David Calavera --- lambda-runtime/src/streaming.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs index 85af784e..15cd210f 100644 --- a/lambda-runtime/src/streaming.rs +++ b/lambda-runtime/src/streaming.rs @@ -108,6 +108,14 @@ where let event = next_event_response?; let (parts, body) = event.into_parts(); + #[cfg(debug_assertions)] + if parts.status == http::StatusCode::NO_CONTENT { + // Ignore the event if the status code is 204. + // This is a way to keep the runtime alive when + // there are no events pending to be processed. + continue; + } + let ctx: Context = Context::try_from(parts.headers)?; let ctx: Context = ctx.with_config(config); let request_id = &ctx.request_id.clone(); @@ -128,6 +136,12 @@ where let body = hyper::body::to_bytes(body).await?; trace!("incoming request payload - {}", std::str::from_utf8(&body)?); + #[cfg(debug_assertions)] + if parts.status.is_server_error() { + error!("Lambda Runtime server returned an unexpected error"); + return Err(parts.status.to_string().into()); + } + let body = match serde_json::from_slice(&body) { Ok(body) => body, Err(err) => { From 393d6447bea0502e1f939d197f4facc228e6e007 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 11 Apr 2023 15:00:01 +0000 Subject: [PATCH 176/394] Release v0.8.0 (#636) * Release lambda-runtime-api-client v0.8.0 * Release lambda-runtime v0.8.0 * Release lambda-http v0.8.0 * Release lambda-extension v0.8.1 --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 4 ++-- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 1132c6de..fcae3cc7 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.8.0" +version = "0.8.1" edition = "2021" authors = [ "David Calavera ", @@ -19,7 +19,7 @@ bytes = "1.0" chrono = { version = "0.4", features = ["serde"] } http = "0.2" hyper = { version = "0.14.20", features = ["http1", "client", "server", "stream", "runtime"] } -lambda_runtime_api_client = { version = "0.7", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index aacf739b..687e1531 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.7.3" +version = "0.8.0" authors = [ "David Calavera ", "Harold Sun ", @@ -29,7 +29,7 @@ futures = "0.3" http = "0.2" http-body = "0.4" hyper = "0.14" -lambda_runtime = { path = "../lambda-runtime", version = "0.7" } +lambda_runtime = { path = "../lambda-runtime", version = "0.8" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index dab52a48..ae3e0f70 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 37679fbb..788b3c89 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.7.3" +version = "0.8.0" authors = [ "David Calavera ", "Harold Sun ", @@ -40,4 +40,4 @@ async-stream = "0.3" tracing = { version = "0.1.37", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.7", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } From 2360a05515dda21c3716eaa31215b6ef84d066bc Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 14 Apr 2023 08:20:58 -0700 Subject: [PATCH 177/394] Provide context when deserialization fails. (#640) * Provide context when deserialization fails. Use serde_path_to_error to provide a little bit more information about the serde failure. Signed-off-by: David Calavera * Include the error path in the message when the problem is not the root element "." Signed-off-by: David Calavera * Cleanup formatting Signed-off-by: David Calavera * Fix typo Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-runtime/Cargo.toml | 1 + lambda-runtime/src/deserializer.rs | 43 ++++++++++++++++++++++++++++++ lambda-runtime/src/lib.rs | 8 +++--- lambda-runtime/src/streaming.rs | 11 ++++---- 4 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 lambda-runtime/src/deserializer.rs diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 788b3c89..96506022 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -41,3 +41,4 @@ tracing = { version = "0.1.37", features = ["log"] } tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +serde_path_to_error = "0.1.11" diff --git a/lambda-runtime/src/deserializer.rs b/lambda-runtime/src/deserializer.rs new file mode 100644 index 00000000..1841c050 --- /dev/null +++ b/lambda-runtime/src/deserializer.rs @@ -0,0 +1,43 @@ +use std::{error::Error, fmt}; + +use serde::Deserialize; + +use crate::{Context, LambdaEvent}; + +const ERROR_CONTEXT: &str = "failed to deserialize the incoming data into the function's payload type"; + +/// Event payload deserialization error. +/// Returned when the data sent to the function cannot be deserialized +/// into the type that the function receives. +#[derive(Debug)] +pub(crate) struct DeserializeError { + inner: serde_path_to_error::Error, +} + +impl fmt::Display for DeserializeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let path = self.inner.path().to_string(); + if path == "." { + writeln!(f, "{ERROR_CONTEXT}: {}", self.inner) + } else { + writeln!(f, "{ERROR_CONTEXT}: [{path}] {}", self.inner) + } + } +} + +impl Error for DeserializeError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.inner) + } +} + +/// Deserialize the data sent to the function into the type that the function receives. +pub(crate) fn deserialize(body: &[u8], context: Context) -> Result, DeserializeError> +where + T: for<'de> Deserialize<'de>, +{ + let jd = &mut serde_json::Deserializer::from_slice(body); + serde_path_to_error::deserialize(jd) + .map(|payload| LambdaEvent::new(payload, context)) + .map_err(|inner| DeserializeError { inner }) +} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 31c9297c..86a0848b 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -28,6 +28,7 @@ pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; use tracing::{error, trace, Instrument}; +mod deserializer; mod requests; #[cfg(test)] mod simulated; @@ -149,8 +150,8 @@ where return Err(parts.status.to_string().into()); } - let body = match serde_json::from_slice(&body) { - Ok(body) => body, + let lambda_event = match deserializer::deserialize(&body, ctx) { + Ok(lambda_event) => lambda_event, Err(err) => { let req = build_event_error_request(request_id, err)?; client.call(req).await.expect("Unable to send response to Runtime APIs"); @@ -161,8 +162,7 @@ where let req = match handler.ready().await { Ok(handler) => { // Catches panics outside of a `Future` - let task = - panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); let task = match task { // Catches panics inside of the `Future` diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs index 15cd210f..b9cf978b 100644 --- a/lambda-runtime/src/streaming.rs +++ b/lambda-runtime/src/streaming.rs @@ -1,6 +1,6 @@ use crate::{ - build_event_error_request, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, IntoRequest, - LambdaEvent, Runtime, + build_event_error_request, deserializer, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, + IntoRequest, LambdaEvent, Runtime, }; use bytes::Bytes; use futures::FutureExt; @@ -142,8 +142,8 @@ where return Err(parts.status.to_string().into()); } - let body = match serde_json::from_slice(&body) { - Ok(body) => body, + let lambda_event = match deserializer::deserialize(&body, ctx) { + Ok(lambda_event) => lambda_event, Err(err) => { let req = build_event_error_request(request_id, err)?; client.call(req).await.expect("Unable to send response to Runtime APIs"); @@ -154,8 +154,7 @@ where let req = match handler.ready().await { Ok(handler) => { // Catches panics outside of a `Future` - let task = - panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx)))); + let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); let task = match task { // Catches panics inside of the `Future` From c5d6f1ecf30cadf19aee2f2d5ef3f911d1b8fd00 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 14 Apr 2023 08:21:11 -0700 Subject: [PATCH 178/394] Update aws_lambda_events (#641) There are some performance improvements in deserializing payloads. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 687e1531..73d05675 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -39,7 +39,7 @@ url = "2.2" percent-encoding = "2.2" [dependencies.aws_lambda_events] -version = "^0.7.2" +version = "0.8.3" default-features = false features = ["alb", "apigw"] From 3fce312eb77f7ec1722e1c4605b11f770e286ed9 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 14 Apr 2023 13:31:46 -0700 Subject: [PATCH 179/394] Update README.md (#642) Add wording to follow the legal guidelines about non-official packages. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 781ef364..8fdd232c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. +The Rust runtime client is an experimental package. It is subject to change and intended only for evaluation purposes. + ## Getting started The easiest way to start writing Lambda functions with Rust is by using [Cargo Lambda](https://www.cargo-lambda.info/), a related project. Cargo Lambda is a Cargo plugin, or subcommand, that provides several commands to help you in your journey with Rust on AWS Lambda. From e18d3662119b66c464e94a17d81bdbd1d40dfba0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 2 May 2023 08:50:22 -0700 Subject: [PATCH 180/394] Cleanup runtime config (#646) - Move Config data into the Runtime struct - Make runtime methods private - Implement IntoRequest for EventCompletionStreamingRequest Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 18 +++++++++--------- lambda-runtime/src/streaming.rs | 11 +++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 86a0848b..e3ffd49d 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -87,6 +87,7 @@ where struct Runtime = HttpConnector> { client: Client, + config: Config, } impl Runtime @@ -96,11 +97,10 @@ where C::Error: Into>, C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { - pub async fn run( + async fn run( &self, incoming: impl Stream, Error>> + Send, mut handler: F, - config: &Config, ) -> Result<(), Error> where F: Service>, @@ -125,7 +125,7 @@ where } let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(config); + let ctx: Context = ctx.with_config(&self.config); let request_id = &ctx.request_id.clone(); let request_span = match &ctx.xray_trace_id { @@ -254,11 +254,11 @@ where trace!("Loading config from env"); let config = Config::from_env()?; let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client }; + let runtime = Runtime { client, config }; let client = &runtime.client; let incoming = incoming(client); - runtime.run(incoming, handler, &config).await + runtime.run(incoming, handler).await } fn type_name_of_val(_: T) -> &'static str { @@ -522,10 +522,10 @@ mod endpoint_tests { } let config = crate::Config::from_env().expect("Failed to read env vars"); - let runtime = Runtime { client }; + let runtime = Runtime { client, config }; let client = &runtime.client; let incoming = incoming(client).take(1); - runtime.run(incoming, f, &config).await?; + runtime.run(incoming, f).await?; // shutdown server tx.send(()).expect("Receiver has been dropped"); @@ -565,10 +565,10 @@ mod endpoint_tests { log_group: "test_log".to_string(), }; - let runtime = Runtime { client }; + let runtime = Runtime { client, config }; let client = &runtime.client; let incoming = incoming(client).take(1); - runtime.run(incoming, f, &config).await?; + runtime.run(incoming, f).await?; match server.await { Ok(_) => Ok(()), diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs index b9cf978b..e541f3d6 100644 --- a/lambda-runtime/src/streaming.rs +++ b/lambda-runtime/src/streaming.rs @@ -72,11 +72,11 @@ where trace!("Loading config from env"); let config = Config::from_env()?; let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client }; + let runtime = Runtime { client, config }; let client = &runtime.client; let incoming = incoming(client); - runtime.run_with_streaming_response(incoming, handler, &config).await + runtime.run_with_streaming_response(incoming, handler).await } impl Runtime @@ -86,11 +86,10 @@ where C::Error: Into>, C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { - pub async fn run_with_streaming_response( + async fn run_with_streaming_response( &self, incoming: impl Stream, Error>> + Send, mut handler: F, - config: &Config, ) -> Result<(), Error> where F: Service>, @@ -117,7 +116,7 @@ where } let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(config); + let ctx: Context = ctx.with_config(&self.config); let request_id = &ctx.request_id.clone(); let request_span = match &ctx.xray_trace_id { @@ -204,7 +203,7 @@ pub(crate) struct EventCompletionStreamingRequest<'a, B> { pub(crate) body: Response, } -impl<'a, B> EventCompletionStreamingRequest<'a, B> +impl<'a, B> IntoRequest for EventCompletionStreamingRequest<'a, B> where B: HttpBody + Unpin + Send + 'static, B::Data: Into + Send, From 494dd187c9e83d0e4bd7f9e544a6b350e57e472b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 5 May 2023 08:22:38 -0700 Subject: [PATCH 181/394] Bring aws_lambda_events as subpackage (#647) Signed-off-by: David Calavera --- .github/actions/rust-build/action.yml | 26 + .github/workflows/build-events.yml | 28 + .github/workflows/build-extension.yml | 39 + .github/workflows/build-runtime.yml | 45 + .github/workflows/build.yml | 93 -- .github/workflows/check-examples.yml | 16 + .github/workflows/format.yml | 55 ++ Cargo.toml | 3 +- README.md | 1 + .../Cargo.toml | 4 +- examples/basic-s3-thumbnail/Cargo.toml | 2 +- examples/basic-sqs/Cargo.toml | 2 +- lambda-events/Cargo.toml | 117 +++ lambda-events/README.md | 34 + .../src/custom_serde/codebuild_time.rs | 105 ++ .../src/custom_serde/float_unix_epoch.rs | 109 +++ lambda-events/src/custom_serde/headers.rs | 189 ++++ lambda-events/src/custom_serde/http_method.rs | 103 ++ lambda-events/src/custom_serde/mod.rs | 455 +++++++++ lambda-events/src/encodings.rs | 449 +++++++++ lambda-events/src/event/activemq/mod.rs | 66 ++ lambda-events/src/event/alb/mod.rs | 98 ++ lambda-events/src/event/apigw/mod.rs | 911 ++++++++++++++++++ lambda-events/src/event/appsync/mod.rs | 165 ++++ lambda-events/src/event/autoscaling/mod.rs | 111 +++ lambda-events/src/event/chime_bot/mod.rs | 52 + lambda-events/src/event/clientvpn/mod.rs | 61 ++ .../src/event/cloudwatch_events/cloudtrail.rs | 49 + .../src/event/cloudwatch_events/codedeploy.rs | 35 + .../event/cloudwatch_events/codepipeline.rs | 47 + .../src/event/cloudwatch_events/ec2.rs | 10 + .../src/event/cloudwatch_events/emr.rs | 50 + .../src/event/cloudwatch_events/gamelift.rs | 119 +++ .../src/event/cloudwatch_events/glue.rs | 89 ++ .../src/event/cloudwatch_events/health.rs | 31 + .../src/event/cloudwatch_events/kms.rs | 9 + .../src/event/cloudwatch_events/macie.rs | 223 +++++ .../src/event/cloudwatch_events/mod.rs | 50 + .../src/event/cloudwatch_events/opsworks.rs | 55 ++ .../src/event/cloudwatch_events/signin.rs | 50 + .../src/event/cloudwatch_events/sms.rs | 15 + .../src/event/cloudwatch_events/ssm.rs | 240 +++++ .../src/event/cloudwatch_events/tag.rs | 16 + .../event/cloudwatch_events/trustedadvisor.rs | 17 + .../src/event/cloudwatch_logs/mod.rs | 157 +++ lambda-events/src/event/code_commit/mod.rs | 82 ++ lambda-events/src/event/codebuild/mod.rs | 237 +++++ lambda-events/src/event/codedeploy/mod.rs | 92 ++ .../src/event/codepipeline_cloudwatch/mod.rs | 114 +++ .../src/event/codepipeline_job/mod.rs | 129 +++ lambda-events/src/event/cognito/mod.rs | 612 ++++++++++++ lambda-events/src/event/config/mod.rs | 52 + lambda-events/src/event/connect/mod.rs | 116 +++ .../src/event/dynamodb/attributes.rs | 191 ++++ lambda-events/src/event/dynamodb/mod.rs | 280 ++++++ lambda-events/src/event/ecr_scan/mod.rs | 73 ++ lambda-events/src/event/firehose/mod.rs | 87 ++ lambda-events/src/event/iam/mod.rs | 23 + lambda-events/src/event/iot/mod.rs | 99 ++ lambda-events/src/event/iot_1_click/mod.rs | 72 ++ lambda-events/src/event/iot_button/mod.rs | 27 + lambda-events/src/event/iot_deprecated/mod.rs | 35 + lambda-events/src/event/kafka/mod.rs | 49 + lambda-events/src/event/kinesis/analytics.rs | 35 + lambda-events/src/event/kinesis/event.rs | 88 ++ lambda-events/src/event/kinesis/mod.rs | 3 + .../src/event/lambda_function_urls/mod.rs | 104 ++ lambda-events/src/event/lex/mod.rs | 130 +++ lambda-events/src/event/mod.rs | 140 +++ lambda-events/src/event/rabbitmq/mod.rs | 76 ++ lambda-events/src/event/s3/batch_job.rs | 58 ++ lambda-events/src/event/s3/event.rs | 114 +++ lambda-events/src/event/s3/mod.rs | 5 + lambda-events/src/event/s3/object_lambda.rs | 171 ++++ lambda-events/src/event/ses/mod.rs | 155 +++ lambda-events/src/event/sns/mod.rs | 248 +++++ lambda-events/src/event/sqs/mod.rs | 161 ++++ lambda-events/src/event/streams/mod.rs | 44 + .../src/fixtures/example-activemq-event.json | 25 + ...lb-lambda-target-request-headers-only.json | 26 + ...bda-target-request-multivalue-headers.json | 49 + .../example-alb-lambda-target-response.json | 15 + .../example-apigw-console-test-request.json | 45 + ...pigw-custom-auth-request-type-request.json | 88 ++ .../example-apigw-custom-auth-request.json | 5 + .../example-apigw-custom-auth-response.json | 19 + .../src/fixtures/example-apigw-request.json | 96 ++ .../src/fixtures/example-apigw-response.json | 42 + ...example-apigw-restapi-openapi-request.json | 97 ++ ...apigw-v2-custom-authorizer-v1-request.json | 61 ++ ...authorizer-v2-request-without-cookies.json | 49 + ...apigw-v2-custom-authorizer-v2-request.json | 51 + ...2-custom-authorizer-websocket-request.json | 51 + .../example-apigw-v2-request-iam.json | 73 ++ ...ample-apigw-v2-request-jwt-authorizer.json | 70 ++ ...le-apigw-v2-request-lambda-authorizer.json | 63 ++ ...xample-apigw-v2-request-no-authorizer.json | 48 + ...pigw-websocket-request-without-method.json | 58 ++ .../example-apigw-websocket-request.json | 108 +++ .../fixtures/example-appsync-batchinvoke.json | 28 + .../example-appsync-identity-cognito.json | 19 + .../example-appsync-identity-iam.json | 11 + .../src/fixtures/example-appsync-invoke.json | 14 + .../example-appsync-lambda-auth-request.json | 12 + .../example-appsync-lambda-auth-response.json | 8 + ...e-autoscaling-event-launch-successful.json | 29 + ...autoscaling-event-launch-unsuccessful.json | 28 + ...le-autoscaling-event-lifecycle-action.json | 21 + ...le-autoscaling-event-terminate-action.json | 19 + ...utoscaling-event-terminate-successful.json | 29 + ...oscaling-event-terminate-unsuccessful.json | 28 + ...e-clientvpn-connectionhandler-request.json | 12 + ...ch-alarm-sns-payload-multiple-metrics.json | 40 + ...watch-alarm-sns-payload-single-metric.json | 22 + .../example-cloudwatch_logs-event.json | 6 + .../fixtures/example-code_commit-event.json | 27 + .../example-codebuild-phase-change.json | 138 +++ .../example-codebuild-state-change.json | 142 +++ .../example-codedeploy-deployment-event.json | 22 + .../example-codedeploy-instance-event.json | 23 + ...e-action-execution-stage-change-event.json | 27 + ...pipeline-execution-stage-change-event.json | 18 + ...pipeline-execution-state-change-event.json | 18 + .../example-codepipeline_job-event.json | 34 + ...event-userpools-create-auth-challenge.json | 40 + ...cognito-event-userpools-custommessage.json | 28 + ...th-challenge-optional-response-fields.json | 34 + ...event-userpools-define-auth-challenge.json | 36 + ...e-cognito-event-userpools-migrateuser.json | 34 + ...to-event-userpools-postauthentication.json | 22 + ...nito-event-userpools-postconfirmation.json | 21 + ...ito-event-userpools-preauthentication.json | 21 + ...ple-cognito-event-userpools-presignup.json | 29 + ...-event-userpools-pretokengen-incoming.json | 29 + ...e-cognito-event-userpools-pretokengen.json | 40 + ...uth-challenge-optional-answer-correct.json | 29 + ...event-userpools-verify-auth-challenge.json | 30 + .../src/fixtures/example-cognito-event.json | 17 + .../src/fixtures/example-config-event.json | 13 + .../example-connect-event-without-queue.json | 29 + .../src/fixtures/example-connect-event.json | 33 + ...odb-event-record-with-optional-fields.json | 26 + .../src/fixtures/example-dynamodb-event.json | 153 +++ .../example-ecr-image-scan-event.json | 24 + .../src/fixtures/example-firehose-event.json | 33 + .../example-iot-custom-auth-request.json | 25 + .../example-iot-custom-auth-response.json | 19 + .../fixtures/example-iot_1_click-event.json | 30 + .../fixtures/example-iot_button-event.json | 5 + .../src/fixtures/example-kafka-event.json | 24 + .../src/fixtures/example-kinesis-event.json | 36 + .../example-kinesis-firehose-event.json | 33 + .../example-kinesis-firehose-response.json | 32 + .../src/fixtures/example-lex-event.json | 44 + .../src/fixtures/example-lex-response.json | 40 + .../src/fixtures/example-rabbitmq-event.json | 52 + .../example-s3-event-with-decoded.json | 41 + .../src/fixtures/example-s3-event.json | 40 + ...-lambda-event-get-object-assumed-role.json | 42 + ...s3-object-lambda-event-get-object-iam.json | 29 + ...3-object-lambda-event-head-object-iam.json | 28 + ...-object-lambda-event-list-objects-iam.json | 28 + ...ject-lambda-event-list-objects-v2-iam.json | 28 + .../src/fixtures/example-ses-event.json | 101 ++ .../fixtures/example-ses-lambda-event.json | 101 ++ .../src/fixtures/example-ses-s3-event.json | 115 +++ .../src/fixtures/example-ses-sns-event.json | 113 +++ .../src/fixtures/example-sns-event-obj.json | 22 + .../example-sns-event-pascal-case.json | 22 + .../src/fixtures/example-sns-event.json | 22 + .../fixtures/example-sqs-batch-response.json | 10 + .../src/fixtures/example-sqs-event-obj.json | 41 + .../src/fixtures/example-sqs-event.json | 41 + lambda-events/src/lib.rs | 175 ++++ lambda-events/src/time_window.rs | 81 ++ lambda-http/Cargo.toml | 3 +- lambda-http/src/request.rs | 4 +- lambda-http/src/response.rs | 6 +- 178 files changed, 12467 insertions(+), 104 deletions(-) create mode 100644 .github/actions/rust-build/action.yml create mode 100644 .github/workflows/build-events.yml create mode 100644 .github/workflows/build-extension.yml create mode 100644 .github/workflows/build-runtime.yml delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/check-examples.yml create mode 100644 .github/workflows/format.yml create mode 100644 lambda-events/Cargo.toml create mode 100644 lambda-events/README.md create mode 100644 lambda-events/src/custom_serde/codebuild_time.rs create mode 100644 lambda-events/src/custom_serde/float_unix_epoch.rs create mode 100644 lambda-events/src/custom_serde/headers.rs create mode 100644 lambda-events/src/custom_serde/http_method.rs create mode 100644 lambda-events/src/custom_serde/mod.rs create mode 100644 lambda-events/src/encodings.rs create mode 100644 lambda-events/src/event/activemq/mod.rs create mode 100644 lambda-events/src/event/alb/mod.rs create mode 100644 lambda-events/src/event/apigw/mod.rs create mode 100644 lambda-events/src/event/appsync/mod.rs create mode 100644 lambda-events/src/event/autoscaling/mod.rs create mode 100644 lambda-events/src/event/chime_bot/mod.rs create mode 100644 lambda-events/src/event/clientvpn/mod.rs create mode 100644 lambda-events/src/event/cloudwatch_events/cloudtrail.rs create mode 100644 lambda-events/src/event/cloudwatch_events/codedeploy.rs create mode 100644 lambda-events/src/event/cloudwatch_events/codepipeline.rs create mode 100644 lambda-events/src/event/cloudwatch_events/ec2.rs create mode 100644 lambda-events/src/event/cloudwatch_events/emr.rs create mode 100644 lambda-events/src/event/cloudwatch_events/gamelift.rs create mode 100644 lambda-events/src/event/cloudwatch_events/glue.rs create mode 100644 lambda-events/src/event/cloudwatch_events/health.rs create mode 100644 lambda-events/src/event/cloudwatch_events/kms.rs create mode 100644 lambda-events/src/event/cloudwatch_events/macie.rs create mode 100644 lambda-events/src/event/cloudwatch_events/mod.rs create mode 100644 lambda-events/src/event/cloudwatch_events/opsworks.rs create mode 100644 lambda-events/src/event/cloudwatch_events/signin.rs create mode 100644 lambda-events/src/event/cloudwatch_events/sms.rs create mode 100644 lambda-events/src/event/cloudwatch_events/ssm.rs create mode 100644 lambda-events/src/event/cloudwatch_events/tag.rs create mode 100644 lambda-events/src/event/cloudwatch_events/trustedadvisor.rs create mode 100644 lambda-events/src/event/cloudwatch_logs/mod.rs create mode 100644 lambda-events/src/event/code_commit/mod.rs create mode 100644 lambda-events/src/event/codebuild/mod.rs create mode 100644 lambda-events/src/event/codedeploy/mod.rs create mode 100644 lambda-events/src/event/codepipeline_cloudwatch/mod.rs create mode 100644 lambda-events/src/event/codepipeline_job/mod.rs create mode 100644 lambda-events/src/event/cognito/mod.rs create mode 100644 lambda-events/src/event/config/mod.rs create mode 100644 lambda-events/src/event/connect/mod.rs create mode 100644 lambda-events/src/event/dynamodb/attributes.rs create mode 100644 lambda-events/src/event/dynamodb/mod.rs create mode 100644 lambda-events/src/event/ecr_scan/mod.rs create mode 100644 lambda-events/src/event/firehose/mod.rs create mode 100644 lambda-events/src/event/iam/mod.rs create mode 100644 lambda-events/src/event/iot/mod.rs create mode 100644 lambda-events/src/event/iot_1_click/mod.rs create mode 100644 lambda-events/src/event/iot_button/mod.rs create mode 100644 lambda-events/src/event/iot_deprecated/mod.rs create mode 100644 lambda-events/src/event/kafka/mod.rs create mode 100644 lambda-events/src/event/kinesis/analytics.rs create mode 100644 lambda-events/src/event/kinesis/event.rs create mode 100644 lambda-events/src/event/kinesis/mod.rs create mode 100644 lambda-events/src/event/lambda_function_urls/mod.rs create mode 100644 lambda-events/src/event/lex/mod.rs create mode 100644 lambda-events/src/event/mod.rs create mode 100644 lambda-events/src/event/rabbitmq/mod.rs create mode 100644 lambda-events/src/event/s3/batch_job.rs create mode 100644 lambda-events/src/event/s3/event.rs create mode 100644 lambda-events/src/event/s3/mod.rs create mode 100644 lambda-events/src/event/s3/object_lambda.rs create mode 100644 lambda-events/src/event/ses/mod.rs create mode 100644 lambda-events/src/event/sns/mod.rs create mode 100644 lambda-events/src/event/sqs/mod.rs create mode 100644 lambda-events/src/event/streams/mod.rs create mode 100644 lambda-events/src/fixtures/example-activemq-event.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-console-test-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-iam.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request.json create mode 100644 lambda-events/src/fixtures/example-appsync-batchinvoke.json create mode 100644 lambda-events/src/fixtures/example-appsync-identity-cognito.json create mode 100644 lambda-events/src/fixtures/example-appsync-identity-iam.json create mode 100644 lambda-events/src/fixtures/example-appsync-invoke.json create mode 100644 lambda-events/src/fixtures/example-appsync-lambda-auth-request.json create mode 100644 lambda-events/src/fixtures/example-appsync-lambda-auth-response.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json create mode 100644 lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch_logs-event.json create mode 100644 lambda-events/src/fixtures/example-code_commit-event.json create mode 100644 lambda-events/src/fixtures/example-codebuild-phase-change.json create mode 100644 lambda-events/src/fixtures/example-codebuild-state-change.json create mode 100644 lambda-events/src/fixtures/example-codedeploy-deployment-event.json create mode 100644 lambda-events/src/fixtures/example-codedeploy-instance-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline_job-event.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event.json create mode 100644 lambda-events/src/fixtures/example-config-event.json create mode 100644 lambda-events/src/fixtures/example-connect-event-without-queue.json create mode 100644 lambda-events/src/fixtures/example-connect-event.json create mode 100644 lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json create mode 100644 lambda-events/src/fixtures/example-dynamodb-event.json create mode 100644 lambda-events/src/fixtures/example-ecr-image-scan-event.json create mode 100644 lambda-events/src/fixtures/example-firehose-event.json create mode 100644 lambda-events/src/fixtures/example-iot-custom-auth-request.json create mode 100644 lambda-events/src/fixtures/example-iot-custom-auth-response.json create mode 100644 lambda-events/src/fixtures/example-iot_1_click-event.json create mode 100644 lambda-events/src/fixtures/example-iot_button-event.json create mode 100644 lambda-events/src/fixtures/example-kafka-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-firehose-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-firehose-response.json create mode 100644 lambda-events/src/fixtures/example-lex-event.json create mode 100644 lambda-events/src/fixtures/example-lex-response.json create mode 100644 lambda-events/src/fixtures/example-rabbitmq-event.json create mode 100644 lambda-events/src/fixtures/example-s3-event-with-decoded.json create mode 100644 lambda-events/src/fixtures/example-s3-event.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json create mode 100644 lambda-events/src/fixtures/example-ses-event.json create mode 100644 lambda-events/src/fixtures/example-ses-lambda-event.json create mode 100644 lambda-events/src/fixtures/example-ses-s3-event.json create mode 100644 lambda-events/src/fixtures/example-ses-sns-event.json create mode 100644 lambda-events/src/fixtures/example-sns-event-obj.json create mode 100644 lambda-events/src/fixtures/example-sns-event-pascal-case.json create mode 100644 lambda-events/src/fixtures/example-sns-event.json create mode 100644 lambda-events/src/fixtures/example-sqs-batch-response.json create mode 100644 lambda-events/src/fixtures/example-sqs-event-obj.json create mode 100644 lambda-events/src/fixtures/example-sqs-event.json create mode 100644 lambda-events/src/lib.rs create mode 100644 lambda-events/src/time_window.rs diff --git a/.github/actions/rust-build/action.yml b/.github/actions/rust-build/action.yml new file mode 100644 index 00000000..85b5c0e8 --- /dev/null +++ b/.github/actions/rust-build/action.yml @@ -0,0 +1,26 @@ +name: "Rust builds" +description: "Builds, tests, and formats Rust code" +inputs: + package: + required: true + description: "the Rust package to test" + toolchain: + required: true + description: "the Rust toolchain to use" + +runs: + using: "composite" + steps: + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ inputs.toolchain }} + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + + - name: Build + shell: bash + run: cargo build --all-features --verbose --package ${{ inputs.package }} + + - name: Run tests + shell: bash + run: cargo test --all-features --verbose --package ${{ inputs.package }} diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml new file mode 100644 index 00000000..dbf9a0ae --- /dev/null +++ b/.github/workflows/build-events.yml @@ -0,0 +1,28 @@ +name: Check Lambda Events + +on: + push: + paths: + - 'lambda-events/**' + pull_request: + paths: + - 'lambda-events/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build events + uses: ./.github/actions/rust-build + with: + package: aws_lambda_events + toolchain: ${{ matrix.toolchain}} diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml new file mode 100644 index 00000000..0905f289 --- /dev/null +++ b/.github/workflows/build-extension.yml @@ -0,0 +1,39 @@ +name: Check Lambda Runtime + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-extension/**' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-extension/**' + + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build Runtime API Client + uses: ./.github/actions/rust-build + with: + package: lambda_runtime_api_client + toolchain: ${{ matrix.toolchain}} + + + - name: Build Extensions runtime + uses: ./.github/actions/rust-build + with: + package: lambda-extension + toolchain: ${{ matrix.toolchain}} diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml new file mode 100644 index 00000000..68913c95 --- /dev/null +++ b/.github/workflows/build-runtime.yml @@ -0,0 +1,45 @@ +name: Check Lambda Runtime + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build Runtime API Client + uses: ./.github/actions/rust-build + with: + package: lambda_runtime_api_client + toolchain: ${{ matrix.toolchain}} + + - name: Build Functions runtime + uses: ./.github/actions/rust-build + with: + package: lambda_runtime + toolchain: ${{ matrix.toolchain}} + + - name: Build HTTP layer + uses: ./.github/actions/rust-build + with: + package: lambda_http + toolchain: ${{ matrix.toolchain}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 370d8249..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Rust - -on: [push, pull_request, workflow_dispatch] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macOS-latest - toolchain: - - "1.62.0" # Current MSRV - - stable - - beta - - nightly - target: - - "" - - x86_64-unknown-linux-musl - include: - - rust: nightly - allow_failure: true - env: - RUST_BACKTRACE: 1 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - - uses: Swatinem/rust-cache@v2 - - - name: Build - run: cargo build --all --verbose - env: - TARGET: ${{ matrix.target }} - continue-on-error: ${{ matrix.allow_failure }} - - name: Run tests - run: cargo test --all --verbose - env: - TARGET: ${{ matrix.target }} - continue-on-error: ${{ matrix.allow_failure }} - formatting: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Run fmt check - run: cargo fmt --all -- --check - - name: Run clippy check - run: cargo clippy --all-features -- -D warnings - - check-examples: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Check examples - working-directory: examples - shell: bash - run: ./check-examples.sh - - # publish rustdoc to a gh-pages branch on pushes to main - # this can be helpful to those depending on the mainline branch - publish-docs: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - needs: [build] - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Generate Docs - run: | - cargo doc --no-deps - echo "" > target/doc/index.html - - name: Publish - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml new file mode 100644 index 00000000..ba7bc709 --- /dev/null +++ b/.github/workflows/check-examples.yml @@ -0,0 +1,16 @@ +name: Check examples + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Check examples + working-directory: examples + shell: bash + run: ./check-examples.sh \ No newline at end of file diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..f18d1f83 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,55 @@ +name: Formatting and Linting + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Run fmt check + id: cargoFmt + shell: bash + run: cargo fmt --all -- --check + - name: Notify fmt check + if: failure() && steps.cargoFmt.outcome == 'failure' + uses: actions/github-script@v6 + with: + script: | + const message = `👋 It looks like your code is not formatted like we expect. + + Please run \`cargo fmt\` and push the code again.`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message, + }); + core.setFailed('It looks like there are formatting errors'); + + - name: Run clippy check + id: cargoClippy + shell: bash + run: cargo clippy --workspace --all-features -- -D warnings + - name: Notify fmt check + if: failure() && steps.cargoClippy.outcome == 'failure' + uses: actions/github-script@v6 + with: + script: | + const message = `👋 It looks like your code has some linting issues. + + Please run \`cargo clippy --fix\` and push the code again.`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message, + }); + core.setFailed('It looks like there are linting errors'); + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7361557e..48bcd5db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "lambda-integration-tests", "lambda-runtime-api-client", "lambda-runtime", - "lambda-extension" + "lambda-extension", + "lambda-events" ] exclude = ["examples"] diff --git a/README.md b/README.md index 8fdd232c..157c1c98 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. - [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. +- [![Docs](https://docs.rs/aws_lambda_events/badge.svg)](https://docs.rs/aws_lambda_events) **`lambda-events`** is a library with strongly-typed Lambda event structs in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. The Rust runtime client is an experimental package. It is subject to change and intended only for evaluation purposes. diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 06e56053..0dfe49d9 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -8,8 +8,8 @@ serde = "^1" serde_derive = "^1" serde_with = { version = "^2", features = ["json"], optional = true } serde_json = "^1" -aws_lambda_events = "0.7.3" -lambda_runtime = "0.7" +aws_lambda_events = { path = "../../lambda-events" } +lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } futures = "0.3" tracing = { version = "0.1", features = ["log"] } diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index dfa6d69b..0ef0d463 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws_lambda_events = "0.7.2" +aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 086a22dc..a1b11567 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws_lambda_events = "0.7.2" +aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml new file mode 100644 index 00000000..225d9d8f --- /dev/null +++ b/lambda-events/Cargo.toml @@ -0,0 +1,117 @@ +[package] +name = "aws_lambda_events" +version = "0.9.0" +description = "AWS Lambda event definitions" +authors = [ + "Christian Legnitto ", + "Sam Rijs ", + "David Calavera ", +] +license = "MIT" +homepage = "https://github.com/awslabs/aws-lambda-rust-runtime" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +readme = "README.md" +keywords = ["lambda", "aws", "amazon", "events", "S3"] +categories = ["api-bindings", "encoding", "web-programming"] + +[dependencies] +base64 = "0.13" +http = "0.2" +http-body = "0.4" +http-serde = "^1" +serde = "^1" +serde_derive = "^1" +serde_with = { version = "^2", features = ["json"], optional = true } +serde_json = "^1" +serde_dynamo = { version = "^4.1", optional = true } +bytes = { version = "1", features = ["serde"] } +chrono = { version = "0.4.23", default-features = false, features = [ + "clock", + "serde", + "std", +] } +query_map = { version = "^0.6", features = ["serde", "url-query"] } +flate2 = { version = "1.0.24", optional = true } + +[dev-dependencies] +pretty_assertions = "1.3" + +[features] +default = [ + "activemq", + "alb", + "apigw", + "appsync", + "autoscaling", + "chime_bot", + "clientvpn", + "cloudwatch_events", + "cloudwatch_logs", + "code_commit", + "codebuild", + "codedeploy", + "codepipeline_cloudwatch", + "codepipeline_job", + "cognito", + "config", + "connect", + "dynamodb", + "ecr_scan", + "firehose", + "iam", + "iot", + "iot_1_click", + "iot_button", + "iot_deprecated", + "kafka", + "kinesis", + "kinesis_analytics", + "lambda_function_urls", + "lex", + "rabbitmq", + "s3", + "s3_batch_job", + "ses", + "sns", + "sqs", + "streams", +] + +activemq = [] +alb = [] +apigw = [] +appsync = [] +autoscaling = [] +chime_bot = [] +clientvpn = [] +cloudwatch_events = [] +cloudwatch_logs = ["flate2"] +code_commit = [] +codebuild = [] +codedeploy = [] +codepipeline = [] +codepipeline_cloudwatch = [] +codepipeline_job = [] +cognito = [] +config = [] +connect = [] +dynamodb = ["streams", "serde_dynamo"] +ecr_scan = [] +firehose = [] +iam = [] +iot = ["iam"] +iot_1_click = [] +iot_button = [] +iot_deprecated = ["iot"] +kafka = [] +kinesis = [] +kinesis_analytics = ["kinesis"] +lambda_function_urls = [] +lex = [] +rabbitmq = [] +s3 = [] +s3_batch_job = ["s3"] +ses = [] +sns = ["serde_with"] +sqs = ["serde_with"] +streams = [] diff --git a/lambda-events/README.md b/lambda-events/README.md new file mode 100644 index 00000000..0813c63a --- /dev/null +++ b/lambda-events/README.md @@ -0,0 +1,34 @@ +# AWS Lambda Events + +[![crates.io][crate-image]][crate-link] +[![Documentation][docs-image]][docs-link] + +This crate provides strongly-typed [AWS Lambda event structs](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html) in Rust. + +## Installation + +Add the dependency with Cargo: `cargo add aws_lambda_events`. + +## Usage + +The crate itself has no AWS Lambda handler logic and instead exists to serialize +and deserialize AWS Lambda events into strongly-typed Rust structs. + +The types +defined in this crate are usually used with handlers / runtimes provided by the [official Rust runtime](https://github.com/awslabs/aws-lambda-rust-runtime). + +For a list of supported AWS Lambda events and services, see [the crate reference documentation](https://docs.rs/aws_lambda_events). + +## Conditional compilation of features + +This crate divides all Lambda Events into features named after the service that the events are generated from. By default all events are enabled when you include this crate as a dependency to your project. If you only want to import specific events from this crate, you can disable the default features, and enable only the events that you need. This will make your project to compile a little bit faster, since rustc doesn't need to compile events that you're not going to use. Here's an example on how to do that: + +``` +cargo add aws_lambda_events --no-default-features --features apigw,alb +``` + +[//]: # 'badges' +[crate-image]: https://img.shields.io/crates/v/aws_lambda_events.svg +[crate-link]: https://crates.io/crates/aws_lambda_events +[docs-image]: https://docs.rs/aws_lambda_events/badge.svg +[docs-link]: https://docs.rs/aws_lambda_events \ No newline at end of file diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs new file mode 100644 index 00000000..c57ccd6a --- /dev/null +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -0,0 +1,105 @@ +use chrono::{DateTime, TimeZone, Utc}; +use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; +use serde::ser::Serializer; +use std::fmt; + +// Jan 2, 2006 3:04:05 PM +const CODEBUILD_TIME_FORMAT: &str = "%b %e, %Y %l:%M:%S %p"; + +struct TimeVisitor; +impl<'de> Visitor<'de> for TimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) + } + + fn visit_str(self, val: &str) -> Result { + Utc.datetime_from_str(val, CODEBUILD_TIME_FORMAT) + .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) + } +} + +pub(crate) mod str_time { + use super::*; + + pub(crate) fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + d.deserialize_str(TimeVisitor) + } + + pub fn serialize(date: &DateTime, ser: S) -> Result { + let s = format!("{}", date.format(CODEBUILD_TIME_FORMAT)); + ser.serialize_str(&s) + } +} + +pub(crate) mod optional_time { + use super::*; + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(val) = s { + let visitor = TimeVisitor {}; + return visitor.visit_str(&val).map(Some); + } + + Ok(None) + } + + pub fn serialize(date: &Option>, ser: S) -> Result { + if let Some(date) = date { + return str_time::serialize(date, ser); + } + + ser.serialize_none() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + type TestTime = DateTime; + + #[test] + fn test_deserialize_codebuild_time() { + #[derive(Deserialize)] + struct Test { + #[serde(with = "str_time")] + pub date: TestTime, + } + let data = json!({ + "date": "Sep 1, 2017 4:12:29 PM" + }); + + let expected = Utc + .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap(); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.date); + } + + #[test] + fn test_deserialize_codebuild_optional_time() { + #[derive(Deserialize)] + struct Test { + #[serde(with = "optional_time")] + pub date: Option, + } + let data = json!({ + "date": "Sep 1, 2017 4:12:29 PM" + }); + + let expected = Utc + .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap(); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(Some(expected), decoded.date); + } +} diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs new file mode 100644 index 00000000..54fc64e4 --- /dev/null +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -0,0 +1,109 @@ +use serde::{de, ser}; +use std::fmt; + +use chrono::offset::TimeZone; +use chrono::{DateTime, LocalResult, Utc}; + +enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, +} + +fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } +} + +impl fmt::Debug for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChronoSerdeError({})", self) + } +} + +impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SerdeError::NonExistent { ref timestamp } => { + write!(f, "value is not a legal timestamp: {}", timestamp) + } + SerdeError::Ambiguous { + ref timestamp, + ref min, + ref max, + } => write!( + f, + "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max + ), + } + } +} + +fn serde_from(me: LocalResult, ts: &V) -> Result +where + E: de::Error, + V: fmt::Display, + T: fmt::Display, +{ + match me { + LocalResult::None => Err(E::custom(ne_timestamp(ts))), + LocalResult::Ambiguous(min, max) => Err(E::custom(SerdeError::Ambiguous { + timestamp: ts, + min, + max, + })), + LocalResult::Single(val) => Ok(val), + } +} + +struct SecondsFloatTimestampVisitor; + +/// Serialize a UTC datetime into an float number of seconds since the epoch +/// ``` +pub fn serialize(dt: &DateTime, serializer: S) -> Result +where + S: ser::Serializer, +{ + serializer.serialize_i64(dt.timestamp_millis() / 1000) +} + +/// Deserialize a `DateTime` from a float seconds timestamp +pub fn deserialize<'de, D>(d: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + d.deserialize_f64(SecondsFloatTimestampVisitor) +} + +impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp as a float") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result, E> + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value as i64, 0), &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result, E> + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value, 0), &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_f64(self, value: f64) -> Result, E> + where + E: de::Error, + { + let time_ms = (value.fract() * 1_000_000.).floor() as u32; + let time_s = value.trunc() as i64; + serde_from(Utc.timestamp_opt(time_s, time_ms), &value) + } +} diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs new file mode 100644 index 00000000..904ccd9b --- /dev/null +++ b/lambda-events/src/custom_serde/headers.rs @@ -0,0 +1,189 @@ +use http::header::HeaderName; +use http::{HeaderMap, HeaderValue}; +use serde::de::{self, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor}; +use serde::ser::{Error as SerError, SerializeMap, Serializer}; +use std::{borrow::Cow, fmt}; + +/// Serialize a http::HeaderMap into a serde str => Vec map +pub(crate) fn serialize_multi_value_headers(headers: &HeaderMap, serializer: S) -> Result +where + S: Serializer, +{ + let mut map = serializer.serialize_map(Some(headers.keys_len()))?; + for key in headers.keys() { + let mut map_values = Vec::new(); + for value in headers.get_all(key) { + map_values.push(value.to_str().map_err(S::Error::custom)?) + } + map.serialize_entry(key.as_str(), &map_values)?; + } + map.end() +} + +/// Serialize a http::HeaderMap into a serde str => str map +pub(crate) fn serialize_headers(headers: &HeaderMap, serializer: S) -> Result +where + S: Serializer, +{ + let mut map = serializer.serialize_map(Some(headers.keys_len()))?; + for key in headers.keys() { + let map_value = headers[key].to_str().map_err(S::Error::custom)?; + map.serialize_entry(key.as_str(), map_value)?; + } + map.end() +} + +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum OneOrMore<'a> { + One(Cow<'a, str>), + Strings(Vec>), + Bytes(Vec>), +} + +struct HeaderMapVisitor { + is_human_readable: bool, +} + +impl<'de> Visitor<'de> for HeaderMapVisitor { + type Value = HeaderMap; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("lots of things can go wrong with HeaderMap") + } + + fn visit_unit(self) -> Result + where + E: DeError, + { + Ok(HeaderMap::default()) + } + + fn visit_none(self) -> Result + where + E: DeError, + { + Ok(HeaderMap::default()) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(self) + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = HeaderMap::with_capacity(access.size_hint().unwrap_or(0)); + + if !self.is_human_readable { + while let Some((key, arr)) = access.next_entry::, Vec>>()? { + let key = HeaderName::from_bytes(key.as_bytes()) + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; + for val in arr { + let val = HeaderValue::from_bytes(&val) + .map_err(|_| de::Error::invalid_value(Unexpected::Bytes(&val), &self))?; + map.append(&key, val); + } + } + } else { + while let Some((key, val)) = access.next_entry::, OneOrMore>()? { + let key = HeaderName::from_bytes(key.as_bytes()) + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; + match val { + OneOrMore::One(val) => { + let val = val + .parse() + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?; + map.insert(key, val); + } + OneOrMore::Strings(arr) => { + for val in arr { + let val = val + .parse() + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?; + map.append(&key, val); + } + } + OneOrMore::Bytes(arr) => { + for val in arr { + let val = HeaderValue::from_bytes(&val) + .map_err(|_| de::Error::invalid_value(Unexpected::Bytes(&val), &self))?; + map.append(&key, val); + } + } + }; + } + } + Ok(map) + } +} + +/// Implementation detail. +pub(crate) fn deserialize_headers<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + let is_human_readable = de.is_human_readable(); + de.deserialize_option(HeaderMapVisitor { is_human_readable }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_missing_http_headers() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + pub headers: HeaderMap, + } + let data = json!({ + "not_headers": {} + }); + + let expected = HeaderMap::new(); + + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.headers); + } + + #[test] + fn test_serialize_headers() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + headers: HeaderMap, + } + let data = json!({ + "headers": { + "Accept": ["*/*"] + } + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(&"*/*", decoded.headers.get("Accept").unwrap()); + + let recoded = serde_json::to_value(decoded).unwrap(); + let decoded: Test = serde_json::from_value(recoded).unwrap(); + assert_eq!(&"*/*", decoded.headers.get("Accept").unwrap()); + } + + #[test] + fn test_null_headers() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers")] + headers: HeaderMap, + } + let data = json!({ "headers": null }); + + let decoded: Test = serde_json::from_value(data).unwrap(); + assert!(decoded.headers.is_empty()); + } +} diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs new file mode 100644 index 00000000..6060a429 --- /dev/null +++ b/lambda-events/src/custom_serde/http_method.rs @@ -0,0 +1,103 @@ +use http::Method; +use serde::de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}; +use serde::ser::Serializer; +use std::fmt; + +pub fn serialize(method: &Method, ser: S) -> Result { + ser.serialize_str(method.as_str()) +} + +struct MethodVisitor; +impl<'de> Visitor<'de> for MethodVisitor { + type Value = Method; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "valid method name") + } + + fn visit_str(self, val: &str) -> Result { + if val.is_empty() { + Ok(Method::GET) + } else { + val.parse() + .map_err(|_| DeError::invalid_value(Unexpected::Str(val), &self)) + } + } +} + +pub fn deserialize<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + de.deserialize_str(MethodVisitor) +} + +pub fn deserialize_optional<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: Option = Option::deserialize(deserializer)?; + if let Some(val) = s { + let visitor = MethodVisitor {}; + return visitor.visit_str(&val).map(Some); + } + + Ok(None) +} + +pub fn serialize_optional(method: &Option, ser: S) -> Result { + if let Some(method) = method { + return serialize(method, ser); + } + + ser.serialize_none() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_http_method_serializer() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(with = "crate::custom_serde::http_method")] + pub method: http::Method, + } + let data = json!({ + "method": "DELETE" + }); + let decoded: Test = serde_json::from_value(data.clone()).unwrap(); + assert_eq!(http::Method::DELETE, decoded.method); + + let recoded = serde_json::to_value(decoded).unwrap(); + assert_eq!(data, recoded); + } + + #[test] + fn test_http_optional_method_serializer() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_optional")] + #[serde(serialize_with = "serialize_optional")] + #[serde(default)] + pub method: Option, + } + let data = json!({ + "method": "DELETE" + }); + let decoded: Test = serde_json::from_value(data.clone()).unwrap(); + assert_eq!(Some(http::Method::DELETE), decoded.method); + + let recoded = serde_json::to_value(decoded).unwrap(); + assert_eq!(data, recoded); + + let data = json!({ "method": null }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(None, decoded.method); + + let data = json!({}); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(None, decoded.method); + } +} diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs new file mode 100644 index 00000000..964f460e --- /dev/null +++ b/lambda-events/src/custom_serde/mod.rs @@ -0,0 +1,455 @@ +#[allow(unused)] +use base64::{decode, encode}; +use chrono::{DateTime, Duration, TimeZone, Utc}; +use serde; +use serde::de::{Deserialize, Deserializer, Error as DeError}; +use serde::ser::Serializer; +use std::collections::HashMap; + +#[cfg(feature = "codebuild")] +pub(crate) mod codebuild_time; +#[cfg(feature = "codebuild")] +pub type CodeBuildNumber = f32; + +#[cfg(any( + feature = "alb", + feature = "apigw", + feature = "s3", + feature = "iot", + feature = "lambda_function_urls" +))] +mod headers; +#[cfg(any( + feature = "alb", + feature = "apigw", + feature = "s3", + feature = "iot", + feature = "lambda_function_urls" +))] +pub(crate) use self::headers::*; + +#[cfg(feature = "dynamodb")] +pub(crate) mod float_unix_epoch; + +#[cfg(any(feature = "alb", feature = "apigw"))] +pub(crate) mod http_method; + +fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrNumber { + String(String), + Float(f64), + Int(u64), + } + + let input: f64 = match StringOrNumber::deserialize(deserializer)? { + StringOrNumber::String(s) => s.parse::().map_err(DeError::custom)?, + StringOrNumber::Float(f) => f, + StringOrNumber::Int(i) => i as f64, + }; + + // We need to do this due to floating point issues. + let input_as_string = format!("{}", input); + let parts: Result, _> = input_as_string + .split('.') + .map(|x| x.parse::().map_err(DeError::custom)) + .collect(); + let parts = parts?; + if parts.len() > 1 { + Ok((parts[0], parts[1])) + } else { + Ok((parts[0], 0)) + } +} + +pub(crate) fn serialize_milliseconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let ts_with_millis = date.timestamp_millis(); + serializer.serialize_str(&ts_with_millis.to_string()) +} + +pub(crate) fn deserialize_milliseconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + assert_eq!(frac, 0); + let seconds: f64 = whole as f64 / 1000.0; + let milliseconds: u32 = (seconds.fract() * 1000f64) as u32; + let nanos = milliseconds * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +pub(crate) fn serialize_seconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = date.timestamp(); + let milliseconds = date.timestamp_subsec_millis(); + let whole_seconds = seconds + (milliseconds as i64 / 1000); + let subsec_millis = milliseconds % 1000; + if milliseconds > 0 { + let combined = format!("{}.{:03}", whole_seconds, subsec_millis); + serializer.serialize_str(&combined) + } else { + serializer.serialize_str(&whole_seconds.to_string()) + } +} + +#[allow(dead_code)] +pub(crate) fn deserialize_seconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + let seconds = whole; + let nanos = frac * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos as u32) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +pub(crate) fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: String = String::deserialize(deserializer)?; + decode(s).map_err(DeError::custom) +} + +pub(crate) fn serialize_base64(value: &[u8], serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&encode(value)) +} + +/// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. +pub(crate) fn deserialize_lambda_map<'de, D, K, V>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + K: serde::Deserialize<'de>, + K: std::hash::Hash, + K: std::cmp::Eq, + V: serde::Deserialize<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + +#[cfg(feature = "dynamodb")] +/// Deserializes `Item`, mapping JSON `null` to an empty item. +pub(crate) fn deserialize_lambda_dynamodb_item<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + +pub(crate) fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = duration.num_seconds(); + + serializer.serialize_i64(seconds) +} + +pub(crate) fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let seconds = f64::deserialize(deserializer)?; + Ok(Duration::seconds(seconds as i64)) +} + +pub(crate) fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let minutes = duration.num_minutes(); + + serializer.serialize_i64(minutes) +} + +pub(crate) fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let minutes = f64::deserialize(deserializer)?; + Ok(Duration::minutes(minutes as i64)) +} + +/// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. +#[cfg(any( + feature = "alb", + feature = "apigw", + feature = "cloudwatch_events", + feature = "code_commit", + test +))] +pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + use serde_json; + + #[test] + fn test_deserialize_base64() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_base64")] + v: Vec, + } + let data = json!({ + "v": "SGVsbG8gV29ybGQ=", + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(String::from_utf8(decoded.v).unwrap(), "Hello World".to_string()); + } + + #[test] + fn test_serialize_base64() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_base64")] + v: Vec, + } + let instance = Test { + v: "Hello World".as_bytes().to_vec(), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, r#"{"v":"SGVsbG8gV29ybGQ="}"#.to_string()); + } + + #[test] + fn test_deserialize_milliseconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_milliseconds")] + v: DateTime, + } + let expected = Utc.ymd(2017, 10, 5).and_hms_nano(15, 33, 44, 302_000_000); + + // Test parsing strings. + let data = json!({ + "v": "1507217624302", + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing ints. + let decoded: Test = serde_json::from_slice(r#"{"v":1507217624302}"#.as_bytes()).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing floats. + let data = json!({ + "v": 1507217624302.0, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_milliseconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_milliseconds")] + v: DateTime, + } + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99_888_777), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600099"}"#)); + } + + #[test] + fn test_serialize_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_seconds")] + v: DateTime, + } + + // Make sure nanoseconds are chopped off. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 2_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); + } + + #[test] + fn test_deserialize_map() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_lambda_map")] + v: HashMap, + } + let input = json!({ + "v": {}, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(HashMap::new(), decoded.v); + + let input = json!({ + "v": null, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(HashMap::new(), decoded.v); + } + + #[cfg(feature = "dynamodb")] + #[test] + fn test_deserialize_lambda_dynamodb_item() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + v: serde_dynamo::Item, + } + let input = json!({ + "v": {}, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); + + let input = json!({ + "v": null, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); + } + + #[test] + fn test_deserialize_duration_seconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_seconds")] + v: Duration, + } + + let expected = Duration::seconds(36); + + let data = json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_seconds")] + v: Duration, + } + let instance = Test { + v: Duration::seconds(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } + + #[test] + fn test_deserialize_duration_minutes() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_minutes")] + v: Duration, + } + + let expected = Duration::minutes(36); + + let data = json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_minutes() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_minutes")] + v: Duration, + } + let instance = Test { + v: Duration::minutes(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } + + #[test] + fn test_deserialize_nullish_boolean() { + #[derive(Deserialize)] + struct Test { + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + v: bool, + } + + let test = r#"{"v": null}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + + let test = r#"{}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + + let test = r#"{"v": true}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(true, decoded.v); + + let test = r#"{"v": false}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + } +} diff --git a/lambda-events/src/encodings.rs b/lambda-events/src/encodings.rs new file mode 100644 index 00000000..ecd32340 --- /dev/null +++ b/lambda-events/src/encodings.rs @@ -0,0 +1,449 @@ +use super::custom_serde::*; +use chrono::{DateTime, Duration, Utc}; +use std::{borrow::Cow, mem::take, ops::Deref, ops::DerefMut, pin::Pin, task::Poll}; + +use base64::display::Base64Display; +use bytes::Bytes; +use http_body::{Body as HttpBody, SizeHint}; +use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; +use serde::ser::{Error as SerError, Serialize, Serializer}; + +pub type Error = Box; + +/// Binary data encoded in base64. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Base64Data( + #[serde(deserialize_with = "deserialize_base64")] + #[serde(serialize_with = "serialize_base64")] + pub Vec, +); + +impl Deref for Base64Data { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Base64Data { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Timestamp with millisecond precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MillisecondTimestamp( + #[serde(deserialize_with = "deserialize_milliseconds")] + #[serde(serialize_with = "serialize_milliseconds")] + pub DateTime, +); + +impl Deref for MillisecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MillisecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Timestamp with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondTimestamp( + #[serde(deserialize_with = "deserialize_seconds")] + #[serde(serialize_with = "serialize_seconds")] + pub DateTime, +); + +impl Deref for SecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondDuration( + #[serde(deserialize_with = "deserialize_duration_seconds")] + #[serde(serialize_with = "serialize_duration_seconds")] + pub Duration, +); + +impl Deref for SecondDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with minute precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MinuteDuration( + #[serde(deserialize_with = "deserialize_duration_minutes")] + #[serde(serialize_with = "serialize_duration_minutes")] + pub Duration, +); + +impl Deref for MinuteDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MinuteDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Representation of http request and response bodies as supported +/// by API Gateway and ALBs. +/// +/// These come in three flavors +/// * `Empty` ( no body ) +/// * `Text` ( text data ) +/// * `Binary` ( binary data ) +/// +/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the [hyper crate](https://crates.io/crates/hyper) +/// +/// # Examples +/// +/// Body types are inferred with `From` implementations. +/// +/// ## Text +/// +/// Types like `String`, `str` whose type reflects +/// text produce `Body::Text` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from("text") { +/// aws_lambda_events::encodings::Body::Text(_) => true, +/// _ => false +/// }) +/// ``` +/// +/// ## Binary +/// +/// Types like `Vec` and `&[u8]` whose types reflect raw bytes produce `Body::Binary` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from("text".as_bytes()) { +/// aws_lambda_events::encodings::Body::Binary(_) => true, +/// _ => false +/// }) +/// ``` +/// +/// `Binary` responses bodies will automatically get based64 encoded to meet API Gateway's response expectations. +/// +/// ## Empty +/// +/// The unit type ( `()` ) whose type represents an empty value produces `Body::Empty` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from(()) { +/// aws_lambda_events::encodings::Body::Empty => true, +/// _ => false +/// }) +/// ``` +/// +/// +/// For more information about API Gateway's body types, +/// refer to [this documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html). +#[derive(Debug, Default, Eq, PartialEq)] +pub enum Body { + /// An empty body + #[default] + Empty, + /// A body containing string data + Text(String), + /// A body containing binary data + Binary(Vec), +} + +impl Body { + /// Decodes body, if needed. + /// + /// # Panics + /// + /// Panics when aws communicates to handler that request is base64 encoded but + /// it can not be base64 decoded + pub fn from_maybe_encoded(is_base64_encoded: bool, body: &str) -> Body { + if is_base64_encoded { + Body::from(::base64::decode(body).expect("failed to decode aws base64 encoded body")) + } else { + Body::from(body) + } + } +} + +impl From<()> for Body { + fn from(_: ()) -> Self { + Body::Empty + } +} + +impl<'a> From<&'a str> for Body { + fn from(s: &'a str) -> Self { + Body::Text(s.into()) + } +} + +impl From for Body { + fn from(b: String) -> Self { + Body::Text(b) + } +} + +impl From> for Body { + #[inline] + fn from(cow: Cow<'static, str>) -> Body { + match cow { + Cow::Borrowed(b) => Body::from(b.to_owned()), + Cow::Owned(o) => Body::from(o), + } + } +} + +impl From> for Body { + #[inline] + fn from(cow: Cow<'static, [u8]>) -> Body { + match cow { + Cow::Borrowed(b) => Body::from(b), + Cow::Owned(o) => Body::from(o), + } + } +} + +impl From> for Body { + fn from(b: Vec) -> Self { + Body::Binary(b) + } +} + +impl<'a> From<&'a [u8]> for Body { + fn from(b: &'a [u8]) -> Self { + Body::Binary(b.to_vec()) + } +} + +impl Deref for Body { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl AsRef<[u8]> for Body { + #[inline] + fn as_ref(&self) -> &[u8] { + match self { + Body::Empty => &[], + Body::Text(ref bytes) => bytes.as_ref(), + Body::Binary(ref bytes) => bytes.as_ref(), + } + } +} + +impl Clone for Body { + fn clone(&self) -> Self { + match self { + Body::Empty => Body::Empty, + Body::Text(ref bytes) => Body::Text(bytes.clone()), + Body::Binary(ref bytes) => Body::Binary(bytes.clone()), + } + } +} + +impl Serialize for Body { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Body::Text(data) => { + serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?) + } + Body::Binary(data) => serializer.collect_str(&Base64Display::with_config(data, base64::STANDARD)), + Body::Empty => serializer.serialize_unit(), + } + } +} + +impl<'de> Deserialize<'de> for Body { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BodyVisitor; + + impl<'de> Visitor<'de> for BodyVisitor { + type Value = Body; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("string") + } + + fn visit_str(self, value: &str) -> Result + where + E: DeError, + { + Ok(Body::from(value)) + } + } + + deserializer.deserialize_str(BodyVisitor) + } +} + +impl HttpBody for Body { + type Data = Bytes; + type Error = Error; + + fn poll_data( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll>> { + let body = take(self.get_mut()); + Poll::Ready(match body { + Body::Empty => None, + Body::Text(s) => Some(Ok(s.into())), + Body::Binary(b) => Some(Ok(b.into())), + }) + } + + fn poll_trailers( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>> { + Poll::Ready(Ok(None)) + } + + fn is_end_stream(&self) -> bool { + matches!(self, Body::Empty) + } + + fn size_hint(&self) -> SizeHint { + match self { + Body::Empty => SizeHint::default(), + Body::Text(ref s) => SizeHint::with_exact(s.len() as u64), + Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + use std::collections::HashMap; + + #[test] + fn body_has_default() { + assert_eq!(Body::default(), Body::Empty); + } + + #[test] + fn from_unit() { + assert_eq!(Body::from(()), Body::Empty); + } + + #[test] + fn from_str() { + match Body::from(String::from("foo").as_str()) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_string() { + match Body::from(String::from("foo")) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_cow_str() { + match Body::from(Cow::from("foo")) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_cow_bytes() { + match Body::from(Cow::from("foo".as_bytes())) { + Body::Binary(_) => (), + not => panic!("expected Body::Binary(...) got {:?}", not), + } + } + + #[test] + fn from_bytes() { + match Body::from("foo".as_bytes()) { + Body::Binary(_) => (), + not => panic!("expected Body::Binary(...) got {:?}", not), + } + } + + #[test] + fn serialize_text() { + let mut map = HashMap::new(); + map.insert("foo", Body::from("bar")); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"bar"}"#); + } + + #[test] + fn serialize_binary() { + let mut map = HashMap::new(); + map.insert("foo", Body::from("bar".as_bytes())); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"YmFy"}"#); + } + + #[test] + fn serialize_empty() { + let mut map = HashMap::new(); + map.insert("foo", Body::Empty); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":null}"#); + } + + #[test] + fn serialize_from_maybe_encoded() { + match Body::from_maybe_encoded(false, "foo") { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + + match Body::from_maybe_encoded(true, "Zm9v") { + Body::Binary(b) => assert_eq!(&[102, 111, 111], b.as_slice()), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } +} diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs new file mode 100644 index 00000000..fcb490ec --- /dev/null +++ b/lambda-events/src/event/activemq/mod.rs @@ -0,0 +1,66 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + pub messages: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqMessage { + #[serde(default)] + #[serde(rename = "messageID")] + pub message_id: Option, + #[serde(default)] + pub message_type: Option, + pub timestamp: i64, + pub delivery_mode: i64, + #[serde(default)] + #[serde(rename = "correlationID")] + pub correlation_id: Option, + #[serde(default)] + pub reply_to: Option, + pub destination: ActiveMqDestination, + pub redelivered: bool, + #[serde(default)] + pub type_: Option, + pub expiration: i64, + pub priority: i64, + #[serde(default)] + pub data: Option, + pub broker_in_time: i64, + pub broker_out_time: i64, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub properties: HashMap, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqDestination { + #[serde(default)] + pub physical_name: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "activemq")] + fn example_activemq_event() { + let data = include_bytes!("../../fixtures/example-activemq-event.json"); + let parsed: ActiveMqEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ActiveMqEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs new file mode 100644 index 00000000..b9a69ce5 --- /dev/null +++ b/lambda-events/src/event/alb/mod.rs @@ -0,0 +1,98 @@ +use crate::custom_serde::{http_method, serialize_headers, serialize_multi_value_headers}; +use crate::encodings::Body; +use http::{HeaderMap, Method}; +use query_map::QueryMap; + +/// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupRequest { + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub query_string_parameters: QueryMap, + #[serde(default)] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + pub request_context: AlbTargetGroupRequestContext, + pub is_base64_encoded: bool, + pub body: Option, +} + +/// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupRequestContext { + pub elb: ElbContext, +} + +/// `ElbContext` contains the information to identify the ARN invoking the lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ElbContext { + /// nolint: stylecheck + #[serde(default)] + pub target_group_arn: Option, +} + +/// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupResponse { + pub status_code: i64, + #[serde(default)] + pub status_description: Option, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + pub is_base64_encoded: bool, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_request_headers_only() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-request-headers-only.json"); + let parsed: AlbTargetGroupRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AlbTargetGroupRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_request_multivalue_headers() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-request-multivalue-headers.json"); + let parsed: AlbTargetGroupRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AlbTargetGroupRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_response() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-response.json"); + let parsed: AlbTargetGroupResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AlbTargetGroupResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs new file mode 100644 index 00000000..80063911 --- /dev/null +++ b/lambda-events/src/event/apigw/mod.rs @@ -0,0 +1,911 @@ +use crate::custom_serde::{ + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, +}; +use crate::encodings::Body; +use http::{HeaderMap, Method}; +use query_map::QueryMap; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayProxyRequest +where + T1: DeserializeOwned, + T1: Serialize, +{ + /// The resource path defined in API Gateway + #[serde(default)] + pub resource: Option, + /// The url path for the caller + #[serde(default)] + pub path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + #[serde(default)] + #[serde(bound = "")] + pub request_context: ApiGatewayProxyRequestContext, + #[serde(default)] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayProxyResponse { + pub status_code: i64, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the +/// Lambda function. It also includes Cognito identity information for the caller. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayProxyRequestContext +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + pub operation_name: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub identity: ApiGatewayRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub authorizer: HashMap, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + pub request_time: Option, + pub request_time_epoch: i64, + /// The API Gateway rest API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +/// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequest { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + pub cookies: Option>, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde( + default, + deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" + )] + pub query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + pub request_context: ApiGatewayV2httpRequestContext, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub body: Option, + #[serde(default)] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContext +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(bound = "", default)] + pub authorizer: Option>, + /// The API Gateway HTTP API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub time: Option, + pub time_epoch: i64, + pub http: ApiGatewayV2httpRequestContextHttpDescription, + pub authentication: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerDescription` contains authorizer information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerDescription +where + T1: DeserializeOwned, + T1: Serialize, +{ + pub jwt: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub lambda: HashMap, + pub iam: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerJwtDescription` contains JWT authorizer information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub claims: HashMap, + pub scopes: Option>, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub caller_id: Option, + pub cognito_identity: Option, + #[serde(default)] + pub principal_org_id: Option, + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_id: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity { + pub amr: Vec, + #[serde(default)] + pub identity_id: Option, + #[serde(default)] + pub identity_pool_id: Option, +} + +/// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextHttpDescription { + #[serde(with = "http_method")] + pub method: Method, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub user_agent: Option, +} + +/// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpResponse { + pub status_code: i64, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, + pub cookies: Vec, +} + +/// `ApiGatewayRequestIdentity` contains identity information for the request caller. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayRequestIdentity { + #[serde(default)] + pub cognito_identity_pool_id: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub cognito_identity_id: Option, + #[serde(default)] + pub caller: Option, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub api_key_id: Option, + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub cognito_authentication_type: Option, + #[serde(default)] + pub cognito_authentication_provider: Option, + /// nolint: stylecheck + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_agent: Option, + #[serde(default)] + pub user: Option, +} + +/// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayWebsocketProxyRequest +where + T1: DeserializeOwned, + T1: Serialize, + T2: DeserializeOwned, + T2: Serialize, +{ + /// The resource path defined in API Gateway + #[serde(default)] + pub resource: Option, + /// The url path for the caller + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + #[serde(bound = "", default)] + pub request_context: ApiGatewayWebsocketProxyRequestContext, + #[serde(default)] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify +/// the AWS account and resources invoking the Lambda function. It also includes +/// Cognito identity information for the caller. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayWebsocketProxyRequestContext +where + T1: DeserializeOwned, + T1: Serialize, + T2: DeserializeOwned, + T2: Serialize, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub identity: ApiGatewayRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(bound = "")] + pub authorizer: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + /// The API Gateway rest API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + pub connected_at: i64, + #[serde(default)] + pub connection_id: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub error: Option, + #[serde(default)] + pub event_type: Option, + #[serde(default)] + pub extended_request_id: Option, + #[serde(default)] + pub integration_latency: Option, + #[serde(default)] + pub message_direction: Option, + #[serde(bound = "")] + pub message_id: Option, + #[serde(default)] + pub request_time: Option, + pub request_time_epoch: i64, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub status: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { + #[serde(default)] + pub api_key_id: Option, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub client_cert: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { + #[serde(default)] + pub client_cert_pem: Option, + #[serde(default)] + #[serde(rename = "issuerDN")] + pub issuer_dn: Option, + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + #[serde(rename = "subjectDN")] + pub subject_dn: Option, + pub validity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity { + #[serde(default)] + pub not_after: Option, + #[serde(default)] + pub not_before: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthentication { + #[serde(default)] + pub client_cert: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { + #[serde(default)] + pub client_cert_pem: Option, + #[serde(default)] + #[serde(rename = "issuerDN")] + pub issuer_dn: Option, + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + #[serde(rename = "subjectDN")] + pub subject_dn: Option, + pub validity: ApiGatewayV2httpRequestContextAuthenticationClientCertValidity, +} + +/// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { + #[serde(default)] + pub not_after: Option, + #[serde(default)] + pub not_before: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { + #[serde(default)] + pub path: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + pub identity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV1Request { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, + #[serde(default)] + pub identity_source: Option, + #[serde(default)] + pub authorization_token: Option, + #[serde(default)] + pub resource: Option, + #[serde(default)] + pub path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub request_context: ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV2Request { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub route_arn: Option, + pub identity_source: Vec, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + #[serde(default)] + pub cookies: Vec, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + pub request_context: ApiGatewayV2httpRequestContext, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, +} + +/// `ApiGatewayCustomAuthorizerContext` represents the expected format of an API Gateway custom authorizer response. +/// Deprecated. Code should be updated to use the Authorizer map from APIGatewayRequestIdentity. Ex: Authorizer["principalId"] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerContext { + pub principal_id: Option, + pub string_key: Option, + pub num_key: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub bool_key: bool, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { + #[serde(default)] + pub path: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub identity: Option, + #[serde(default)] + pub resource_path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +/// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequest { + #[serde(default)] + pub type_: Option, + #[serde(default)] + pub authorization_token: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, + #[serde(default)] + pub resource: Option, + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub request_context: ApiGatewayCustomAuthorizerRequestTypeRequestContext, +} + +/// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub principal_id: Option, + pub policy_document: ApiGatewayCustomAuthorizerPolicy, + #[serde(bound = "", default)] + pub context: T1, + pub usage_identifier_key: Option, +} + +/// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerSimpleResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + pub is_authorized: bool, + #[serde(bound = "", default)] + pub context: T1, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerIamPolicyResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub principal_id: Option, + pub policy_document: ApiGatewayCustomAuthorizerPolicy, + #[serde(bound = "", default)] + pub context: T1, +} + +/// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerPolicy { + #[serde(default)] + #[serde(rename = "Version")] + pub version: Option, + #[serde(rename = "Statement")] + pub statement: Vec, +} + +/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IamPolicyStatement { + #[serde(rename = "Action")] + pub action: Vec, + #[serde(default)] + #[serde(rename = "Effect")] + pub effect: Option, + #[serde(rename = "Resource")] + pub resource: Vec, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request_type_request() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-request-type-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request_type_request_websocket() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-websocket-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request() { + let data = include_bytes!("../../fixtures/example-apigw-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_response() { + let data = include_bytes!("../../fixtures/example-apigw-response.json"); + let parsed: ApiGatewayProxyResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_restapi_openapi_request() { + let data = include_bytes!("../../fixtures/example-apigw-restapi-openapi-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_iam() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-iam.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_jwt_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-jwt-authorizer.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_lambda_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-lambda-authorizer.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_no_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-no-authorizer.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request.json"); + let parsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_console_test_request() { + let data = include_bytes!("../../fixtures/example-apigw-console-test-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request_without_method() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request-without-method.json"); + let parsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v1_request() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v1-request.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v2_request() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request.json"); + let parsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v2_request_without_cookies() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json"); + let parsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs new file mode 100644 index 00000000..0ef67b7b --- /dev/null +++ b/lambda-events/src/event/appsync/mod.rs @@ -0,0 +1,165 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use map[string]string, json.RawMessage, interface{}, etc.. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncResolverTemplate +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub version: Option, + pub operation: AppSyncOperation, + #[serde(bound = "")] + pub payload: Option, +} + +/// `AppSyncIamIdentity` contains information about the caller authed via IAM. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncIamIdentity { + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub cognito_identity_auth_provider: Option, + #[serde(default)] + pub cognito_identity_auth_type: Option, + #[serde(default)] + pub cognito_identity_pool_id: Option, + #[serde(default)] + pub cognito_identity_id: Option, + pub source_ip: Vec, + #[serde(default)] + pub username: Option, + #[serde(default)] + pub user_arn: Option, +} + +/// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncCognitoIdentity +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub sub: Option, + #[serde(default)] + pub issuer: Option, + #[serde(default)] + pub username: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub claims: HashMap, + pub source_ip: Vec, + #[serde(default)] + pub default_auth_strategy: Option, +} + +pub type AppSyncOperation = String; + +/// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerRequest { + #[serde(default)] + pub authorization_token: Option, + pub request_context: AppSyncLambdaAuthorizerRequestContext, +} + +/// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered +/// this authorization request. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerRequestContext +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub query_string: Option, + #[serde(default)] + pub operation_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub variables: HashMap, +} + +/// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + pub is_authorized: bool, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub resolver_context: HashMap, + pub denied_fields: Option>, + pub ttl_override: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_identity_cognito() { + let data = include_bytes!("../../fixtures/example-appsync-identity-cognito.json"); + let parsed: AppSyncCognitoIdentity = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncCognitoIdentity = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_identity_iam() { + let data = include_bytes!("../../fixtures/example-appsync-identity-iam.json"); + let parsed: AppSyncIamIdentity = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncIamIdentity = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_lambda_auth_request() { + let data = include_bytes!("../../fixtures/example-appsync-lambda-auth-request.json"); + let parsed: AppSyncLambdaAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncLambdaAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_lambda_auth_response() { + let data = include_bytes!("../../fixtures/example-appsync-lambda-auth-response.json"); + let parsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs new file mode 100644 index 00000000..ce0128c2 --- /dev/null +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -0,0 +1,111 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoScalingEvent +where + T1: DeserializeOwned, + T1: Serialize, +{ + /// The version of event data + #[serde(default)] + pub version: Option, + /// The unique ID of the event + #[serde(default)] + pub id: Option, + /// Details about event type + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source of the event + #[serde(default)] + pub source: Option, + /// AccountId + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Event timestamp + pub time: DateTime, + /// Region of event + #[serde(default)] + pub region: Option, + /// Information about resources impacted by event + pub resources: Vec, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub detail: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_launch_successful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-launch-successful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_launch_unsuccessful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-launch-unsuccessful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_lifecycle_action() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-lifecycle-action.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_action() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-action.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_successful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-successful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_unsuccessful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-unsuccessful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs new file mode 100644 index 00000000..ef57c6f9 --- /dev/null +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -0,0 +1,52 @@ +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEvent { + #[serde(rename = "Sender")] + pub sender: ChimeBotEventSender, + #[serde(rename = "Discussion")] + pub discussion: ChimeBotEventDiscussion, + #[serde(default)] + #[serde(rename = "EventType")] + pub event_type: Option, + #[serde(rename = "InboundHttpsEndpoint")] + pub inbound_https_endpoint: Option, + #[serde(rename = "EventTimestamp")] + pub event_timestamp: DateTime, + #[serde(rename = "Message")] + pub message: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventSender { + #[serde(default)] + #[serde(rename = "SenderId")] + pub sender_id: Option, + #[serde(default)] + #[serde(rename = "SenderIdType")] + pub sender_id_type: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventDiscussion { + #[serde(default)] + #[serde(rename = "DiscussionId")] + pub discussion_id: Option, + #[serde(default)] + #[serde(rename = "DiscussionType")] + pub discussion_type: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventInboundHttpsEndpoint { + #[serde(default)] + #[serde(rename = "EndpointType")] + pub endpoint_type: Option, + #[serde(default)] + #[serde(rename = "Url")] + pub url: Option, +} diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs new file mode 100644 index 00000000..f0e61dda --- /dev/null +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -0,0 +1,61 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientVpnConnectionHandlerRequest { + #[serde(default)] + #[serde(rename = "connection-id")] + pub connection_id: Option, + #[serde(default)] + #[serde(rename = "endpoint-id")] + pub endpoint_id: Option, + #[serde(default)] + #[serde(rename = "common-name")] + pub common_name: Option, + #[serde(default)] + pub username: Option, + #[serde(default)] + #[serde(rename = "platform")] + pub os_platform: Option, + #[serde(default)] + #[serde(rename = "platform-version")] + pub os_platform_version: Option, + #[serde(default)] + #[serde(rename = "public-ip")] + pub public_ip: Option, + #[serde(default)] + #[serde(rename = "client-openvpn-version")] + pub client_open_vpn_version: Option, + #[serde(default)] + #[serde(rename = "schema-version")] + pub schema_version: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientVpnConnectionHandlerResponse { + pub allow: bool, + #[serde(default)] + #[serde(rename = "error-msg-on-failed-posture-compliance")] + pub error_msg_on_failed_posture_compliance: Option, + #[serde(rename = "posture-compliance-statuses")] + pub posture_compliance_statuses: Vec, + #[serde(default)] + #[serde(rename = "schema-version")] + pub schema_version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "clientvpn")] + fn example_clientvpn_connectionhandler_request() { + let data = include_bytes!("../../fixtures/example-clientvpn-connectionhandler-request.json"); + let parsed: ClientVpnConnectionHandlerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ClientVpnConnectionHandlerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs new file mode 100644 index 00000000..aefa7f4a --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -0,0 +1,49 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; +use serde_json::Value; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AWSAPICall { + pub event_version: String, + pub user_identity: UserIdentity, + pub event_time: String, + pub event_source: String, + pub event_name: String, + pub aws_region: String, + #[serde(rename = "sourceIPAddress")] + pub source_ipaddress: String, + pub user_agent: String, + pub request_parameters: I, + pub response_elements: Option, + #[serde(default)] + pub additional_event_data: Option, + #[serde(rename = "requestID")] + pub request_id: String, + #[serde(rename = "eventID")] + pub event_id: String, + pub event_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, + pub session_context: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContext { + pub attributes: Attributes, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Attributes { + pub mfa_authenticated: String, + pub creation_date: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs new file mode 100644 index 00000000..0dd2b540 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -0,0 +1,35 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StateChangeNotification { + pub instance_group_id: String, + pub region: String, + pub application: String, + pub deployment_id: String, + pub state: String, + pub deployment_group: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentStateChangeNotification { + pub instance_id: String, + pub region: String, + pub state: String, + pub application: String, + pub deployment_id: String, + pub instance_group_id: String, + pub deployment_group: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChangeNotification { + pub pipeline: String, + pub version: String, + pub state: String, + #[serde(rename = "execution-id")] + pub execution_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs new file mode 100644 index 00000000..86a1de15 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -0,0 +1,47 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PipelineExecutionStateChange { + pub pipeline: String, + pub version: String, + pub state: String, + #[serde(rename = "execution-id")] + pub execution_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StageExecutionStateChange { + pub pipeline: String, + pub version: String, + #[serde(rename = "execution-id")] + pub execution_id: String, + pub stage: String, + pub state: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionExecutionStateChange { + pub pipeline: String, + pub version: i64, + #[serde(rename = "execution-id")] + pub execution_id: String, + pub stage: String, + pub action: String, + pub state: String, + pub region: String, + #[serde(rename = "type")] + pub type_field: ActionExecutionStateChangeType, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionExecutionStateChangeType { + pub owner: String, + pub category: String, + pub provider: String, + pub version: i64, +} diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs new file mode 100644 index 00000000..c4e26b4e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -0,0 +1,10 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChange { + #[serde(rename = "instance-id")] + pub instance_id: String, + pub state: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs new file mode 100644 index 00000000..942e5984 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -0,0 +1,50 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoScalingPolicyStateChange { + pub resource_id: String, + pub cluster_id: String, + pub state: String, + pub message: String, + pub scaling_resource_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClusterStateChange { + pub severity: String, + pub state_change_reason: String, + pub name: String, + pub cluster_id: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceGroupStateChange { + pub market: String, + pub severity: String, + pub requested_instance_count: String, + pub instance_type: String, + pub instance_group_type: String, + pub instance_group_id: String, + pub cluster_id: String, + pub running_instance_count: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StepStatusChange { + pub severity: String, + pub action_on_failure: String, + pub step_id: String, + pub name: String, + pub cluster_id: String, + pub state: String, + pub message: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs new file mode 100644 index 00000000..1369a793 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -0,0 +1,119 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +use crate::custom_serde::deserialize_nullish_boolean; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingSearching { + pub tickets: Vec, + pub estimated_wait_millis: String, + pub r#type: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Ticket { + pub ticket_id: String, + pub start_time: String, + pub players: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Player { + pub player_id: String, + pub team: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub accepted: bool, + pub player_session_id: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GameSessionInfo { + pub players: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PotentialMatchCreated { + pub tickets: Vec, + pub acceptance_timeout: i64, + pub rule_evaluation_metrics: Vec, + pub acceptance_required: bool, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RuleEvaluationMetric { + pub rule_name: String, + pub passed_count: i64, + pub failed_count: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptMatch { + pub tickets: Vec, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptMatchCompleted { + pub tickets: Vec, + pub acceptance: String, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingSucceeded { + pub tickets: Vec, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingTimedOut { + pub reason: String, + pub tickets: Vec, + pub rule_evaluation_metrics: Vec, + pub r#type: String, + pub message: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingCancelled { + pub reason: String, + pub tickets: Vec, + pub rule_evaluation_metrics: Vec, + pub r#type: String, + pub message: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingFailed { + pub tickets: Vec, + pub custom_event_data: String, + pub r#type: String, + pub reason: String, + pub message: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs new file mode 100644 index 00000000..f752f53e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -0,0 +1,89 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobRunStateChange { + pub job_name: String, + pub severity: String, + pub state: String, + pub job_run_id: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerStarted { + pub account_id: String, + pub crawler_name: String, + pub start_time: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerSucceeded { + pub tables_created: String, + pub warning_message: String, + pub partitions_updated: String, + pub tables_updated: String, + pub message: String, + pub partitions_deleted: String, + pub account_id: String, + #[serde(rename = "runningTime (sec)")] + pub running_time_sec: String, + pub tables_deleted: String, + pub crawler_name: String, + pub completion_date: String, + pub state: String, + pub partitions_created: String, + pub cloud_watch_log_link: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerFailed { + pub crawler_name: String, + pub error_message: String, + pub account_id: String, + pub cloud_watch_log_link: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobRunStatus { + pub job_name: String, + pub severity: String, + pub notification_condition: NotificationCondition, + pub state: String, + pub job_run_id: String, + pub message: String, + pub started_on: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotificationCondition { + #[serde(rename = "NotifyDelayAfter")] + pub notify_delay_after: f64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataCatalogTableStateChange { + pub database_name: String, + pub changed_partitions: Vec, + pub type_of_change: String, + pub table_name: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataCatalogDatabaseStateChange { + pub database_name: String, + pub type_of_change: String, + pub changed_tables: Vec, +} diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs new file mode 100644 index 00000000..3c8acbf9 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -0,0 +1,31 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Event { + pub event_arn: String, + pub service: String, + pub event_type_code: String, + pub event_type_category: String, + pub start_time: String, + pub end_time: String, + pub event_description: Vec, + pub affected_entities: Option>, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EventDescription { + pub language: String, + pub latest_description: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Entity { + pub entity_value: String, + pub tags: HashMap, +} diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs new file mode 100644 index 00000000..ac6f8926 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -0,0 +1,9 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CMKEvent { + #[serde(rename = "key-id")] + pub key_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs new file mode 100644 index 00000000..4ce78b71 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -0,0 +1,223 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Alert { + #[serde(rename = "notification-type")] + pub notification_type: String, + pub name: String, + pub tags: Vec, + pub url: String, + #[serde(rename = "alert-arn")] + pub alert_arn: String, + #[serde(rename = "risk-score")] + pub risk_score: i64, + pub trigger: Trigger, + #[serde(rename = "created-at")] + pub created_at: String, + pub actor: String, + pub summary: T, +} + +pub type BucketScanAlert = Alert; +pub type BucketWritableAlert = Alert; +pub type BucketContainsHighRiskObjectAlert = Alert; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Trigger { + #[serde(rename = "rule-arn")] + pub rule_arn: String, + #[serde(rename = "alert-type")] + pub alert_type: String, + #[serde(rename = "created-at")] + pub created_at: String, + pub description: String, + pub risk: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketScanSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "IP")] + pub ip: Ip, + #[serde(rename = "Time Range")] + pub time_range: Vec, + #[serde(rename = "Source ARN")] + pub source_arn: String, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "Location")] + pub location: Location, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "Events")] + pub events: HashMap, + pub recipient_account_id: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Ip { + #[serde(rename = "34.199.185.34")] + pub n34_199_185_34: i64, + #[serde(rename = "34.205.153.2")] + pub n34_205_153_2: i64, + #[serde(rename = "72.21.196.70")] + pub n72_21_196_70: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeRange { + pub count: i64, + pub start: String, + pub end: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Location { + #[serde(rename = "us-east-1")] + pub us_east_1: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionInfo { + pub count: i64, + #[serde(rename = "ISP")] + pub isp: HashMap, + pub error_code: Option>, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketWritableSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "Bucket")] + pub bucket: Bucket, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "ACL")] + pub acl: Acl, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "Timestamps")] + pub timestamps: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Bucket { + #[serde(rename = "secret-bucket-name")] + pub secret_bucket_name: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Acl { + #[serde(rename = "secret-bucket-name")] + pub secret_bucket_name: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SecretBucketName { + #[serde(rename = "Owner")] + pub owner: Owner, + #[serde(rename = "Grants")] + pub grants: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Owner { + #[serde(rename = "DisplayName")] + pub display_name: String, + #[serde(rename = "ID")] + pub id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Grant { + #[serde(rename = "Grantee")] + pub grantee: Grantee, + #[serde(rename = "Permission")] + pub permission: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Grantee { + pub r#type: String, + #[serde(rename = "URI")] + pub uri: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketContainsHighRiskObjectSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "Object")] + pub object: HashMap, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "Themes")] + pub themes: HashMap, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "DLP risk")] + pub dlp_risk: HashMap, + #[serde(rename = "Owner")] + pub owner: HashMap, + #[serde(rename = "Timestamps")] + pub timestamps: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlertUpdated { + #[serde(rename = "notification-type")] + pub notification_type: String, + pub name: String, + pub tags: Vec, + pub url: String, + #[serde(rename = "alert-arn")] + pub alert_arn: String, + #[serde(rename = "risk-score")] + pub risk_score: i64, + #[serde(rename = "created-at")] + pub created_at: String, + pub actor: String, + pub trigger: UpdatedTrigger, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdatedTrigger { + #[serde(rename = "alert-type")] + pub alert_type: String, + pub features: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FeatureInfo { + pub name: String, + pub description: String, + pub narrative: String, + pub anomalous: bool, + pub multiplier: f64, + #[serde(rename = "excession_times")] + pub excession_times: Vec, + pub risk: i64, +} diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs new file mode 100644 index 00000000..3c39e3d3 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -0,0 +1,50 @@ +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; + +pub mod cloudtrail; +pub mod codedeploy; +pub mod codepipeline; +pub mod ec2; +pub mod emr; +pub mod gamelift; +pub mod glue; +pub mod health; +pub mod kms; +pub mod macie; +pub mod opsworks; +pub mod signin; +pub mod sms; +pub mod ssm; +pub mod tag; +pub mod trustedadvisor; + +/// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. +/// For examples of events that come via CloudWatch Events, see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchEvent +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + pub time: DateTime, + #[serde(default)] + pub region: Option, + pub resources: Vec, + #[serde(bound = "")] + pub detail: Option, +} diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs new file mode 100644 index 00000000..c75f1b5e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -0,0 +1,55 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChange { + #[serde(rename = "initiated_by")] + pub initiated_by: String, + pub hostname: String, + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "layer-ids")] + pub layer_ids: Vec, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "ec2-instance-id")] + pub ec2_instance_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandStateChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + pub r#type: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentStateChange { + pub duration: i64, + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "instance-ids")] + pub instance_ids: Vec, + #[serde(rename = "deployment-id")] + pub deployment_id: String, + pub command: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Alert { + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + pub r#type: String, + pub message: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs new file mode 100644 index 00000000..4d256e3b --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -0,0 +1,50 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; +use serde_json::Value; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SignIn { + pub event_version: String, + pub user_identity: UserIdentity, + pub event_time: String, + pub event_source: String, + pub event_name: String, + pub aws_region: String, + #[serde(rename = "sourceIPAddress")] + pub source_ipaddress: String, + pub user_agent: String, + pub request_parameters: Value, + pub response_elements: ResponseElements, + pub additional_event_data: AdditionalEventData, + #[serde(rename = "eventID")] + pub event_id: String, + pub event_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResponseElements { + #[serde(rename = "ConsoleLogin")] + pub console_login: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalEventData { + #[serde(rename = "LoginTo")] + pub login_to: String, + #[serde(rename = "MobileVersion")] + pub mobile_version: String, + #[serde(rename = "MFAUsed")] + pub mfaused: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs new file mode 100644 index 00000000..33092b76 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -0,0 +1,15 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobStateChange { + pub state: String, + #[serde(rename = "replication-run-id")] + pub replication_run_id: String, + #[serde(rename = "replication-job-id")] + pub replication_job_id: String, + #[serde(rename = "ami-id")] + pub ami_id: Option, + pub version: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs new file mode 100644 index 00000000..a826ed07 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -0,0 +1,240 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2AutomationStepStatusChange { + #[serde(rename = "ExecutionId")] + pub execution_id: String, + #[serde(rename = "Definition")] + pub definition: String, + #[serde(rename = "DefinitionVersion")] + pub definition_version: f64, + #[serde(rename = "Status")] + pub status: String, + #[serde(rename = "EndTime")] + pub end_time: String, + #[serde(rename = "StartTime")] + pub start_time: String, + #[serde(rename = "Time")] + pub time: f64, + #[serde(rename = "StepName")] + pub step_name: String, + #[serde(rename = "Action")] + pub action: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2AutomationExecutionStatusChange { + #[serde(rename = "ExecutionId")] + pub execution_id: String, + #[serde(rename = "Definition")] + pub definition: String, + #[serde(rename = "DefinitionVersion")] + pub definition_version: f64, + #[serde(rename = "Status")] + pub status: String, + #[serde(rename = "StartTime")] + pub start_time: String, + #[serde(rename = "EndTime")] + pub end_time: String, + #[serde(rename = "Time")] + pub time: f64, + #[serde(rename = "ExecutedBy")] + pub executed_by: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StateChange { + pub state: String, + pub at_time: String, + pub next_transition_time: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigurationComplianceStateChange { + #[serde(rename = "last-runtime")] + pub last_runtime: Option, + #[serde(rename = "compliance-status")] + pub compliance_status: String, + #[serde(rename = "resource-type")] + pub resource_type: String, + #[serde(rename = "resource-id")] + pub resource_id: String, + #[serde(rename = "compliance-type")] + pub compliance_type: String, + #[serde(rename = "patch-baseline-id")] + pub patch_baseline_id: Option, + pub serverity: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTargetRegistration { + #[serde(rename = "window-target-id")] + pub window_target_id: String, + #[serde(rename = "window-id")] + pub window_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowExecutionStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTaskExecutionStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "task-execution-id")] + pub task_execution_id: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTaskTargetInvocationStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + #[serde(rename = "task-execution-id")] + pub task_execution_id: String, + #[serde(rename = "window-target-id")] + pub window_target_id: String, + pub status: String, + #[serde(rename = "owner-information")] + pub owner_information: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowStateChange { + #[serde(rename = "window-id")] + pub window_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ParameterStoreStateChange { + pub operation: String, + pub name: String, + pub r#type: String, + pub description: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2CommandStatusChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "expire-after")] + pub expire_after: String, + pub parameters: HashMap, + #[serde(rename = "requested-date-time")] + pub requested_date_time: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2CommandInvocationStatusChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "requested-date-time")] + pub requested_date_time: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2StateManagerAssociationStateChange { + #[serde(rename = "association-id")] + pub association_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "association-version")] + pub association_version: String, + #[serde(rename = "document-version")] + pub document_version: String, + pub targets: String, + #[serde(rename = "creation-date")] + pub creation_date: String, + #[serde(rename = "last-successful-execution-date")] + pub last_successful_execution_date: String, + #[serde(rename = "last-execution-date")] + pub last_execution_date: String, + #[serde(rename = "last-updated-date")] + pub last_updated_date: String, + pub status: String, + #[serde(rename = "association-status-aggregated-count")] + pub association_status_aggregated_count: String, + #[serde(rename = "schedule-expression")] + pub schedule_expression: String, + #[serde(rename = "association-cwe-version")] + pub association_cwe_version: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2StateManagerInstanceAssociationStateChange { + #[serde(rename = "association-id")] + pub association_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "document-version")] + pub document_version: String, + pub targets: String, + #[serde(rename = "creation-date")] + pub creation_date: String, + #[serde(rename = "last-successful-execution-date")] + pub last_successful_execution_date: String, + #[serde(rename = "last-execution-date")] + pub last_execution_date: String, + pub status: String, + #[serde(rename = "detailed-status")] + pub detailed_status: String, + #[serde(rename = "error-code")] + pub error_code: String, + #[serde(rename = "execution-summary")] + pub execution_summary: String, + #[serde(rename = "output-url")] + pub output_url: String, + #[serde(rename = "instance-association-cwe-version")] + pub instance_association_cwe_version: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs new file mode 100644 index 00000000..573a99ea --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -0,0 +1,16 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TagChangeOnResource { + #[serde(rename = "changed-tag-keys")] + pub changed_tag_keys: Vec, + pub service: String, + #[serde(rename = "resource-type")] + pub resource_type: String, + pub version: i64, + pub tags: HashMap, +} diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs new file mode 100644 index 00000000..ce6cf79f --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CheckItemRefreshNotification { + #[serde(rename = "check-name")] + pub check_name: String, + #[serde(rename = "check-item-detail")] + pub check_item_detail: HashMap, + pub status: String, + #[serde(rename = "resource_id")] + pub resource_id: String, + pub uuid: String, +} diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs new file mode 100644 index 00000000..e8371c8f --- /dev/null +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -0,0 +1,157 @@ +use serde::{ + de::{Error, MapAccess, Visitor}, + ser::{Error as SeError, SerializeStruct}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, io::BufReader}; + +/// `LogsEvent` represents the raw event sent by CloudWatch +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct LogsEvent { + /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer + #[serde(rename = "awslogs")] + pub aws_logs: AwsLogs, +} + +/// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AwsLogs { + /// `data` is the log data after is decompressed + pub data: LogData, +} + +/// `LogData` represents the logs group event information +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LogData { + /// Owner of the log event + pub owner: String, + /// Log group where the event was published + pub log_group: String, + /// Log stream where the event was published + pub log_stream: String, + /// Filters applied to the event + pub subscription_filters: Vec, + /// Type of event + pub message_type: String, + /// Entries in the log batch + pub log_events: Vec, +} + +/// `LogEntry` represents a log entry from cloudwatch logs +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct LogEntry { + /// Unique id for the entry + pub id: String, + /// Time when the event was published + pub timestamp: i64, + /// Message published in the application log + pub message: String, +} + +impl<'de> Deserialize<'de> for AwsLogs { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AwsLogsVisitor; + + impl<'de> Visitor<'de> for AwsLogsVisitor { + type Value = AwsLogs; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a base64 gzipped string") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut data = None; + while let Some(key) = map.next_key()? { + match key { + "data" => { + let bytes = map + .next_value::() + .and_then(|string| base64::decode(string).map_err(Error::custom))?; + + let bytes = flate2::read::GzDecoder::new(&bytes[..]); + let mut de = serde_json::Deserializer::from_reader(BufReader::new(bytes)); + data = Some(LogData::deserialize(&mut de).map_err(Error::custom)?); + } + _ => return Err(Error::unknown_field(key, FIELDS)), + } + } + + let data = data.ok_or_else(|| Error::missing_field("data"))?; + Ok(AwsLogs { data }) + } + } + + const FIELDS: &[&str] = &["data"]; + deserializer.deserialize_struct("AwsLogs", FIELDS, AwsLogsVisitor) + } +} + +impl Serialize for AwsLogs { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let base = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD_NO_PAD); + let mut gzip = flate2::write::GzEncoder::new(base, flate2::Compression::default()); + + serde_json::to_writer(&mut gzip, &self.data).map_err(SeError::custom)?; + let mut base = gzip.finish().map_err(SeError::custom)?; + let data = base.finish().map_err(SeError::custom)?; + let string = std::str::from_utf8(data.as_slice()).map_err(SeError::custom)?; + + let mut state = serializer.serialize_struct("AwsLogs", 1)?; + state.serialize_field("data", &string)?; + state.end() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "cloudwatch_logs")] + fn test_deserialize_example() { + let json = r#"{ + "awslogs": { + "data": "H4sIAFETomIAA12Ry27bMBBF9/4KQuiyqsQ36Z2DqEGBGC0sdRUHAS0NExV6uCJVNw3y76Fkx03CFTH3cubwztMChRO14Jy5h+JxD9ESRZerYnW3zvJ8dZVFn4+W/tDBMImYUMaFVDrF5FVs+vuroR/3k56Yg0sa0+4qk0D50MddX8Ev98aa+wFMO3lJinWS0gTT5ObT9arI8uJWM2uUkMCpZIxiorGRtsQMiOXCgHxt5MadK4d67+u++1o3HgYXWt7M4my4nhmOw+7Kph+rg/HlQwBwM1M0W2//c2V/oPPvmzydb7OpriZqygQhFItUa6GlUkymgrNUS5EKpQhRfMpGCEzC/xgWjCpNOBMn8nM3X4fcvWmn2DDnhGNFWXiffvCdtjON3mQ/vm8KtIHfY3j6rVoiEdaxsxZizLSJd4KRWGFrYwIKqBSVMtZu/eU4mCmoJWLii2KodVt/UTcNVOiNJEMdbf0a2n54RHn9DwKYJmh9EYrmLzoJPx2EwfJY33bRmfb5mOjiefECiB5LsVgCAAA=" + } +}"#; + let event: LogsEvent = serde_json::from_str(json).expect("failed to deserialize"); + let data = event.aws_logs.data.clone(); + assert_eq!("DATA_MESSAGE", data.message_type); + assert_eq!("123456789012", data.owner); + assert_eq!("/aws/lambda/echo-nodejs", data.log_group); + assert_eq!("2019/03/13/[$LATEST]94fa867e5374431291a7fc14e2f56ae7", data.log_stream); + assert_eq!(1, data.subscription_filters.len()); + assert_eq!("LambdaStream_cloudwatchlogs-node", data.subscription_filters[0]); + assert_eq!(1, data.log_events.len()); + assert_eq!( + "34622316099697884706540976068822859012661220141643892546", + data.log_events[0].id + ); + assert_eq!(1552518348220, data.log_events[0].timestamp); + assert_eq!("REPORT RequestId: 6234bffe-149a-b642-81ff-2e8e376d8aff\tDuration: 46.84 ms\tBilled Duration: 47 ms \tMemory Size: 192 MB\tMax Memory Used: 72 MB\t\n", data.log_events[0].message); + + let new_json = serde_json::to_string_pretty(&event).unwrap(); + let new_event: LogsEvent = serde_json::from_str(&new_json).expect("failed to deserialize"); + assert_eq!(new_event, event); + } + + #[test] + #[cfg(feature = "cloudwatch_logs")] + fn example_cloudwatch_logs_event() { + let data = include_bytes!("../../fixtures/example-cloudwatch_logs-event.json"); + let parsed: LogsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LogsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs new file mode 100644 index 00000000..8b2e617f --- /dev/null +++ b/lambda-events/src/event/code_commit/mod.rs @@ -0,0 +1,82 @@ +use chrono::{DateTime, Utc}; + +use crate::custom_serde::deserialize_nullish_boolean; + +/// `CodeCommitEvent` represents a CodeCommit event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +pub type CodeCommitEventTime = DateTime; + +/// `CodeCommitRecord` represents a CodeCommit record +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitRecord { + #[serde(default)] + pub event_id: Option, + #[serde(default)] + pub event_version: Option, + pub event_time: CodeCommitEventTime, + #[serde(default)] + pub event_trigger_name: Option, + pub event_part_number: u64, + #[serde(rename = "codecommit")] + pub code_commit: CodeCommitCodeCommit, + #[serde(default)] + pub event_name: Option, + /// nolint: stylecheck + #[serde(default)] + pub event_trigger_config_id: Option, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + #[serde(rename = "userIdentityARN")] + pub user_identity_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, + pub event_total_parts: u64, + pub custom_data: Option, +} + +/// `CodeCommitCodeCommit` represents a CodeCommit object in a record +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitCodeCommit { + pub references: Vec, +} + +/// `CodeCommitReference` represents a Reference object in a CodeCommit object +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitReference { + #[serde(default)] + pub commit: Option, + #[serde(default)] + pub ref_: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub created: bool, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "code_commit")] + fn example_code_commit_event() { + let data = include_bytes!("../../fixtures/example-code_commit-event.json"); + let parsed: CodeCommitEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeCommitEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs new file mode 100644 index 00000000..2c6ddf39 --- /dev/null +++ b/lambda-events/src/event/codebuild/mod.rs @@ -0,0 +1,237 @@ +use crate::custom_serde::*; +use crate::encodings::{MinuteDuration, SecondDuration}; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; + +pub type CodeBuildPhaseStatus = String; + +pub type CodeBuildPhaseType = String; + +/// `CodeBuildEvent` is documented at: +/// https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEvent { + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// DetailType informs the schema of the Detail field. For build state-change + /// events, the value will be CodeBuildStateChangeDetailType. For phase-change + /// events, it will be CodeBuildPhaseChangeDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodeBuildEventSource. + #[serde(default)] + pub source: Option, + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// Resources is a list of ARNs of CodeBuild builds that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a build state-change or + /// build phase-change event. + pub detail: CodeBuildEventDetail, +} + +/// `CodeBuildEventDetail` represents the all details related to the code build event +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEventDetail { + #[serde(rename = "build-status")] + pub build_status: Option, + #[serde(default)] + #[serde(rename = "project-name")] + pub project_name: Option, + #[serde(default)] + #[serde(rename = "build-id")] + pub build_id: Option, + #[serde(rename = "additional-information")] + pub additional_information: CodeBuildEventAdditionalInformation, + #[serde(rename = "current-phase")] + pub current_phase: Option, + #[serde(default)] + #[serde(rename = "current-phase-context")] + pub current_phase_context: Option, + #[serde(default)] + pub version: Option, + #[serde(rename = "completed-phase-status")] + pub completed_phase_status: Option, + #[serde(rename = "completed-phase")] + pub completed_phase: Option, + #[serde(default)] + #[serde(rename = "completed-phase-context")] + pub completed_phase_context: Option, + #[serde(rename = "completed-phase-duration-seconds")] + pub completed_phase_duration: Option, + #[serde(rename = "completed-phase-start")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub completed_phase_start: Option, + #[serde(rename = "completed-phase-end")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub completed_phase_end: Option, +} + +/// `CodeBuildEventAdditionalInformation` represents additional information to the code build event +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEventAdditionalInformation { + pub artifact: CodeBuildArtifact, + pub environment: CodeBuildEnvironment, + #[serde(rename = "timeout-in-minutes")] + pub timeout: MinuteDuration, + #[serde(rename = "build-complete")] + pub build_complete: bool, + #[serde(rename = "build-number")] + pub build_number: Option, + #[serde(default)] + pub initiator: Option, + #[serde(rename = "build-start-time")] + #[serde(with = "codebuild_time::str_time")] + pub build_start_time: CodeBuildTime, + pub source: CodeBuildSource, + #[serde(default)] + #[serde(rename = "source-version")] + pub source_version: Option, + pub logs: CodeBuildLogs, + pub phases: Vec, +} + +/// `CodeBuildArtifact` represents the artifact provided to build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildArtifact { + #[serde(default)] + #[serde(rename = "md5sum")] + pub md5_sum: Option, + #[serde(default)] + #[serde(rename = "sha256sum")] + pub sha256_sum: Option, + #[serde(default)] + pub location: Option, +} + +/// `CodeBuildEnvironment` represents the environment for a build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEnvironment { + #[serde(default)] + pub image: Option, + #[serde(rename = "privileged-mode")] + pub privileged_mode: bool, + #[serde(default)] + #[serde(rename = "compute-type")] + pub compute_type: Option, + #[serde(default)] + pub type_: Option, + #[serde(rename = "environment-variables")] + pub environment_variables: Vec, +} + +/// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEnvironmentVariable { + /// Name is the name of the environment variable. + #[serde(default)] + pub name: Option, + /// Type is PLAINTEXT or PARAMETER_STORE. + #[serde(default)] + pub type_: Option, + /// Value is the value of the environment variable. + #[serde(default)] + pub value: Option, +} + +/// `CodeBuildSource` represent the code source will be build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildSource { + #[serde(default)] + pub location: Option, + #[serde(default)] + pub type_: Option, +} + +/// `CodeBuildLogs` gives the log details of a code build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildLogs { + #[serde(default)] + #[serde(rename = "group-name")] + pub group_name: Option, + #[serde(default)] + #[serde(rename = "stream-name")] + pub stream_name: Option, + #[serde(default)] + #[serde(rename = "deep-link")] + pub deep_link: Option, +} + +/// `CodeBuildPhase` represents the phase of a build and its details +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildPhase +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(bound = "")] + #[serde(rename = "phase-context")] + pub phase_context: Option>, + #[serde(rename = "start-time")] + #[serde(with = "codebuild_time::str_time")] + pub start_time: CodeBuildTime, + #[serde(rename = "end-time")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub end_time: Option, + #[serde(rename = "duration-in-seconds")] + pub duration: Option, + #[serde(rename = "phase-type")] + pub phase_type: CodeBuildPhaseType, + #[serde(rename = "phase-status")] + pub phase_status: Option, +} + +pub type CodeBuildTime = DateTime; + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codebuild")] + fn example_codebuild_phase_change() { + let data = include_bytes!("../../fixtures/example-codebuild-phase-change.json"); + let parsed: CodeBuildEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeBuildEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codebuild")] + fn example_codebuild_state_change() { + let data = include_bytes!("../../fixtures/example-codebuild-state-change.json"); + let parsed: CodeBuildEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeBuildEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs new file mode 100644 index 00000000..61bfc665 --- /dev/null +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -0,0 +1,92 @@ +use chrono::{DateTime, Utc}; + +pub type CodeDeployDeploymentState = String; + +/// `CodeDeployEvent` is documented at: +/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#acd_event_types +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeDeployEvent { + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// DetailType informs the schema of the Detail field. For deployment state-change + /// events, the value should be equal to CodeDeployDeploymentEventDetailType. + /// For instance state-change events, the value should be equal to + /// CodeDeployInstanceEventDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodeDeployEventSource. + #[serde(default)] + pub source: Option, + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// Resources is a list of ARNs of CodeDeploy applications and deployment + /// groups that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a deployment event. + pub detail: CodeDeployEventDetail, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeDeployEventDetail { + /// InstanceGroupID is the ID of the instance group. + #[serde(default)] + pub instance_group_id: Option, + /// InstanceID is the id of the instance. This field is non-empty only if + /// the DetailType of the complete event is CodeDeployInstanceEventDetailType. + pub instance_id: Option, + /// Region is the AWS region that the event originated from. + #[serde(default)] + pub region: Option, + /// Application is the name of the CodeDeploy application. + #[serde(default)] + pub application: Option, + /// DeploymentID is the id of the deployment. + #[serde(default)] + pub deployment_id: Option, + /// State is the new state of the deployment. + pub state: CodeDeployDeploymentState, + /// DeploymentGroup is the name of the deployment group. + #[serde(default)] + pub deployment_group: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_deployment_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-deployment-event.json"); + let parsed: CodeDeployEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_instance_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-instance-event.json"); + let parsed: CodeDeployEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs new file mode 100644 index 00000000..d4d3477b --- /dev/null +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -0,0 +1,114 @@ +use chrono::{DateTime, Utc}; + +pub type CodePipelineStageState = String; + +pub type CodePipelineState = String; + +pub type CodePipelineActionState = String; + +/// CodePipelineEvent is documented at: +/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#codepipeline_event_type +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineCloudWatchEvent { + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// DetailType informs the schema of the Detail field. For deployment state-change + /// events, the value should be equal to CodePipelineDeploymentEventDetailType. + /// For instance state-change events, the value should be equal to + /// CodePipelineInstanceEventDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodePipelineEventSource. + #[serde(default)] + pub source: Option, + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// Resources is a list of ARNs of CodePipeline applications and deployment + /// groups that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a deployment event. + pub detail: CodePipelineEventDetail, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineEventDetail { + #[serde(default)] + pub pipeline: Option, + /// From live testing this is always int64 not string as documented + pub version: i64, + #[serde(default)] + #[serde(rename = "execution-id")] + pub execution_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub action: Option, + pub state: CodePipelineState, + #[serde(default)] + pub region: Option, + pub type_: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineEventDetailType { + #[serde(default)] + pub owner: Option, + #[serde(default)] + pub category: Option, + #[serde(default)] + pub provider: Option, + /// From published EventBridge schema registry this is always int64 not string as documented + pub version: i64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_action_execution_stage_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-action-execution-stage-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_execution_stage_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-execution-stage-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_execution_state_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-execution-state-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs new file mode 100644 index 00000000..0767b272 --- /dev/null +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -0,0 +1,129 @@ +/// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineJobEvent { + #[serde(rename = "CodePipeline.job")] + pub code_pipeline_job: CodePipelineJob, +} + +/// `CodePipelineJob` represents a job from an AWS CodePipeline event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineJob { + #[serde(default)] + pub id: Option, + #[serde(default)] + pub account_id: Option, + pub data: CodePipelineData, +} + +/// `CodePipelineData` represents a job from an AWS CodePipeline event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineData { + pub action_configuration: CodePipelineActionConfiguration, + pub input_artifacts: Vec, + #[serde(rename = "outputArtifacts")] + pub out_put_artifacts: Vec, + pub artifact_credentials: CodePipelineArtifactCredentials, + #[serde(default)] + pub continuation_token: Option, +} + +/// `CodePipelineActionConfiguration` represents an Action Configuration +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineActionConfiguration { + pub configuration: CodePipelineConfiguration, +} + +/// `CodePipelineConfiguration` represents a configuration for an Action Configuration +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineConfiguration { + #[serde(default)] + #[serde(rename = "FunctionName")] + pub function_name: Option, + #[serde(default)] + #[serde(rename = "UserParameters")] + pub user_parameters: Option, +} + +/// `CodePipelineInputArtifact` represents an input artifact +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineInputArtifact { + pub location: CodePipelineInputLocation, + pub revision: Option, + #[serde(default)] + pub name: Option, +} + +/// `CodePipelineInputLocation` represents a input location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineInputLocation { + pub s3_location: CodePipelineS3Location, + #[serde(default)] + #[serde(rename = "type")] + pub location_type: Option, +} + +/// `CodePipelineS3Location` represents an s3 input location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineS3Location { + #[serde(default)] + pub bucket_name: Option, + #[serde(default)] + pub object_key: Option, +} + +/// `CodePipelineOutputArtifact` represents an output artifact +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineOutputArtifact { + pub location: CodePipelineInputLocation, + pub revision: Option, + #[serde(default)] + pub name: Option, +} + +/// `CodePipelineOutputLocation` represents a output location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineOutputLocation { + pub s3_location: CodePipelineS3Location, + #[serde(default)] + #[serde(rename = "type")] + pub location_type: Option, +} + +/// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineArtifactCredentials { + #[serde(default)] + pub secret_access_key: Option, + #[serde(default)] + pub session_token: Option, + #[serde(default)] + pub access_key_id: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codepipeline_job")] + fn example_codepipeline_job_event() { + let data = include_bytes!("../../fixtures/example-codepipeline_job-event.json"); + let parsed: CodePipelineJobEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineJobEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs new file mode 100644 index 00000000..99bc682b --- /dev/null +++ b/lambda-events/src/event/cognito/mod.rs @@ -0,0 +1,612 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `CognitoEvent` contains data from an event sent from AWS Cognito Sync +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEvent { + #[serde(default)] + pub dataset_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub dataset_records: HashMap, + #[serde(default)] + pub event_type: Option, + #[serde(default)] + pub identity_id: Option, + #[serde(default)] + pub identity_pool_id: Option, + #[serde(default)] + pub region: Option, + pub version: i64, +} + +/// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoDatasetRecord { + #[serde(default)] + pub new_value: Option, + #[serde(default)] + pub old_value: Option, + #[serde(default)] + pub op: Option, +} + +/// `CognitoEventUserPoolsPreSignup` is sent by AWS Cognito User Pools when a user attempts to register +/// (sign up), allowing a Lambda to perform custom validation to accept or deny the registration request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignup { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreSignupRequest, + pub response: CognitoEventUserPoolsPreSignupResponse, +} + +/// `CognitoEventUserPoolsPreAuthentication` is sent by AWS Cognito User Pools when a user submits their information +/// to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreAuthentication { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreAuthenticationRequest, + pub response: CognitoEventUserPoolsPreAuthenticationResponse, +} + +/// `CognitoEventUserPoolsPostConfirmation` is sent by AWS Cognito User Pools after a user is confirmed, +/// allowing the Lambda to send custom messages or add custom logic. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostConfirmation { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPostConfirmationRequest, + pub response: CognitoEventUserPoolsPostConfirmationResponse, +} + +/// `CognitoEventUserPoolsPreTokenGen` is sent by AWS Cognito User Pools when a user attempts to retrieve +/// credentials, allowing a Lambda to perform insert, suppress or override claims +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGen { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreTokenGenRequest, + pub response: CognitoEventUserPoolsPreTokenGenResponse, +} + +/// `CognitoEventUserPoolsPostAuthentication` is sent by AWS Cognito User Pools after a user is authenticated, +/// allowing the Lambda to add custom logic. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostAuthentication { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPostAuthenticationRequest, + pub response: CognitoEventUserPoolsPostAuthenticationResponse, +} + +/// `CognitoEventUserPoolsMigrateUser` is sent by AWS Cognito User Pools when a user does not exist in the +/// user pool at the time of sign-in with a password, or in the forgot-password flow. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUser { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + #[serde(rename = "request")] + pub cognito_event_user_pools_migrate_user_request: CognitoEventUserPoolsMigrateUserRequest, + #[serde(rename = "response")] + pub cognito_event_user_pools_migrate_user_response: CognitoEventUserPoolsMigrateUserResponse, +} + +/// `CognitoEventUserPoolsCallerContext` contains information about the caller +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCallerContext { + #[serde(default)] + #[serde(rename = "awsSdkVersion")] + pub awssdk_version: Option, + #[serde(default)] + pub client_id: Option, +} + +/// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsHeader { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub trigger_source: Option, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub user_pool_id: Option, + pub caller_context: CognitoEventUserPoolsCallerContext, + #[serde(default)] + pub user_name: Option, +} + +/// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignupRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignupResponse { + pub auto_confirm_user: bool, + pub auto_verify_email: bool, + pub auto_verify_phone: bool, +} + +/// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreAuthenticationRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, +} + +/// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CognitoEventUserPoolsPreAuthenticationResponse {} +/// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostConfirmationRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CognitoEventUserPoolsPostConfirmationResponse {} +/// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + pub group_configuration: GroupConfiguration, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenResponse { + pub claims_override_details: Option, +} + +/// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostAuthenticationRequest { + pub new_device_used: bool, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CognitoEventUserPoolsPostAuthenticationResponse {} +/// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUserRequest { + #[serde(default)] + pub password: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUserResponse { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(default)] + pub final_user_status: Option, + #[serde(default)] + pub message_action: Option, + pub desired_delivery_mediums: Vec, + pub force_alias_creation: bool, +} + +/// `ClaimsOverrideDetails` allows lambda to add, suppress or override claims in the token +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClaimsOverrideDetails { + pub group_override_details: GroupConfiguration, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, +} + +/// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GroupConfiguration { + pub groups_to_override: Vec, + pub iam_roles_to_override: Vec, + pub preferred_role: Option, +} + +/// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication +/// process that is underway, along with the corresponding result. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsChallengeResult { + #[serde(default)] + pub challenge_name: Option, + pub challenge_result: bool, + #[serde(default)] + pub challenge_metadata: Option, +} + +/// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + pub session: Vec>, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, +} + +/// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { + #[serde(default)] + pub challenge_name: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub issue_tokens: bool, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub fail_authentication: bool, +} + +/// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, + pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, +} + +/// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(default)] + pub challenge_name: Option, + pub session: Vec>, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub public_challenge_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub private_challenge_parameters: HashMap, + #[serde(default)] + pub challenge_metadata: Option, +} + +/// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, + pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, +} + +/// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallengeRequest +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub private_challenge_parameters: HashMap, + #[serde(bound = "")] + pub challenge_answer: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { + #[serde(default)] + pub answer_correct: bool, +} + +/// `CognitoEventUserPoolsVerifyAuthChallenge` sent by AWS Cognito User Pools to verify if the response from the end user +/// for a custom Auth Challenge is valid or not +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, + pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, +} + +/// `CognitoEventUserPoolsCustomMessage` is sent by AWS Cognito User Pools before a verification or MFA message is sent, +/// allowing a user to customize the message dynamically. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessage { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsCustomMessageRequest, + pub response: CognitoEventUserPoolsCustomMessageResponse, +} + +/// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessageRequest +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub user_attributes: HashMap, + #[serde(default)] + pub code_parameter: Option, + #[serde(default)] + pub username_parameter: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessageResponse { + #[serde(default)] + pub sms_message: Option, + #[serde(default)] + pub email_message: Option, + #[serde(default)] + pub email_subject: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event() { + let data = include_bytes!("../../fixtures/example-cognito-event.json"); + let parsed: CognitoEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_create_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-create-auth-challenge.json"); + let parsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_custommessage() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-custommessage.json"); + let parsed: CognitoEventUserPoolsCustomMessage = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsCustomMessage = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_define_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-define-auth-challenge.json"); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_define_auth_challenge_optional_response_fields() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json" + ); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(!parsed.response.fail_authentication); + assert!(!parsed.response.issue_tokens); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_migrateuser() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-migrateuser.json"); + let parsed: CognitoEventUserPoolsMigrateUser = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsMigrateUser = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_postauthentication() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-postauthentication.json"); + let parsed: CognitoEventUserPoolsPostAuthentication = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPostAuthentication = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_postconfirmation() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-postconfirmation.json"); + let parsed: CognitoEventUserPoolsPostConfirmation = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPostConfirmation = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_preauthentication() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-preauthentication.json"); + let parsed: CognitoEventUserPoolsPreAuthentication = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreAuthentication = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_presignup() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-presignup.json"); + let parsed: CognitoEventUserPoolsPreSignup = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreSignup = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen_incoming() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-incoming.json"); + let parsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen.json"); + let parsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-verify-auth-challenge.json"); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge_optional_answer_correct() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json" + ); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(!parsed.response.answer_correct); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs new file mode 100644 index 00000000..bb5d0c11 --- /dev/null +++ b/lambda-events/src/event/config/mod.rs @@ -0,0 +1,52 @@ +/// `ConfigEvent` contains data from an event sent from AWS Config +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigEvent { + /// The ID of the AWS account that owns the rule + #[serde(default)] + pub account_id: Option, + /// The ARN that AWS Config assigned to the rule + /// + /// nolint:stylecheck + #[serde(default)] + pub config_rule_arn: Option, + /// nolint:stylecheck + #[serde(default)] + pub config_rule_id: Option, + /// The name that you assigned to the rule that caused AWS Config to publish the event + #[serde(default)] + pub config_rule_name: Option, + /// A boolean value that indicates whether the AWS resource to be evaluated has been removed from the rule's scope + pub event_left_scope: bool, + /// nolint:stylecheck + #[serde(default)] + pub execution_role_arn: Option, + /// If the event is published in response to a resource configuration change, this value contains a JSON configuration item + #[serde(default)] + pub invoking_event: Option, + /// A token that the function must pass to AWS Config with the PutEvaluations call + #[serde(default)] + pub result_token: Option, + /// Key/value pairs that the function processes as part of its evaluation logic + #[serde(default)] + pub rule_parameters: Option, + #[serde(default)] + pub version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "config")] + fn example_config_event() { + let data = include_bytes!("../../fixtures/example-config-event.json"); + let parsed: ConfigEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConfigEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs new file mode 100644 index 00000000..62e86b52 --- /dev/null +++ b/lambda-events/src/event/connect/mod.rs @@ -0,0 +1,116 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +/// `ConnectEvent` contains the data structure for a Connect event. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectEvent { + #[serde(rename = "Details")] + pub details: ConnectDetails, + /// The name of the event. + #[serde(default)] + #[serde(rename = "Name")] + pub name: Option, +} + +/// `ConnectDetails` holds the details of a Connect event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectDetails { + #[serde(rename = "ContactData")] + pub contact_data: ConnectContactData, + /// The parameters that have been set in the Connect instance at the time of the Lambda invocation. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "Parameters")] + pub parameters: HashMap, +} + +/// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectContactData { + /// The custom attributes from Connect that the Lambda function was invoked with. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "Attributes")] + pub attributes: HashMap, + #[serde(default)] + #[serde(rename = "Channel")] + pub channel: Option, + #[serde(default)] + #[serde(rename = "ContactId")] + pub contact_id: Option, + #[serde(rename = "CustomerEndpoint")] + pub customer_endpoint: ConnectEndpoint, + #[serde(default)] + #[serde(rename = "InitialContactId")] + pub initial_contact_id: Option, + /// Either: INBOUND/OUTBOUND/TRANSFER/CALLBACK + #[serde(default)] + #[serde(rename = "InitiationMethod")] + pub initiation_method: Option, + #[serde(default)] + #[serde(rename = "PreviousContactId")] + pub previous_contact_id: Option, + #[serde(rename = "Queue", default)] + pub queue: Option, + #[serde(rename = "SystemEndpoint")] + pub system_endpoint: ConnectEndpoint, + #[serde(default)] + #[serde(rename = "InstanceARN")] + pub instance_arn: Option, +} + +/// `ConnectEndpoint` represents routing information. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectEndpoint { + #[serde(default)] + #[serde(rename = "Address")] + pub address: Option, + #[serde(default)] + #[serde(rename = "Type")] + pub type_: Option, +} + +/// `ConnectQueue` represents a queue object. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectQueue { + #[serde(default)] + #[serde(rename = "Name")] + pub name: Option, + #[serde(default)] + #[serde(rename = "ARN")] + pub arn: Option, +} + +pub type ConnectResponse = HashMap; + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "connect")] + fn example_connect_event() { + let data = include_bytes!("../../fixtures/example-connect-event.json"); + let parsed: ConnectEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConnectEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "connect")] + fn example_connect_event_without_queue() { + let data = include_bytes!("../../fixtures/example-connect-event-without-queue.json"); + let parsed: ConnectEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConnectEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs new file mode 100644 index 00000000..74449fe8 --- /dev/null +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -0,0 +1,191 @@ +use event::serde_dynamo::AttributeValue; +use std::collections::HashMap; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_null_attribute() { + let value = serde_json::json!({ + "NULL": true + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Null(true) => {} + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_string_attribute() { + let value = serde_json::json!({ + "S": "value" + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::S(ref s) => assert_eq!("value", s.as_str()), + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_number_attribute() { + let value = serde_json::json!({ + "N": "123.45" + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::N(ref n) => assert_eq!("123.45", n.as_str()), + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_binary_attribute() { + let value = serde_json::json!({ + "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk" + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::B(ref b) => { + let expected = base64::decode("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk").unwrap(); + assert_eq!(&expected, b) + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_boolean_attribute() { + let value = serde_json::json!({ + "BOOL": true + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Bool(b) => assert_eq!(true, b), + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_string_set_attribute() { + let value = serde_json::json!({ + "SS": ["Giraffe", "Hippo" ,"Zebra"] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Ss(ref s) => { + let expected = vec!["Giraffe", "Hippo", "Zebra"]; + assert_eq!(expected, s.iter().collect::>()); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_number_set_attribute() { + let value = serde_json::json!({ + "NS": ["42.2", "-19", "7.5", "3.14"] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Ns(ref s) => { + let expected = vec!["42.2", "-19", "7.5", "3.14"]; + assert_eq!(expected, s.iter().collect::>()); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_binary_set_attribute() { + let value = serde_json::json!({ + "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Bs(ref s) => { + let expected = vec!["U3Vubnk=", "UmFpbnk=", "U25vd3k="] + .into_iter() + .flat_map(base64::decode) + .collect::>(); + assert_eq!(&expected, s); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_attribute_list_attribute() { + let value = serde_json::json!({ + "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::L(ref s) => { + let expected = vec![ + AttributeValue::S("Cookies".into()), + AttributeValue::S("Coffee".into()), + AttributeValue::N("3.14159".into()), + ]; + assert_eq!(&expected, s); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_attribute_map_attribute() { + let value = serde_json::json!({ + "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}} + }); + + let attr: AttributeValue = serde_json::from_value(value).unwrap(); + match attr { + AttributeValue::M(s) => { + let mut expected = HashMap::new(); + expected.insert("Name".into(), AttributeValue::S("Joe".into())); + expected.insert("Age".into(), AttributeValue::N("35".into())); + assert_eq!(expected, s); + } + other => panic!("unexpected value {:?}", other), + } + } +} diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs new file mode 100644 index 00000000..00ff08e4 --- /dev/null +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -0,0 +1,280 @@ +use crate::custom_serde::*; +use crate::streams::DynamoDbBatchItemFailure; +use crate::time_window::*; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[cfg(test)] +mod attributes; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum StreamViewType { + NewImage, + OldImage, + NewAndOldImages, + KeysOnly, +} + +impl fmt::Display for StreamViewType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + StreamViewType::NewImage => "NEW_IMAGE", + StreamViewType::OldImage => "OLD_IMAGE", + StreamViewType::NewAndOldImages => "NEW_AND_OLD_IMAGES", + StreamViewType::KeysOnly => "KEYS_ONLY", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum StreamStatus { + Enabling, + Enabled, + Disabling, + Disabled, +} + +impl fmt::Display for StreamStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + StreamStatus::Enabling => "ENABLING", + StreamStatus::Enabled => "ENABLED", + StreamStatus::Disabling => "DISABLING", + StreamStatus::Disabled => "DISABLED", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SharedIteratorType { + TrimHorizon, + Latest, + AtSequenceNumber, + AfterSequenceNumber, +} + +impl fmt::Display for SharedIteratorType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + SharedIteratorType::TrimHorizon => "TRIM_HORIZON", + SharedIteratorType::Latest => "LATEST", + SharedIteratorType::AtSequenceNumber => "AT_SEQUENCE_NUMBER", + SharedIteratorType::AfterSequenceNumber => "AFTER_SEQUENCE_NUMBER", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum OperationType { + Insert, + Modify, + Remove, +} + +impl fmt::Display for OperationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + OperationType::Insert => "INSERT", + OperationType::Modify => "MODIFY", + OperationType::Remove => "REMOVE", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum KeyType { + Hash, + Range, +} + +impl fmt::Display for KeyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + KeyType::Hash => "HASH", + KeyType::Range => "RANGE", + }; + write!(f, "{}", val) + } +} + +/// The `Event` stream event handled to Lambda +/// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Event { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows +/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEvent { + #[serde(rename = "DynamoDBEvent")] + #[serde(flatten)] + pub dynamo_db_event: Event, + #[serde(rename = "TimeWindowProperties")] + #[serde(flatten)] + pub time_window_properties: TimeWindowProperties, +} + +/// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEventResponse { + #[serde(rename = "TimeWindowEventResponseProperties")] + #[serde(flatten)] + pub time_window_event_response_properties: TimeWindowEventResponseProperties, + pub batch_item_failures: Vec, +} + +/// EventRecord stores information about each record of a DynamoDb stream event +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EventRecord { + /// The region in which the GetRecords request was received. + pub aws_region: String, + /// The main body of the stream record, containing all of the DynamoDB-specific + /// fields. + #[serde(rename = "dynamodb")] + pub change: StreamRecord, + /// A globally unique identifier for the event that was recorded in this stream + /// record. + #[serde(rename = "eventID")] + pub event_id: String, + /// The type of data modification that was performed on the DynamoDB table: + /// + /// * INSERT - a new item was added to the table. + /// + /// * MODIFY - one or more of an existing item's attributes were modified. + /// + /// * REMOVE - the item was deleted from the table + pub event_name: String, + /// The AWS service from which the stream record originated. For DynamoDB Streams, + /// this is aws:dynamodb. + #[serde(default)] + pub event_source: Option, + /// The version number of the stream record format. This number is updated whenever + /// the structure of Record is modified. + /// + /// Client applications must not assume that eventVersion will remain at a particular + /// value, as this number is subject to change at any time. In general, eventVersion + /// will only increase as the low-level DynamoDB Streams API evolves. + #[serde(default)] + pub event_version: Option, + /// The event source ARN of DynamoDB + #[serde(rename = "eventSourceARN")] + #[serde(default)] + pub event_source_arn: Option, + /// Items that are deleted by the Time to Live process after expiration have + /// the following fields: + /// + /// * Records[].userIdentity.type + /// + /// "Service" + /// + /// * Records[].userIdentity.principalId + /// + /// "dynamodb.amazonaws.com" + #[serde(default)] + pub user_identity: Option, + /// Describes the record format and relevant mapping information that + /// should be applied to schematize the records on the stream. For + /// DynamoDB Streams, this is application/json. + #[serde(default)] + pub record_format: Option, + /// The DynamoDB table that this event was recorded for. + #[serde(default)] + pub table_name: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + #[serde(default)] + pub type_: String, + #[serde(default)] + pub principal_id: String, +} + +/// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item +/// in a DynamoDB table. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StreamRecord { + /// The approximate date and time when the stream record was created, in UNIX + /// epoch time (http://www.epochconverter.com/) format. + #[serde(rename = "ApproximateCreationDateTime")] + #[serde(with = "float_unix_epoch")] + pub approximate_creation_date_time: DateTime, + /// The primary key attribute(s) for the DynamoDB item that was modified. + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + #[serde(default)] + #[serde(rename = "Keys")] + pub keys: serde_dynamo::Item, + /// The item in the DynamoDB table as it appeared after it was modified. + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + #[serde(default)] + #[serde(rename = "NewImage")] + pub new_image: serde_dynamo::Item, + /// The item in the DynamoDB table as it appeared before it was modified. + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + #[serde(default)] + #[serde(rename = "OldImage")] + pub old_image: serde_dynamo::Item, + /// The sequence number of the stream record. + #[serde(default)] + #[serde(rename = "SequenceNumber")] + pub sequence_number: Option, + /// The size of the stream record, in bytes. + #[serde(rename = "SizeBytes")] + pub size_bytes: i64, + /// The type of data from the modified DynamoDB item that was captured in this + /// stream record. + #[serde(default)] + #[serde(rename = "StreamViewType")] + pub stream_view_type: Option, +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + + extern crate serde_json; + + #[test] + #[cfg(feature = "dynamodb")] + fn example_dynamodb_event() { + let data = include_bytes!("../../fixtures/example-dynamodb-event.json"); + let mut parsed: Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: Event = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + let event = parsed.records.pop().unwrap(); + let date = Utc.ymd(2016, 12, 2).and_hms(1, 27, 0); + assert_eq!(date, event.change.approximate_creation_date_time); + } + + #[test] + #[cfg(feature = "dynamodb")] + fn example_dynamodb_event_with_optional_fields() { + let data = include_bytes!("../../fixtures/example-dynamodb-event-record-with-optional-fields.json"); + let parsed: EventRecord = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventRecord = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs new file mode 100644 index 00000000..87dede6f --- /dev/null +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -0,0 +1,73 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEvent { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + pub time: Option, + #[serde(default)] + pub region: Option, + pub resources: Vec, + #[serde(default)] + pub account: Option, + pub detail: EcrScanEventDetailType, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEventDetailType { + #[serde(default)] + #[serde(rename = "scan-status")] + pub scan_status: Option, + #[serde(default)] + #[serde(rename = "repository-name")] + pub repository_name: Option, + #[serde(rename = "finding-severity-counts")] + pub finding_severity_counts: EcrScanEventFindingSeverityCounts, + #[serde(default)] + #[serde(rename = "image-digest")] + pub image_digest: Option, + #[serde(rename = "image-tags")] + pub image_tags: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEventFindingSeverityCounts { + #[serde(rename = "CRITICAL")] + pub critical: i64, + #[serde(rename = "HIGH")] + pub high: i64, + #[serde(rename = "MEDIUM")] + pub medium: i64, + #[serde(rename = "LOW")] + pub low: i64, + #[serde(rename = "INFORMATIONAL")] + pub informational: i64, + #[serde(rename = "UNDEFINED")] + pub undefined: i64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "ecr_scan")] + fn example_ecr_image_scan_event() { + let data = include_bytes!("../../fixtures/example-ecr-image-scan-event.json"); + let parsed: EcrScanEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EcrScanEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs new file mode 100644 index 00000000..63ef3e1f --- /dev/null +++ b/lambda-events/src/event/firehose/mod.rs @@ -0,0 +1,87 @@ +use crate::custom_serde::*; +use crate::encodings::{Base64Data, MillisecondTimestamp}; +use std::collections::HashMap; + +/// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseEvent { + #[serde(default)] + pub invocation_id: Option, + /// nolint: stylecheck + #[serde(default)] + pub delivery_stream_arn: Option, + /// nolint: stylecheck + #[serde(default)] + pub source_kinesis_stream_arn: Option, + #[serde(default)] + pub region: Option, + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseEventRecord { + #[serde(default)] + pub record_id: Option, + pub approximate_arrival_timestamp: MillisecondTimestamp, + pub data: Base64Data, + #[serde(rename = "kinesisRecordMetadata")] + pub kinesis_firehose_record_metadata: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponse { + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponseRecord { + #[serde(default)] + pub record_id: Option, + /// The status of the transformation. May be TransformedStateOk, TransformedStateDropped or TransformedStateProcessingFailed + #[serde(default)] + pub result: Option, + pub data: Base64Data, + pub metadata: KinesisFirehoseResponseRecordMetadata, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponseRecordMetadata { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub partition_keys: HashMap, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseRecordMetadata { + #[serde(default)] + pub shard_id: Option, + #[serde(default)] + pub partition_key: Option, + #[serde(default)] + pub sequence_number: Option, + pub subsequence_number: i64, + pub approximate_arrival_timestamp: MillisecondTimestamp, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "firehose")] + fn example_firehose_event() { + let data = include_bytes!("../../fixtures/example-firehose-event.json"); + let parsed: KinesisFirehoseEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KinesisFirehoseEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs new file mode 100644 index 00000000..1b73e44b --- /dev/null +++ b/lambda-events/src/event/iam/mod.rs @@ -0,0 +1,23 @@ +/// `IamPolicyDocument` represents an IAM policy document. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IamPolicyDocument { + #[serde(default)] + #[serde(rename = "Version")] + pub version: Option, + #[serde(rename = "Statement")] + pub statement: Vec, +} + +/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IamPolicyStatement { + #[serde(rename = "Action")] + pub action: Vec, + #[serde(default)] + #[serde(rename = "Effect")] + pub effect: Option, + #[serde(rename = "Resource")] + pub resource: Vec, +} diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs new file mode 100644 index 00000000..9f45899f --- /dev/null +++ b/lambda-events/src/event/iot/mod.rs @@ -0,0 +1,99 @@ +use crate::custom_serde::*; +use crate::encodings::Base64Data; +use crate::iam::IamPolicyDocument; +use http::HeaderMap; + +/// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. +/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreCustomAuthorizerRequest { + #[serde(default)] + pub token: Option, + pub signature_verified: bool, + pub protocols: Vec, + pub protocol_data: Option, + pub connection_metadata: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreProtocolData { + pub tls: Option, + pub http: Option, + pub mqtt: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreTlsContext { + #[serde(default)] + pub server_name: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreHttpContext { + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(default)] + pub query_string: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreMqttContext { + #[serde(default)] + pub client_id: Option, + pub password: Base64Data, + #[serde(default)] + pub username: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreConnectionMetadata { + #[serde(default)] + pub id: Option, +} + +/// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. +/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreCustomAuthorizerResponse { + pub is_authenticated: bool, + #[serde(default)] + pub principal_id: Option, + pub disconnect_after_in_seconds: u32, + pub refresh_after_in_seconds: u32, + pub policy_documents: Vec>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot")] + fn example_iot_custom_auth_request() { + let data = include_bytes!("../../fixtures/example-iot-custom-auth-request.json"); + let parsed: IoTCoreCustomAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTCoreCustomAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "iot")] + fn example_iot_custom_auth_response() { + let data = include_bytes!("../../fixtures/example-iot-custom-auth-response.json"); + let parsed: IoTCoreCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTCoreCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs new file mode 100644 index 00000000..0e1c11b6 --- /dev/null +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -0,0 +1,72 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +/// `IoTOneClickEvent` represents a click event published by clicking button type +/// device. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickEvent { + pub device_event: IoTOneClickDeviceEvent, + pub device_info: IoTOneClickDeviceInfo, + pub placement_info: IoTOneClickPlacementInfo, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickDeviceEvent { + pub button_clicked: IoTOneClickButtonClicked, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickButtonClicked { + #[serde(default)] + pub click_type: Option, + #[serde(default)] + pub reported_time: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickDeviceInfo { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(default)] + pub type_: Option, + #[serde(default)] + pub device_id: Option, + pub remaining_life: f64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickPlacementInfo { + #[serde(default)] + pub project_name: Option, + #[serde(default)] + pub placement_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub devices: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot_1_click")] + fn example_iot_1_click_event() { + let data = include_bytes!("../../fixtures/example-iot_1_click-event.json"); + let parsed: IoTOneClickEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTOneClickEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs new file mode 100644 index 00000000..32ba8d5a --- /dev/null +++ b/lambda-events/src/event/iot_button/mod.rs @@ -0,0 +1,27 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTButtonEvent { + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + pub click_type: Option, + #[serde(default)] + pub battery_voltage: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot_button")] + fn example_iot_button_event() { + let data = include_bytes!("../../fixtures/example-iot_button-event.json"); + let parsed: IoTButtonEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTButtonEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs new file mode 100644 index 00000000..4304d7cd --- /dev/null +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -0,0 +1,35 @@ +use crate::iot::*; + +/// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. +/// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCustomAuthorizerRequest { + pub http_context: Option, + pub mqtt_context: Option, + pub tls_context: Option, + #[serde(default)] + #[serde(rename = "token")] + pub authorization_token: Option, + #[serde(default)] + pub token_signature: Option, +} + +pub type IoTHttpContext = IoTCoreHttpContext; + +pub type IoTMqttContext = IoTCoreMqttContext; + +pub type IoTTlsContext = IoTCoreTlsContext; + +/// `IoTCustomAuthorizerResponse` represents the expected format of an IoT device gateway authorization response. +/// Deprecated: Use IoTCoreCustomAuthorizerResponse. `IoTCustomAuthorizerResponse` does not correctly model the response schema. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCustomAuthorizerResponse { + pub is_authenticated: bool, + #[serde(default)] + pub principal_id: Option, + pub disconnect_after_in_seconds: i32, + pub refresh_after_in_seconds: i32, + pub policy_documents: Vec, +} diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs new file mode 100644 index 00000000..6c4d78fa --- /dev/null +++ b/lambda-events/src/event/kafka/mod.rs @@ -0,0 +1,49 @@ +use crate::custom_serde::*; +use crate::encodings::MillisecondTimestamp; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub records: HashMap>, + #[serde(default)] + pub bootstrap_servers: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaRecord { + #[serde(default)] + pub topic: Option, + pub partition: i64, + pub offset: i64, + pub timestamp: MillisecondTimestamp, + #[serde(default)] + pub timestamp_type: Option, + pub key: Option, + pub value: Option, + pub headers: Vec>>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "kafka")] + fn example_kafka_event() { + let data = include_bytes!("../../fixtures/example-kafka-event.json"); + let parsed: KafkaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KafkaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs new file mode 100644 index 00000000..1704009e --- /dev/null +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -0,0 +1,35 @@ +use crate::encodings::Base64Data; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryEvent { + #[serde(default)] + pub invocation_id: Option, + #[serde(default)] + pub application_arn: Option, + pub records: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryEventRecord { + #[serde(default)] + pub record_id: Option, + pub data: Base64Data, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryResponse { + pub records: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryResponseRecord { + #[serde(default)] + pub record_id: Option, + /// possible values include Ok and DeliveryFailed + #[serde(default)] + pub result: Option, +} diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs new file mode 100644 index 00000000..c401fa72 --- /dev/null +++ b/lambda-events/src/event/kinesis/event.rs @@ -0,0 +1,88 @@ +use crate::encodings::{Base64Data, SecondTimestamp}; +use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows +/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisTimeWindowEvent { + #[serde(rename = "KinesisEvent")] + #[serde(flatten)] + pub kinesis_event: KinesisEvent, + #[serde(rename = "TimeWindowProperties")] + #[serde(flatten)] + pub time_window_properties: TimeWindowProperties, +} + +/// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisTimeWindowEventResponse { + #[serde(rename = "TimeWindowEventResponseProperties")] + #[serde(flatten)] + pub time_window_event_response_properties: TimeWindowEventResponseProperties, + // pub batch_item_failures: Vec, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisEventRecord { + /// nolint: stylecheck + #[serde(default)] + pub aws_region: Option, + #[serde(default)] + #[serde(rename = "eventID")] + pub event_id: Option, + #[serde(default)] + pub event_name: Option, + #[serde(default)] + pub event_source: Option, + /// nolint: stylecheck + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_version: Option, + /// nolint: stylecheck + #[serde(default)] + pub invoke_identity_arn: Option, + pub kinesis: KinesisRecord, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisRecord { + pub approximate_arrival_timestamp: SecondTimestamp, + pub data: Base64Data, + pub encryption_type: Option, + #[serde(default)] + pub partition_key: Option, + #[serde(default)] + pub sequence_number: Option, + #[serde(default)] + pub kinesis_schema_version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "kinesis")] + fn example_kinesis_event() { + let data = include_bytes!("../../fixtures/example-kinesis-event.json"); + let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/kinesis/mod.rs b/lambda-events/src/event/kinesis/mod.rs new file mode 100644 index 00000000..079280b9 --- /dev/null +++ b/lambda-events/src/event/kinesis/mod.rs @@ -0,0 +1,3 @@ +pub mod analytics; +mod event; +pub use self::event::*; diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs new file mode 100644 index 00000000..d1567b56 --- /dev/null +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -0,0 +1,104 @@ +use crate::custom_serde::*; +use http::HeaderMap; +use std::collections::HashMap; + +/// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequest { + /// Version is expected to be `"2.0"` + #[serde(default)] + pub version: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + pub cookies: Option>, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + pub request_context: LambdaFunctionUrlRequestContext, + pub body: Option, + pub is_base64_encoded: bool, +} + +/// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContext { + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub request_id: Option, + pub authorizer: Option, + /// APIID is the Lambda URL ID + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + /// DomainName is of the format `".lambda-url..on.aws"` + #[serde(default)] + pub domain_name: Option, + /// DomainPrefix is the Lambda URL ID + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub time: Option, + pub time_epoch: i64, + pub http: LambdaFunctionUrlRequestContextHttpDescription, +} + +/// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { + pub iam: Option, +} + +/// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub caller_id: Option, + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_id: Option, +} + +/// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextHttpDescription { + #[serde(default)] + pub method: Option, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub user_agent: Option, +} + +/// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlResponse { + pub status_code: i64, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(default)] + pub body: Option, + pub is_base64_encoded: bool, + pub cookies: Vec, +} diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs new file mode 100644 index 00000000..a3593dfd --- /dev/null +++ b/lambda-events/src/event/lex/mod.rs @@ -0,0 +1,130 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexEvent { + pub message_version: Option, + pub invocation_source: Option, + pub user_id: Option, + pub input_transcript: Option, + pub session_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub request_attributes: HashMap, + pub bot: Option, + pub output_dialog_mode: Option, + pub current_intent: Option, + pub alternative_intents: Option>, + /// Deprecated: the DialogAction field is never populated by Lex events + pub dialog_action: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexBot { + pub name: Option, + pub alias: Option, + pub version: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexCurrentIntent { + pub name: Option, + pub nlu_intent_confidence_score: Option, + pub slots: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub slot_details: HashMap, + pub confirmation_status: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexAlternativeIntents { + pub name: Option, + pub nlu_intent_confidence_score: Option, + pub slots: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub slot_details: HashMap, + pub confirmation_status: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SlotDetail { + pub resolutions: Option>>, + pub original_value: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexDialogAction { + pub type_: Option, + pub fulfillment_state: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message: HashMap, + pub intent_name: Option, + pub slots: Option, + pub slot_to_elicit: Option, + pub response_card: Option, +} + +pub type SessionAttributes = HashMap; + +pub type Slots = HashMap>; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexResponse { + pub session_attributes: SessionAttributes, + pub dialog_action: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexResponseCard { + pub version: Option, + pub content_type: Option, + pub generic_attachments: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Attachment { + pub title: Option, + pub sub_title: Option, + pub image_url: Option, + pub attachment_link_url: Option, + pub buttons: Option>>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "lex")] + fn example_lex_event() { + let data = include_bytes!("../../fixtures/example-lex-event.json"); + let parsed: LexEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LexEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "lex")] + fn example_lex_response() { + let data = include_bytes!("../../fixtures/example-lex-response.json"); + let parsed: LexEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LexEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs new file mode 100644 index 00000000..e7b8c7f7 --- /dev/null +++ b/lambda-events/src/event/mod.rs @@ -0,0 +1,140 @@ +/// AWS Lambda event definitions for activemq. +#[cfg(feature = "activemq")] +pub mod activemq; + +/// AWS Lambda event definitions for alb. +#[cfg(feature = "alb")] +pub mod alb; +/// AWS Lambda event definitions for apigw. +#[cfg(feature = "apigw")] +pub mod apigw; + +/// AWS Lambda event definitions for appsync. +#[cfg(feature = "appsync")] +pub mod appsync; + +/// AWS Lambda event definitions for autoscaling. +#[cfg(feature = "autoscaling")] +pub mod autoscaling; + +/// AWS Lambda event definitions for chime_bot. +#[cfg(feature = "chime_bot")] +pub mod chime_bot; + +/// AWS Lambda event definitions for clientvpn. +#[cfg(feature = "clientvpn")] +pub mod clientvpn; + +/// CloudWatch Events payload +#[cfg(feature = "cloudwatch_events")] +pub mod cloudwatch_events; + +/// AWS Lambda event definitions for cloudwatch_logs. +#[cfg(feature = "cloudwatch_logs")] +pub mod cloudwatch_logs; + +/// AWS Lambda event definitions for code_commit. +#[cfg(feature = "code_commit")] +pub mod code_commit; + +/// AWS Lambda event definitions for codebuild. +#[cfg(feature = "codebuild")] +pub mod codebuild; + +/// AWS Lambda event definitions for codedeploy. +#[cfg(feature = "codedeploy")] +pub mod codedeploy; + +/// AWS Lambda event definitions for codepipeline_cloudwatch. +#[cfg(feature = "codepipeline_cloudwatch")] +pub mod codepipeline_cloudwatch; + +/// AWS Lambda event definitions for codepipeline_job. +#[cfg(feature = "codepipeline_job")] +pub mod codepipeline_job; + +/// AWS Lambda event definitions for cognito. +#[cfg(feature = "cognito")] +pub mod cognito; + +/// AWS Lambda event definitions for config. +#[cfg(feature = "config")] +pub mod config; + +/// AWS Lambda event definitions for connect. +#[cfg(feature = "connect")] +pub mod connect; + +/// AWS Lambda event definitions for dynamodb. +#[cfg(feature = "dynamodb")] +extern crate serde_dynamo; +#[cfg(feature = "dynamodb")] +pub mod dynamodb; + +/// AWS Lambda event definitions for ecr_scan. +#[cfg(feature = "ecr_scan")] +pub mod ecr_scan; + +/// AWS Lambda event definitions for firehose. +#[cfg(feature = "firehose")] +pub mod firehose; + +/// AWS Lambda event definitions for iam. +#[cfg(feature = "iam")] +pub mod iam; + +/// AWS Lambda event definitions for iot. +#[cfg(feature = "iot")] +pub mod iot; + +/// AWS Lambda event definitions for iot_1_click. +#[cfg(feature = "iot_1_click")] +pub mod iot_1_click; + +/// AWS Lambda event definitions for iot_button. +#[cfg(feature = "iot_button")] +pub mod iot_button; + +/// AWS Lambda event definitions for iot_deprecated. +#[cfg(feature = "iot_deprecated")] +pub mod iot_deprecated; + +/// AWS Lambda event definitions for kafka. +#[cfg(feature = "kafka")] +pub mod kafka; + +/// AWS Lambda event definitions for kinesis. +#[cfg(feature = "kinesis")] +pub mod kinesis; + +/// AWS Lambda event definitions for lambda_function_urls. +#[cfg(feature = "lambda_function_urls")] +pub mod lambda_function_urls; + +/// AWS Lambda event definitions for lex. +#[cfg(feature = "lex")] +pub mod lex; + +/// AWS Lambda event definitions for rabbitmq. +#[cfg(feature = "rabbitmq")] +pub mod rabbitmq; + +/// AWS Lambda event definitions for s3. +#[cfg(feature = "s3")] +pub mod s3; + +/// AWS Lambda event definitions for ses. +#[cfg(feature = "ses")] +pub mod ses; + +/// AWS Lambda event definitions for SNS. +#[cfg(feature = "sns")] +pub mod sns; + +/// AWS Lambda event definitions for SQS. +#[cfg(feature = "sqs")] +pub mod sqs; + +/// AWS Lambda event definitions for streams. +#[cfg(feature = "streams")] +pub mod streams; diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs new file mode 100644 index 00000000..c8af802f --- /dev/null +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -0,0 +1,76 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "rmqMessagesByQueue")] + pub messages_by_queue: HashMap>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqMessage { + pub basic_properties: RabbitMqBasicProperties, + #[serde(default)] + pub data: Option, + pub redelivered: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqBasicProperties +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub content_type: Option, + pub content_encoding: Option, + /// Application or header exchange table + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub headers: HashMap, + pub delivery_mode: u8, + pub priority: u8, + pub correlation_id: Option, + pub reply_to: Option, + #[serde(default)] + pub expiration: Option, + pub message_id: Option, + #[serde(default)] + pub timestamp: Option, + pub type_: Option, + #[serde(default)] + pub user_id: Option, + pub app_id: Option, + pub cluster_id: Option, + pub body_size: u64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "rabbitmq")] + fn example_rabbitmq_event() { + let data = include_bytes!("../../fixtures/example-rabbitmq-event.json"); + let parsed: RabbitMqEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: RabbitMqEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs new file mode 100644 index 00000000..8db71896 --- /dev/null +++ b/lambda-events/src/event/s3/batch_job.rs @@ -0,0 +1,58 @@ +/// `S3BatchJobEvent` encapsulates the detail of a s3 batch job +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobEvent { + #[serde(default)] + pub invocation_schema_version: Option, + #[serde(default)] + pub invocation_id: Option, + pub job: S3BatchJob, + pub tasks: Vec, +} + +/// `S3BatchJob` whichs have the job id +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJob { + #[serde(default)] + pub id: Option, +} + +/// `S3BatchJobTask` represents one task in the s3 batch job and have all task details +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobTask { + #[serde(default)] + pub task_id: Option, + #[serde(default)] + pub s3_key: Option, + #[serde(default)] + pub s3_version_id: Option, + #[serde(default)] + pub s3_bucket_arn: Option, +} + +/// `S3BatchJobResponse` is the response of a iven s3 batch job with the results +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobResponse { + #[serde(default)] + pub invocation_schema_version: Option, + #[serde(default)] + pub treat_missing_keys_as: Option, + #[serde(default)] + pub invocation_id: Option, + pub results: Vec, +} + +/// `S3BatchJobResult` represents the result of a given task +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobResult { + #[serde(default)] + pub task_id: Option, + #[serde(default)] + pub result_code: Option, + #[serde(default)] + pub result_string: Option, +} diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs new file mode 100644 index 00000000..b25cfdd2 --- /dev/null +++ b/lambda-events/src/event/s3/event.rs @@ -0,0 +1,114 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use std::collections::HashMap; + +/// `S3Event` which wrap an array of `S3Event`Record +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Event { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// `S3EventRecord` which wrap record data +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3EventRecord { + #[serde(default)] + pub event_version: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, + pub event_time: DateTime, + #[serde(default)] + pub event_name: Option, + #[serde(rename = "userIdentity")] + pub principal_id: S3UserIdentity, + pub request_parameters: S3RequestParameters, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub response_elements: HashMap, + pub s3: S3Entity, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3UserIdentity { + #[serde(default)] + pub principal_id: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3RequestParameters { + #[serde(default)] + #[serde(rename = "sourceIPAddress")] + pub source_ip_address: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Entity { + #[serde(default)] + #[serde(rename = "s3SchemaVersion")] + pub schema_version: Option, + #[serde(default)] + pub configuration_id: Option, + pub bucket: S3Bucket, + pub object: S3Object, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Bucket { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub owner_identity: Option, + #[serde(default)] + pub arn: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Object { + #[serde(default)] + pub key: Option, + pub size: Option, + #[serde(default)] + pub url_decoded_key: Option, + #[serde(default)] + pub version_id: Option, + #[serde(default)] + pub e_tag: Option, + #[serde(default)] + pub sequencer: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "s3")] + fn example_s3_event() { + let data = include_bytes!("../../fixtures/example-s3-event.json"); + let parsed: S3Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3Event = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_s3_event_with_decoded() { + let data = include_bytes!("../../fixtures/example-s3-event-with-decoded.json"); + let parsed: S3Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3Event = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/s3/mod.rs b/lambda-events/src/event/s3/mod.rs new file mode 100644 index 00000000..b5664585 --- /dev/null +++ b/lambda-events/src/event/s3/mod.rs @@ -0,0 +1,5 @@ +mod event; +pub use self::event::*; + +pub mod batch_job; +pub mod object_lambda; diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs new file mode 100644 index 00000000..e31a751e --- /dev/null +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -0,0 +1,171 @@ +use crate::custom_serde::*; +use http::HeaderMap; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas +/// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3ObjectLambdaEvent

+where + P: DeserializeOwned, + P: Serialize, +{ + pub x_amz_request_id: String, + pub get_object_context: Option, + pub head_object_context: Option, + pub list_objects_context: Option, + pub list_objects_v2_context: Option, + #[serde(default, bound = "")] + pub configuration: Configuration

, + pub user_request: UserRequest, + pub user_identity: UserIdentity, + pub protocol_version: String, +} + +/// `GetObjectContext` contains the input and output details +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetObjectContext { + pub input_s3_url: String, + pub output_route: String, + pub output_token: String, +} + +/// `HeadObjectContext` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HeadObjectContext { + pub input_s3_url: String, +} + +/// `ListObjectsContext` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListObjectsContext { + pub input_s3_url: String, +} + +/// `ListObjectsV2Context` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListObjectsV2Context { + pub input_s3_url: String, +} + +/// `Configuration` contains information about the Object Lambda access point +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Configuration

+where + P: DeserializeOwned, + P: Serialize, +{ + pub access_point_arn: String, + pub supporting_access_point_arn: String, + #[serde(default, bound = "")] + pub payload: P, +} + +/// `UserRequest` contains information about the original call to S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserRequest { + pub url: String, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, +} + +/// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] + +pub struct UserIdentity { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, + pub access_key_id: String, + pub session_context: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContext { + pub attributes: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionIssuer { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, + pub user_name: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_get_object_assumed_role() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-assumed-role.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_get_object_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_head_object_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-head-object-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_list_objects_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_list_objects_v2_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs new file mode 100644 index 00000000..3570652f --- /dev/null +++ b/lambda-events/src/event/ses/mod.rs @@ -0,0 +1,155 @@ +use chrono::{DateTime, Utc}; + +/// `SimpleEmailEvent` is the outer structure of an event sent via SES. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailRecord { + #[serde(default)] + pub event_version: Option, + #[serde(default)] + pub event_source: Option, + pub ses: SimpleEmailService, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailService { + pub mail: SimpleEmailMessage, + pub receipt: SimpleEmailReceipt, + pub content: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailMessage { + pub common_headers: SimpleEmailCommonHeaders, + #[serde(default)] + pub source: Option, + pub timestamp: DateTime, + pub destination: Vec, + pub headers: Vec, + pub headers_truncated: bool, + #[serde(default)] + pub message_id: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailReceipt { + pub recipients: Vec, + pub timestamp: DateTime, + pub spam_verdict: SimpleEmailVerdict, + pub dkim_verdict: SimpleEmailVerdict, + pub dmarc_verdict: SimpleEmailVerdict, + #[serde(default)] + pub dmarc_policy: Option, + pub spf_verdict: SimpleEmailVerdict, + pub virus_verdict: SimpleEmailVerdict, + pub action: SimpleEmailReceiptAction, + pub processing_time_millis: i64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailHeader { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub value: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailCommonHeaders { + pub from: Vec, + pub to: Vec, + #[serde(default)] + pub return_path: Option, + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub date: Option, + #[serde(default)] + pub subject: Option, +} + +/// `SimpleEmailReceiptAction` is a logical union of fields present in all action +/// Types. For example, the FunctionARN and InvocationType fields are only +/// present for the Lambda Type, and the BucketName and ObjectKey fields are only +/// present for the S3 Type. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailReceiptAction { + #[serde(default)] + pub type_: Option, + pub topic_arn: Option, + pub bucket_name: Option, + pub object_key: Option, + pub smtp_reply_code: Option, + pub status_code: Option, + pub message: Option, + pub sender: Option, + pub invocation_type: Option, + pub function_arn: Option, + pub organization_arn: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailVerdict { + #[serde(default)] + pub status: Option, +} + +pub type SimpleEmailDispositionValue = String; + +/// `SimpleEmailDisposition` disposition return for SES to control rule functions +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailDisposition { + pub disposition: SimpleEmailDispositionValue, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "ses")] + fn example_ses_lambda_event() { + let data = include_bytes!("../../fixtures/example-ses-lambda-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "ses")] + fn example_ses_s3_event() { + let data = include_bytes!("../../fixtures/example-ses-s3-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "ses")] + fn example_ses_sns_event() { + let data = include_bytes!("../../fixtures/example-ses-sns-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs new file mode 100644 index 00000000..78193fcf --- /dev/null +++ b/lambda-events/src/event/sns/mod.rs @@ -0,0 +1,248 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// The `Event` notification event handled by Lambda +/// +/// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SnsEvent { + pub records: Vec, +} + +/// SnsRecord stores information about each record of a SNS event +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SnsRecord { + /// A string containing the event source. + pub event_source: String, + + /// A string containing the event version. + pub event_version: String, + + /// A string containing the event subscription ARN. + pub event_subscription_arn: String, + + /// An SNS object representing the SNS message. + pub sns: SnsMessage, +} + +/// SnsMessage stores information about each record of a SNS event +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SnsMessage { + /// The type of SNS message. For a lambda event, this should always be **Notification** + #[serde(rename = "Type")] + pub sns_message_type: String, + + /// A Universally Unique Identifier, unique for each message published. For a notification that Amazon SNS resends during a retry, the message ID of the original message is used. + pub message_id: String, + + /// The Amazon Resource Name (ARN) for the topic that this message was published to. + pub topic_arn: String, + + /// The Subject parameter specified when the notification was published to the topic. + /// + /// The SNS Developer Guide states: *This is an optional parameter. If no Subject was specified, then this name-value pair does not appear in this JSON document.* + /// + /// Preliminary tests show this appears in the lambda event JSON as `Subject: null`, marking as Option with need to test additional scenarios + #[serde(default)] + pub subject: Option, + + /// The time (UTC) when the notification was published. + pub timestamp: DateTime, + + /// Version of the Amazon SNS signature used. + pub signature_version: String, + + /// Base64-encoded SHA1withRSA signature of the Message, MessageId, Subject (if present), Type, Timestamp, and TopicArn values. + pub signature: String, + + /// The URL to the certificate that was used to sign the message. + #[serde(alias = "SigningCertURL")] + pub signing_cert_url: String, + + /// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint. + #[serde(alias = "UnsubscribeURL")] + pub unsubscribe_url: String, + + /// The Message value specified when the notification was published to the topic. + pub message: String, + + /// This is a HashMap of defined attributes for a message. Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// An alternate `Event` notification event to use alongside `SnsRecordObj` and `SnsMessageObj` if you want to deserialize an object inside your SNS messages rather than getting an `Option` message +/// +/// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsEventObj { + pub records: Vec>, +} + +/// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsRecordObj { + /// A string containing the event source. + pub event_source: String, + + /// A string containing the event version. + pub event_version: String, + + /// A string containing the event subscription ARN. + pub event_subscription_arn: String, + + /// An SNS object representing the SNS message. + pub sns: SnsMessageObj, +} + +/// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsMessageObj { + /// The type of SNS message. For a lambda event, this should always be **Notification** + #[serde(rename = "Type")] + pub sns_message_type: String, + + /// A Universally Unique Identifier, unique for each message published. For a notification that Amazon SNS resends during a retry, the message ID of the original message is used. + pub message_id: String, + + /// The Amazon Resource Name (ARN) for the topic that this message was published to. + pub topic_arn: String, + + /// The Subject parameter specified when the notification was published to the topic. + /// + /// The SNS Developer Guide states: *This is an optional parameter. If no Subject was specified, then this name-value pair does not appear in this JSON document.* + /// + /// Preliminary tests show this appears in the lambda event JSON as `Subject: null`, marking as Option with need to test additional scenarios + #[serde(default)] + pub subject: Option, + + /// The time (UTC) when the notification was published. + pub timestamp: DateTime, + + /// Version of the Amazon SNS signature used. + pub signature_version: String, + + /// Base64-encoded SHA1withRSA signature of the Message, MessageId, Subject (if present), Type, Timestamp, and TopicArn values. + pub signature: String, + + /// The URL to the certificate that was used to sign the message. + #[serde(alias = "SigningCertURL")] + pub signing_cert_url: String, + + /// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint. + #[serde(alias = "UnsubscribeURL")] + pub unsubscribe_url: String, + + /// Deserialized into a `T` from nested JSON inside the SNS message string. `T` must implement the `Deserialize` or `DeserializeOwned` trait. + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub message: T, + + /// This is a HashMap of defined attributes for a message. Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// Structured metadata items (such as timestamps, geospatial data, signatures, and identifiers) about the message. +/// +/// Message attributes are optional and separate from—but are sent together with—the message body. The receiver can use this information to decide how to handle the message without having to process the message body first. +/// +/// Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct MessageAttribute { + /// The data type of the attribute. Per the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html), lambda notifications, this will only be **String** or **Binary**. + #[serde(rename = "Type")] + pub data_type: String, + + /// The user-specified message attribute value. + #[serde(rename = "Value")] + pub value: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event() { + let data = include_bytes!("../../fixtures/example-sns-event.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_pascal_case() { + let data = include_bytes!("../../fixtures/example-sns-event-pascal-case.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_cloudwatch_single_metric() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(1, parsed.records.len()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_cloudwatch_multiple_metrics() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(2, parsed.records.len()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_obj_event() { + let data = include_bytes!("../../fixtures/example-sns-event-obj.json"); + + #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] + struct CustStruct { + foo: String, + bar: i32, + } + + let parsed: SnsEventObj = serde_json::from_slice(data).unwrap(); + println!("{:?}", parsed); + + assert_eq!(parsed.records[0].sns.message.foo, "Hello world!"); + assert_eq!(parsed.records[0].sns.message.bar, 123); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs new file mode 100644 index 00000000..5dc178b2 --- /dev/null +++ b/lambda-events/src/event/sqs/mod.rs @@ -0,0 +1,161 @@ +use crate::{custom_serde::*, encodings::Base64Data}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// An individual SQS Message, its metadata, and Message Attributes +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsMessage { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + #[serde(default)] + pub body: Option, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, +} + +/// Alternative to `SqsEvent` to be used alongside `SqsMessageObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SqsEventObj { + #[serde(rename = "Records")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub records: Vec>, +} + +/// Alternative to `SqsMessage` to be used alongside `SqsEventObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "camelCase")] +pub struct SqsMessageObj { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + + /// Deserialized into a `T` from nested JSON inside the SQS body string. `T` must implement the `Deserialize` or `DeserializeOwned` trait. + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub body: T, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsMessageAttribute { + pub string_value: Option, + pub binary_value: Option, + #[serde(default)] + pub string_list_values: Vec, + #[serde(default)] + pub binary_list_values: Vec, + #[serde(default)] + pub data_type: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsBatchResponse { + pub batch_item_failures: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchItemFailure { + pub item_identifier: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_event() { + let data = include_bytes!("../../fixtures/example-sqs-event.json"); + let parsed: SqsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_obj_event() { + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustStruct { + a: String, + b: u32, + } + + let data = include_bytes!("../../fixtures/example-sqs-event-obj.json"); + let parsed: SqsEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.records[0].body.a, "Test"); + assert_eq!(parsed.records[0].body.b, 123); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_batch_response() { + // Example sqs batch response fetched 2022-05-13, from: + // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting + let data = include_bytes!("../../fixtures/example-sqs-batch-response.json"); + let parsed: SqsBatchResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsBatchResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs new file mode 100644 index 00000000..51a77121 --- /dev/null +++ b/lambda-events/src/event/streams/mod.rs @@ -0,0 +1,44 @@ +/// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisEventResponse { + pub batch_item_failures: Vec, +} + +/// `KinesisBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} + +/// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DynamoDbEventResponse { + pub batch_item_failures: Vec, +} + +/// `DynamoDbBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DynamoDbBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} + +/// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsEventResponse { + pub batch_item_failures: Vec, +} + +/// `SqsBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} diff --git a/lambda-events/src/fixtures/example-activemq-event.json b/lambda-events/src/fixtures/example-activemq-event.json new file mode 100644 index 00000000..48ae90cd --- /dev/null +++ b/lambda-events/src/fixtures/example-activemq-event.json @@ -0,0 +1,25 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:533019413397:broker:shask-test:b-0f5b7522-2b41-4f85-a615-735a4e6d96b5", + "messages": [ + { + "messageID": "ID:b-0f5b7522-2b41-4f85-a615-735a4e6d96b5-2.mq.us-west-2.amazonaws.com-34859-1598944546501-4:12:1:1:3", + "messageType": "jms/text-message", + "timestamp": 1599863938941, + "deliveryMode": 1, + "correlationID": "", + "replyTo": "null", + "destination": { + "physicalName": "testQueue" + }, + "redelivered": false, + "type": "", + "expiration": 0, + "priority": 0, + "data": "RW50ZXIgc29tZSB0ZXh0IGhlcmUgZm9yIHRoZSBtZXNzYWdlIGJvZHkuLi4=", + "brokerInTime": 1599863938943, + "brokerOutTime": 1599863938944, + "properties": {"testKey": "testValue"} + } + ] +} diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json b/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json new file mode 100644 index 00000000..8fc2ec51 --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json @@ -0,0 +1,26 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefg" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { + "key": "hello" + }, + "headers": { + "accept": "*/*", + "connection": "keep-alive", + "host": "lambda-test-alb-1334523864.us-east-1.elb.amazonaws.com", + "user-agent": "curl/7.54.0", + "x-amzn-trace-id": "Root=1-5c34e93e-4dea0086f9763ac0667b115a", + "x-forwarded-for": "25.12.198.67", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20", + "x-myheader": "123" + }, + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json b/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json new file mode 100644 index 00000000..7d54621e --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json @@ -0,0 +1,49 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh" + } + }, + "httpMethod": "GET", + "path": "/", + "multiValueQueryStringParameters": { + "key": [ + "hello" + ] + }, + "multiValueHeaders": { + "accept": [ + "*/*" + ], + "connection": [ + "keep-alive" + ], + "host": [ + "lambda-test-alb-1234567.us-east-1.elb.amazonaws.com" + ], + "user-agent": [ + "curl/7.54.0" + ], + "x-amzn-trace-id": [ + "Root=1-5c34e7d4-00ca239424b68028d4c56d68" + ], + "x-forwarded-for": [ + "72.21.198.67" + ], + "x-forwarded-port": [ + "80" + ], + "x-forwarded-proto": [ + "http" + ], + "x-imforwards": [ + "20" + ], + "x-myheader": [ + "123" + ] + }, + "body": "Some text", + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-response.json b/lambda-events/src/fixtures/example-alb-lambda-target-response.json new file mode 100644 index 00000000..1411bf34 --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-response.json @@ -0,0 +1,15 @@ +{ + "isBase64Encoded": false, + "statusCode": 200, + "statusDescription": "200 OK", + "headers": { + "Set-cookie": "cookies", + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "Set-cookie": ["cookie-name=cookie-value;Domain=myweb.com;Secure;HttpOnly","cookie-name=cookie-value;Expires=May 8, 2019"], + "Content-Type": ["application/json"] + }, + "body": "Hello from Lambda" +} + diff --git a/lambda-events/src/fixtures/example-apigw-console-test-request.json b/lambda-events/src/fixtures/example-apigw-console-test-request.json new file mode 100644 index 00000000..ba119d85 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-console-test-request.json @@ -0,0 +1,45 @@ +{ + "body": "{\r\n\t\"a\": 1\r\n}", + "headers":null, + "httpMethod":"POST", + "isBase64Encoded":false, + "multiValueHeaders":null, + "multiValueQueryStringParameters":null, + "path":"/myPath", + "pathParameters":null, + "queryStringParameters":null, + "requestContext":{ + "accountId":"xxxxx", + "apiId":"xxxxx", + "domainName":"testPrefix.testDomainName", + "domainPrefix":"testPrefix", + "extendedRequestId":"NvWWKEZbliAFliA=", + "httpMethod":"POST", + "identity":{ + "accessKey":"xxxxx", + "accountId":"xxxxx", + "apiKey":"test-invoke-api-key", + "apiKeyId":"test-invoke-api-key-id", + "caller":"xxxxx:xxxxx", + "cognitoAuthenticationProvider":null, + "cognitoAuthenticationType":null, + "cognitoIdentityId":null, + "cognitoIdentityPoolId":null, + "principalOrgId":null, + "sourceIp":"test-invoke-source-ip", + "user":"xxxxx:xxxxx", + "userAgent":"aws-internal/3 aws-sdk-java/1.12.154 Linux/5.4.156-94.273.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.322-b06 java/1.8.0_322 vendor/Oracle_Corporation cfg/retry-mode/standard", + "userArn":"arn:aws:sts::xxxxx:assumed-role/xxxxx/xxxxx" + }, + "path":"/myPath", + "protocol":"HTTP/1.1", + "requestId":"e5488776-afe4-4e5e-92b1-37bd23f234d6", + "requestTime":"18/Feb/2022:13:23:12 +0000", + "requestTimeEpoch":1645190592806, + "resourceId":"ddw8yd", + "resourcePath":"/myPath", + "stage":"test-invoke-stage" + }, + "resource":"/myPath", + "stageVariables":null +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json b/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json new file mode 100644 index 00000000..101c63a7 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json @@ -0,0 +1,88 @@ +{ + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:us-east-1:123456789012:s4x3opwd6i/test/GET/request", + "resource": "/request", + "path": "/request", + "httpMethod": "GET", + "headers": { + "X-AMZ-Date": "20170718T062915Z", + "Accept": "*/*", + "HeaderAuth1": "headerValue1", + "CloudFront-Viewer-Country": "US", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "User-Agent": "...", + "X-Forwarded-Proto": "https", + "CloudFront-Is-SmartTV-Viewer": "false", + "Host": "....execute-api.us-east-1.amazonaws.com", + "Accept-Encoding": "gzip, deflate", + "X-Forwarded-Port": "443", + "X-Amzn-Trace-Id": "...", + "Via": "...cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "...", + "X-Forwarded-For": "..., ...", + "Postman-Token": "...", + "cache-control": "no-cache", + "CloudFront-Is-Desktop-Viewer": "true", + "Content-Type": "application/x-www-form-urlencoded" + }, + "multiValueHeaders": { + "X-AMZ-Date": ["20170718T062915Z"], + "Accept": ["*/*"], + "HeaderAuth1": ["headerValue1"], + "CloudFront-Viewer-Country": ["US"], + "CloudFront-Forwarded-Proto": ["https"], + "CloudFront-Is-Tablet-Viewer": ["false"], + "CloudFront-Is-Mobile-Viewer": ["false"], + "User-Agent": ["..."], + "X-Forwarded-Proto": ["https"], + "CloudFront-Is-SmartTV-Viewer": ["false"], + "Host": ["....execute-api.us-east-1.amazonaws.com"], + "Accept-Encoding": ["gzip, deflate"], + "X-Forwarded-Port": ["443"], + "X-Amzn-Trace-Id": ["..."], + "Via": ["...cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["..."], + "X-Forwarded-For": ["..., ..."], + "Postman-Token": ["..."], + "cache-control": ["no-cache"], + "CloudFront-Is-Desktop-Viewer": ["true"], + "Content-Type": ["application/x-www-form-urlencoded"] + }, + "queryStringParameters": { + "QueryString1": "queryValue1" + }, + "multiValueQueryStringParameters": { + "QueryString1": ["queryValue1"] + }, + "pathParameters": {}, + "stageVariables": { + "StageVar1": "stageValue1" + }, + "requestContext": { + "path": "/request", + "accountId": "123456789012", + "resourceId": "05c7jb", + "stage": "test", + "requestId": "...", + "identity": { + "apiKey": "...", + "sourceIp": "...", + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "resourcePath": "/request", + "httpMethod": "GET", + "apiId": "s4x3opwd6i" + } +} + diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-request.json b/lambda-events/src/fixtures/example-apigw-custom-auth-request.json new file mode 100644 index 00000000..9ff8134e --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-request.json @@ -0,0 +1,5 @@ +{ + "type":"TOKEN", + "authorizationToken":"allow", + "methodArn":"arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/*/GET/" +} diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json new file mode 100644 index 00000000..9b624141 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json @@ -0,0 +1,19 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["execute-api:Invoke"], + "Effect": "Allow|Deny", + "Resource": ["arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"] + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} diff --git a/lambda-events/src/fixtures/example-apigw-request.json b/lambda-events/src/fixtures/example-apigw-request.json new file mode 100644 index 00000000..570f785b --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request.json @@ -0,0 +1,96 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": ["*/*"], + "Accept-Encoding": ["gzip, deflate"], + "cache-control": ["no-cache"], + "CloudFront-Forwarded-Proto": ["https"], + "CloudFront-Is-Desktop-Viewer": ["true"], + "CloudFront-Is-Mobile-Viewer": ["false"], + "CloudFront-Is-SmartTV-Viewer": ["false"], + "CloudFront-Is-Tablet-Viewer": ["false"], + "CloudFront-Viewer-Country": ["US"], + "Content-Type": ["application/json"], + "headerName": ["headerValue"], + "Host": ["gy415nuibc.execute-api.us-east-1.amazonaws.com"], + "Postman-Token": ["9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"], + "User-Agent": ["PostmanRuntime/2.4.5"], + "Via": ["1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="], + "X-Forwarded-For": ["54.240.196.186, 54.182.214.83"], + "X-Forwarded-Port": ["443"], + "X-Forwarded-Proto": ["https"] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": ["me"] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser" + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} + diff --git a/lambda-events/src/fixtures/example-apigw-response.json b/lambda-events/src/fixtures/example-apigw-response.json new file mode 100644 index 00000000..ba87222e --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-response.json @@ -0,0 +1,42 @@ +{ + "statusCode": 200, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, lzma, sdch, br", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", + "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==", + "X-Forwarded-For": "192.168.100.1, 192.168.1.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"], + "Accept-Encoding": ["gzip, deflate, lzma, sdch, br"], + "Accept-Language": ["en-US,en;q=0.8"], + "CloudFront-Forwarded-Proto": ["https"], + "CloudFront-Is-Desktop-Viewer": ["true"], + "CloudFront-Is-Mobile-Viewer": ["false"], + "CloudFront-Is-SmartTV-Viewer": ["false"], + "CloudFront-Is-Tablet-Viewer": ["false"], + "CloudFront-Viewer-Country": ["US"], + "Host": ["wt6mne2s9k.execute-api.us-west-2.amazonaws.com"], + "Upgrade-Insecure-Requests": ["1"], + "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48"], + "Via": ["1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g=="], + "X-Forwarded-For": ["192.168.100.1, 192.168.1.1"], + "X-Forwarded-Port": ["443"], + "X-Forwarded-Proto": ["https"] + }, + "body": "Hello World" + } diff --git a/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json b/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json new file mode 100644 index 00000000..906d0eb5 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json @@ -0,0 +1,97 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": ["*/*"], + "Accept-Encoding": ["gzip, deflate"], + "cache-control": ["no-cache"], + "CloudFront-Forwarded-Proto": ["https"], + "CloudFront-Is-Desktop-Viewer": ["true"], + "CloudFront-Is-Mobile-Viewer": ["false"], + "CloudFront-Is-SmartTV-Viewer": ["false"], + "CloudFront-Is-Tablet-Viewer": ["false"], + "CloudFront-Viewer-Country": ["US"], + "Content-Type": ["application/json"], + "headerName": ["headerValue"], + "Host": ["gy415nuibc.execute-api.us-east-1.amazonaws.com"], + "Postman-Token": ["9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"], + "User-Agent": ["PostmanRuntime/2.4.5"], + "Via": ["1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="], + "X-Forwarded-For": ["54.240.196.186, 54.182.214.83"], + "X-Forwarded-Port": ["443"], + "X-Forwarded-Proto": ["https"] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": ["me"] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "operationName": "HelloWorld", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser" + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json new file mode 100644 index 00000000..c220f37b --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json @@ -0,0 +1,61 @@ +{ + "version": "1.0", + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": "user1,123", + "authorizationToken": "user1,123", + "resource": "/request", + "path": "/request", + "httpMethod": "GET", + "headers": { + "X-AMZ-Date": "20170718T062915Z", + "Accept": "*/*", + "HeaderAuth1": "headerValue1", + "CloudFront-Viewer-Country": "US", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "User-Agent": "..." + }, + "queryStringParameters": { + "QueryString1": "queryValue1" + }, + "pathParameters": {}, + "stageVariables": { + "StageVar1": "stageValue1" + }, + "requestContext": { + "path": "/request", + "accountId": "123456789012", + "resourceId": "05c7jb", + "stage": "test", + "requestId": "...", + "identity": { + "apiKey": "...", + "sourceIp": "...", + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } + }, + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "resourcePath": "/request", + "httpMethod": "GET", + "apiId": "abcdef123", + "routeKey": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + } +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json new file mode 100644 index 00000000..a70cac91 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json @@ -0,0 +1,49 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": ["user1", "123"], + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "$default", + "stage": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "pathParameters": { "parameter1": "value1" }, + "stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" } +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json new file mode 100644 index 00000000..59166c8c --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json @@ -0,0 +1,51 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": ["user1", "123"], + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": ["cookie1", "cookie2"], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "$default", + "stage": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "pathParameters": { "parameter1": "value1" }, + "stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" } +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json new file mode 100644 index 00000000..1e741abf --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json @@ -0,0 +1,51 @@ +{ + "version": "$LATEST", + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:eu-west-1:123456789012:abcdef123/test/$connect", + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "multiValueHeaders": { + "Header1": ["value1"], + "Header2": ["value2"] + }, + "multiValueQueryStringParameters": {}, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "apiId": "api-id", + "connectedAt": 1655103417248, + "connectionId": "connection-id", + "domainName": "example.com", + "eventType": "CONNECT", + "extendedRequestId": "extended-req-id", + "identity": { + "sourceIp": "1.2.3.4", + "userAgent": "user-agent" + }, + "messageDirection": "IN", + "requestId": "req-id", + "requestTime": "13/Jun/2022:06:56:57 +0000", + "requestTimeEpoch": 1655103417249, + "routeKey": "$connect", + "stage": "test" + }, + "stageVariables": {}, + "context": { + "request_id": "5req-id" + }, + "deadline": 1655103420483, + "invoked_function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:a-lambda", + "xray_trace_id": "xray-trace-id", + "client_context": null, + "identity": null, + "env_config": { + "function_name": "a-lambda" + }, + "memory": 128, + "log_stream": "log-stream-id", + "log_group": "log-group-id" +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-iam.json b/lambda-events/src/fixtures/example-apigw-v2-request-iam.json new file mode 100644 index 00000000..b78edb3d --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-iam.json @@ -0,0 +1,73 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1", + "cookie2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "iam": { + "accessKey": "ARIA2ZJZYVUEREEIHAKY", + "accountId": "1234567890", + "callerId": "AROA7ZJZYVRE7C3DUXHH6:CognitoIdentityCredentials", + "cognitoIdentity": { + "amr" : ["foo"], + "identityId": "us-east-1:3f291106-8703-466b-8f2b-3ecee1ca56ce", + "identityPoolId": "us-east-1:4f291106-8703-466b-8f2b-3ecee1ca56ce" + }, + "principalOrgId": "AwsOrgId", + "userArn": "arn:aws:iam::1234567890:user/Admin", + "userId": "AROA2ZJZYVRE7Y3TUXHH6" + } + }, + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "time": "12/Mar/2020:19:03:58+0000", + "timeEpoch": 1583348638390, + "http": { + "method": "GET", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + } + }, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json new file mode 100644 index 00000000..e3422a9a --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json @@ -0,0 +1,70 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1", + "cookie2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "time": "12/Mar/2020:19:03:58+0000", + "timeEpoch": 1583348638390, + "http": { + "method": "GET", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + } + }, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json new file mode 100644 index 00000000..60263e99 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json @@ -0,0 +1,63 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1", + "cookie2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "lambda": { + "key": "value" + } + }, + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "time": "12/Mar/2020:19:03:58+0000", + "timeEpoch": 1583348638390, + "http": { + "method": "GET", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + } + }, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json new file mode 100644 index 00000000..9f7a7838 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json @@ -0,0 +1,48 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "content-length": "0", + "host": "aaaaaaaaaa.execute-api.us-west-2.amazonaws.com", + "user-agent": "curl/7.58.0", + "x-amzn-trace-id": "Root=1-5e9f0c65-1de4d666d4dd26aced652b6c", + "x-forwarded-for": "1.2.3.4", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "aaaaaaaaaa", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "aaaaaaaaaa.execute-api.us-west-2.amazonaws.com", + "domainPrefix": "aaaaaaaaaa", + "http": { + "method": "GET", + "path": "/", + "protocol": "HTTP/1.1", + "sourceIp": "1.2.3.4", + "userAgent": "curl/7.58.0" + }, + "requestId": "LV7fzho-PHcEJPw=", + "routeKey": "$default", + "stage": "$default", + "time": "21/Apr/2020:15:08:21 +0000", + "timeEpoch": 1587481701067 + }, + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json b/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json new file mode 100644 index 00000000..27ec3479 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json @@ -0,0 +1,58 @@ +{ + "headers": { + "Host": "asdfasdf.execute-api.us-west-2.amazonaws.com", + "Sec-WebSocket-Key": "asdfasdf==", + "Sec-WebSocket-Version": "13", + "X-Amzn-Trace-Id": "Root=1-asdf-asdfasdf", + "x-api-key": "asdfasdf", + "X-Forwarded-For": "10.0.0.13", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ + "asdfasdf.execute-api.us-west-2.amazonaws.com" + ], + "Sec-WebSocket-Key": [ + "asdfasdf==" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Amzn-Trace-Id": [ + "Root=1-asdf-asdfasdf" + ], + "x-api-key": [ + "asdfasdf" + ], + "X-Forwarded-For": [ + "10.0.0.13" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "requestContext": { + "routeKey": "$connect", + "eventType": "CONNECT", + "extendedRequestId": "asdfasdf=", + "requestTime": "22/Feb/2022:19:07:37 +0000", + "messageDirection": "IN", + "stage": "dev", + "connectedAt": 1645556857902, + "requestTimeEpoch": 1645556857902, + "identity": { + "apiKey": "asdfasdf", + "apiKeyId": "asdf", + "sourceIp": "10.0.0.13" + }, + "requestId": "asdf=", + "domainName": "asdfasdf.execute-api.us-west-2.amazonaws.com", + "connectionId": "asdfasdf=", + "apiId": "asdfasdf" + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request.json b/lambda-events/src/fixtures/example-apigw-websocket-request.json new file mode 100644 index 00000000..2628ae26 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request.json @@ -0,0 +1,108 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ + "*.execute-api.eu-central-1.amazonaws.com" + ], + "Sec-WebSocket-Extensions": [ + "permessage-deflate; client_max_window_bits" + ], + "Sec-WebSocket-Key": [ + "*" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Amzn-Trace-Id": [ + "Root=*" + ], + "X-Forwarded-For": [ + "*.*.*.*" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": ["me"] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "stage": "testStage", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser" + }, + "resourcePath": "/{proxy+}", + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "httpMethod": "POST", + "apiId": "gy415nuibc", + "connectedAt": 1547230720092, + "connectionId": "TWegAcC4EowCHnA=", + "domainName": "*.execute-api.eu-central-1.amazonaws.com", + "error": "*", + "eventType": "CONNECT", + "extendedRequestId": "TWegAcC4EowCHnA=", + "integrationLatency": "123", + "messageDirection": "IN", + "messageId": null, + "requestTime": "07/Jan/2019:09:20:57 +0000", + "requestTimeEpoch": 0, + "routeKey": "$connect", + "status": "*" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} + diff --git a/lambda-events/src/fixtures/example-appsync-batchinvoke.json b/lambda-events/src/fixtures/example-appsync-batchinvoke.json new file mode 100644 index 00000000..cd6a323a --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-batchinvoke.json @@ -0,0 +1,28 @@ +{ + "version": "2017-02-28", + "operation": "BatchInvoke", + "payload": [{ + "arguments": { + "id": "postId1", + "count": 1, + "float": 1.2, + "flag": true + } + }, + { + "arguments": { + "id": "postId2", + "count": 2, + "float": 1.2 + } + }, + { + "arguments": { + "id": "postId3", + "count": 3, + "flag": false + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-appsync-identity-cognito.json b/lambda-events/src/fixtures/example-appsync-identity-cognito.json new file mode 100644 index 00000000..266774b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-identity-cognito.json @@ -0,0 +1,19 @@ +{ + "sub": "123-456", + "issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc", + "username": "user1", + "claims": { + "sub": "123-456", + "aud": "abcdefg", + "event_id": "123-123-123", + "token_use": "id", + "auth_time": 1551226125, + "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc", + "cognito:username": "user1", + "exp": 1551228178628, + "iat": 1551228178629 + }, + "sourceIp": ["192.168.196.186", "193.168.196.186"], + "defaultAuthStrategy": "ALLOW" +} + diff --git a/lambda-events/src/fixtures/example-appsync-identity-iam.json b/lambda-events/src/fixtures/example-appsync-identity-iam.json new file mode 100644 index 00000000..ffeaa707 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-identity-iam.json @@ -0,0 +1,11 @@ +{ + "accountId": "accountid123", + "cognitoIdentityPoolId": "identitypoolid123", + "cognitoIdentityId": "identityid123", + "cognitoIdentityAuthType": "authenticated", + "cognitoIdentityAuthProvider": "providerABC", + "sourceIp": ["192.168.196.186", "193.168.196.186"], + "username": "user1", + "userArn": "arn:aws:iam::123456789012:user/appsync" +} + diff --git a/lambda-events/src/fixtures/example-appsync-invoke.json b/lambda-events/src/fixtures/example-appsync-invoke.json new file mode 100644 index 00000000..7943e082 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-invoke.json @@ -0,0 +1,14 @@ +{ + "version": "2017-02-28", + "operation": "Invoke", + "payload": { + "field": "getPost", + "arguments": { + "id": "postId1", + "count": 1, + "float": 1.2, + "flag": true + } + } +} + diff --git a/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json b/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json new file mode 100644 index 00000000..bcc28672 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json @@ -0,0 +1,12 @@ +{ + "authorizationToken": "ExampleAUTHtoken123123123", + "requestContext": { + "apiId": "aaaaaa123123123example123", + "accountId": "111122223333", + "requestId": "f4081827-1111-4444-5555-5cf4695f339f", + "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", + "operationName": "MyQuery", + "variables": {} + } +} + diff --git a/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json b/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json new file mode 100644 index 00000000..461c4a4d --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json @@ -0,0 +1,8 @@ +{ + "isAuthorized": true, + "resolverContext": { + "banana": "very yellow", + "apple": "very green" + } +} + diff --git a/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json b/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json new file mode 100644 index 00000000..d144c10b --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json @@ -0,0 +1,29 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Launch Successful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "InProgress", + "Description": "Launching a new EC2 instance: i-12345678", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json b/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json new file mode 100644 index 00000000..e446d90d --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json @@ -0,0 +1,28 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Launch Unsuccessful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "Failed", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "message-text", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json b/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json new file mode 100644 index 00000000..cd19446a --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json @@ -0,0 +1,21 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance-launch Lifecycle Action", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn" + ], + "detail": { + "LifecycleActionToken": "87654321-4321-4321-4321-210987654321", + "AutoScalingGroupName": "my-asg", + "LifecycleHookName": "my-lifecycle-hook", + "EC2InstanceId": "i-1234567890abcdef0", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING", + "NotificationMetadata": "additional-info" + } + } + diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json new file mode 100644 index 00000000..7afd13c7 --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn" + ], + "detail": { + "LifecycleActionToken":"87654321-4321-4321-4321-210987654321", + "AutoScalingGroupName":"my-asg", + "LifecycleHookName":"my-lifecycle-hook", + "EC2InstanceId":"i-1234567890abcdef0", + "LifecycleTransition":"autoscaling:EC2_INSTANCE_TERMINATING" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json new file mode 100644 index 00000000..cf6d1b0a --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json @@ -0,0 +1,29 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Terminate Successful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "InProgress", + "Description": "Terminating EC2 instance: i-12345678", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json new file mode 100644 index 00000000..ff973d97 --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json @@ -0,0 +1,28 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Terminate Unsuccessful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "Failed", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "message-text", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json b/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json new file mode 100644 index 00000000..92d59ee3 --- /dev/null +++ b/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json @@ -0,0 +1,12 @@ +{ + "connection-id": "cvpn-connection-04e7e1b2f0daf9460", + "endpoint-id": "cvpn-endpoint-0f13eab7f860433cc", + "common-name": "", + "username": "username", + "platform": "", + "platform-version": "", + "public-ip": "10.11.12.13", + "client-openvpn-version": "", + "schema-version": "v1" +} + diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json new file mode 100644 index 00000000..94a10f96 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json @@ -0,0 +1,40 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\":\"EXAMPLE\",\"AlarmDescription\":\"EXAMPLE\",\"AWSAccountId\":\"EXAMPLE\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2015-06-03T17:43:27.123+0000\",\"Region\":\"EXAMPLE\",\"AlarmArn\":\"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\":\"INSUFFICIENT_DATA\",\"Trigger\":{\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":0.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\",\"Metrics\":[{\"Expression\":\"m1*1\",\"Id\":\"e1\",\"Label\":\"Expression1\",\"ReturnData\":true},{\"Id\":\"m1\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"TestInstance\",\"name\":\"InstanceId\"}],\"MetricName\":\"NetworkOut\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false}]}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + }, + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\":\"EXAMPLE\",\"AlarmDescription\":\"EXAMPLE\",\"AWSAccountId\":\"EXAMPLE\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2015-06-03T17:43:27.123+0000\",\"Region\":\"EXAMPLE\",\"AlarmArn\":\"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\":\"INSUFFICIENT_DATA\",\"Trigger\":{\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":0.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\",\"Metrics\":[{\"Expression\":\"m1*1\",\"Id\":\"e1\",\"Label\":\"Expression1\",\"ReturnData\":true},{\"Id\":\"m1\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"TestInstance\",\"name\":\"InstanceId\"}],\"MetricName\":\"NetworkOut\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false}]}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + } + ] +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json new file mode 100644 index 00000000..8f58b797 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\": \"EXAMPLE\",\"AlarmDescription\": \"EXAMPLE\",\"AWSAccountId\": \"123456789012\",\"NewStateValue\": \"ALARM\",\"NewStateReason\": \"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\": \"2015-06-03T17:43:27.123+0000\",\"Region\": \"EXAMPLE\",\"AlarmArn\": \"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\": \"INSUFFICIENT_DATA\",\"Trigger\": {\"MetricName\": \"NetworkOut\",\"Namespace\": \"AWS/EC2\",\"StatisticType\": \"Statistic\",\"Statistic\": \"AVERAGE\",\"Unit\": \"Bytes\",\"Dimensions\": [{\"value\": \"TestInstance\",\"name\": \"InstanceId\"}],\"Period\": 60,\"EvaluationPeriods\": 1,\"ComparisonOperator\": \"GreaterThanThreshold\",\"Threshold\": 0.0,\"TreatMissingData\": \"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\": \"\"}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + } + ] +} diff --git a/lambda-events/src/fixtures/example-cloudwatch_logs-event.json b/lambda-events/src/fixtures/example-cloudwatch_logs-event.json new file mode 100644 index 00000000..e6aae4d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch_logs-event.json @@ -0,0 +1,6 @@ +{ + "awslogs": { + "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" + } +} + diff --git a/lambda-events/src/fixtures/example-code_commit-event.json b/lambda-events/src/fixtures/example-code_commit-event.json new file mode 100644 index 00000000..751e1afa --- /dev/null +++ b/lambda-events/src/fixtures/example-code_commit-event.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "eventId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventVersion": "1.0", + "eventTime": "2018-01-26T15:58:33.475+0000", + "eventTriggerName": "my-trigger", + "eventPartNumber": 1, + "codecommit": { + "references": [ + { + "commit": "5c4ef1049f1d27deadbeeff313e0730018be182b", + "ref": "refs/heads/master" + } + ] + }, + "eventName": "TriggerEventTest", + "eventTriggerConfigId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventSourceARN": "arn:aws:codecommit:us-east-1:123456789012:my-repo", + "userIdentityARN": "arn:aws:iam::123456789012:root", + "eventSource": "aws:codecommit", + "awsRegion": "us-east-1", + "eventTotalParts": 1 + } + ] +} + diff --git a/lambda-events/src/fixtures/example-codebuild-phase-change.json b/lambda-events/src/fixtures/example-codebuild-phase-change.json new file mode 100644 index 00000000..5f2f5f5d --- /dev/null +++ b/lambda-events/src/fixtures/example-codebuild-phase-change.json @@ -0,0 +1,138 @@ +{ + "version": "0", + "id": "43ddc2bd-af76-9ca5-2dc7-b695e15adeEX", + "detail-type": "CodeBuild Build Phase Change", + "source": "aws.codebuild", + "account": "123456789012", + "time": "2017-09-01T16:14:21Z", + "region": "us-west-2", + "resources":[ + "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX" + ], + "detail":{ + "completed-phase": "COMPLETED", + "project-name": "my-sample-project", + "build-id": "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX", + "completed-phase-context": "[]", + "additional-information": { + "artifact": { + "md5sum": "da9c44c8a9a3cd4b443126e823168fEX", + "sha256sum": "6ccc2ae1df9d155ba83c597051611c42d60e09c6329dcb14a312cecc0a8e39EX", + "location": "arn:aws:s3:::codebuild-123456789012-output-bucket/my-output-artifact.zip" + }, + "environment": { + "image": "aws/codebuild/standard:2.0", + "privileged-mode": false, + "compute-type": "BUILD_GENERAL1_SMALL", + "type": "LINUX_CONTAINER", + "environment-variables": [] + }, + "timeout-in-minutes": 60.0, + "build-complete": true, + "build-number": 55.0, + "initiator": "MyCodeBuildDemoUser", + "build-start-time": "Sep 1, 2017 4:12:29 PM", + "source": { + "location": "codebuild-123456789012-input-bucket/my-input-artifact.zip", + "type": "S3" + }, + "logs": { + "group-name": "/aws/codebuild/my-sample-project", + "stream-name": "8745a7a9-c340-456a-9166-edf953571bEX", + "deep-link": "https://console.aws.amazon.com/cloudwatch/home?region=us-west-2#logEvent:group=/aws/codebuild/my-sample-project;stream=8745a7a9-c340-456a-9166-edf953571bEX" + }, + "phases": [ + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:12:29 PM", + "duration-in-seconds": 0.0, + "phase-type": "SUBMITTED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 13, 2019 4:12:29 AM", + "duration-in-seconds": 0.0, + "phase-type": "QUEUED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:13:05 PM", + "duration-in-seconds": 36, + "phase-type": "PROVISIONING", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:05 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 4.0, + "phase-type": "DOWNLOAD_SOURCE", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0, + "phase-type": "INSTALL", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0.0, + "phase-type": "PRE_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 70, + "phase-type": "BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0.0, + "phase-type": "POST_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0, + "phase-type": "UPLOAD_ARTIFACTS", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:26 PM", + "duration-in-seconds": 4.0, + "phase-type": "FINALIZING", + "phase-status": "SUCCEEDED" + }, + { + "start-time": "Sep 1, 2017 4:14:26 PM", + "phase-type": "COMPLETED" + } + ] + }, + "completed-phase-status": "SUCCEEDED", + "completed-phase-duration-seconds": 4.0, + "version": "1", + "completed-phase-start": "Sep 1, 2017 4:14:21 PM", + "completed-phase-end": "Sep 1, 2017 4:14:26 PM" + } +} + diff --git a/lambda-events/src/fixtures/example-codebuild-state-change.json b/lambda-events/src/fixtures/example-codebuild-state-change.json new file mode 100644 index 00000000..4bf82f20 --- /dev/null +++ b/lambda-events/src/fixtures/example-codebuild-state-change.json @@ -0,0 +1,142 @@ +{ + "version": "0", + "id": "c030038d-8c4d-6141-9545-00ff7b7153EX", + "detail-type": "CodeBuild Build State Change", + "source": "aws.codebuild", + "account": "123456789012", + "time": "2017-09-01T16:14:28Z", + "region": "us-west-2", + "resources":[ + "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX" + ], + "detail":{ + "build-status": "SUCCEEDED", + "project-name": "my-sample-project", + "build-id": "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX", + "additional-information": { + "artifact": { + "md5sum": "da9c44c8a9a3cd4b443126e823168fEX", + "sha256sum": "6ccc2ae1df9d155ba83c597051611c42d60e09c6329dcb14a312cecc0a8e39EX", + "location": "arn:aws:s3:::codebuild-123456789012-output-bucket/my-output-artifact.zip" + }, + "environment": { + "image": "aws/codebuild/standard:2.0", + "privileged-mode": false, + "compute-type": "BUILD_GENERAL1_SMALL", + "type": "LINUX_CONTAINER", + "environment-variables": [ + { + "name": "TEST", + "type": "PLAINTEXT", + "value": "TEST" + } + ] + }, + "timeout-in-minutes": 60.0, + "build-complete": true, + "build-number": 55.0, + "initiator": "MyCodeBuildDemoUser", + "build-start-time": "Sep 1, 2017 4:12:29 PM", + "source": { + "location": "codebuild-123456789012-input-bucket/my-input-artifact.zip", + "type": "S3" + }, + "source-version": "my-source-version", + "logs": { + "group-name": "/aws/codebuild/my-sample-project", + "stream-name": "8745a7a9-c340-456a-9166-edf953571bEX", + "deep-link": "https://console.aws.amazon.com/cloudwatch/home?region=us-west-2#logEvent:group=/aws/codebuild/my-sample-project;stream=8745a7a9-c340-456a-9166-edf953571bEX" + }, + "phases": [ + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:12:29 PM", + "duration-in-seconds": 0, + "phase-type": "SUBMITTED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 13, 2019 4:12:29 AM", + "duration-in-seconds": 0.0, + "phase-type": "QUEUED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:13:05 PM", + "duration-in-seconds": 36.0, + "phase-type": "PROVISIONING", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:05 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 4, + "phase-type": "DOWNLOAD_SOURCE", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0.0, + "phase-type": "INSTALL", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0, + "phase-type": "PRE_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 70.0, + "phase-type": "BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0, + "phase-type": "POST_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0.0, + "phase-type": "UPLOAD_ARTIFACTS", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:26 PM", + "duration-in-seconds": 4, + "phase-type": "FINALIZING", + "phase-status": "SUCCEEDED" + }, + { + "start-time": "Sep 1, 2017 4:14:26 PM", + "phase-type": "COMPLETED" + } + ] + }, + "current-phase": "COMPLETED", + "current-phase-context": "[]", + "version": "1" + } +} + diff --git a/lambda-events/src/fixtures/example-codedeploy-deployment-event.json b/lambda-events/src/fixtures/example-codedeploy-deployment-event.json new file mode 100644 index 00000000..dc44df09 --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-deployment-event.json @@ -0,0 +1,22 @@ +{ + "account": "123456789012", + "region": "us-east-1", + "detail-type": "CodeDeploy Deployment State-change Notification", + "source": "aws.codedeploy", + "version": "0", + "time": "2016-06-30T22:06:31Z", + "id": "c071bfbf-83c4-49ca-a6ff-3df053957145", + "resources": [ + "arn:aws:codedeploy:us-east-1:123456789012:application:myApplication", + "arn:aws:codedeploy:us-east-1:123456789012:deploymentgroup:myApplication/myDeploymentGroup" + ], + "detail": { + "instanceGroupId": "9fd2fbef-2157-40d8-91e7-6845af69e2d2", + "region": "us-east-1", + "application": "myApplication", + "deploymentId": "d-123456789", + "state": "SUCCESS", + "deploymentGroup": "myDeploymentGroup" + } +} + diff --git a/lambda-events/src/fixtures/example-codedeploy-instance-event.json b/lambda-events/src/fixtures/example-codedeploy-instance-event.json new file mode 100644 index 00000000..001e1d39 --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-instance-event.json @@ -0,0 +1,23 @@ +{ + "account": "123456789012", + "region": "us-east-1", + "detail-type": "CodeDeploy Instance State-change Notification", + "source": "aws.codedeploy", + "version": "0", + "time": "2016-06-30T23:18:50Z", + "id": "fb1d3015-c091-4bf9-95e2-d98521ab2ecb", + "resources": [ + "arn:aws:ec2:us-east-1:123456789012:instance/i-0000000aaaaaaaaaa", + "arn:aws:codedeploy:us-east-1:123456789012:deploymentgroup:myApplication/myDeploymentGroup", + "arn:aws:codedeploy:us-east-1:123456789012:application:myApplication" + ], + "detail": { + "instanceId": "i-0000000aaaaaaaaaa", + "region": "us-east-1", + "state": "SUCCESS", + "application": "myApplication", + "deploymentId": "d-123456789", + "instanceGroupId": "8cd3bfa8-9e72-4cbe-a1e5-da4efc7efd49", + "deploymentGroup": "myDeploymentGroup" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json b/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json new file mode 100644 index 00000000..38f85f00 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json @@ -0,0 +1,27 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Action Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "execution-id": "01234567-0123-0123-0123-012345678901", + "stage": "Prod", + "action": "myAction", + "state": "STARTED", + "region":"us-west-2", + "type": { + "owner": "AWS", + "category": "Deploy", + "provider": "CodeDeploy", + "version": 1 + } + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json b/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json new file mode 100644 index 00000000..cb311262 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json @@ -0,0 +1,18 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Stage Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "state": "STARTED", + "execution-id": "01234567-0123-0123-0123-012345678901" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json b/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json new file mode 100644 index 00000000..43ade4b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json @@ -0,0 +1,18 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Pipeline Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "state": "STARTED", + "execution-id": "01234567-0123-0123-0123-012345678901" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline_job-event.json b/lambda-events/src/fixtures/example-codepipeline_job-event.json new file mode 100644 index 00000000..d43d2d0f --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline_job-event.json @@ -0,0 +1,34 @@ +{ + "CodePipeline.job": { + "id": "11111111-abcd-1111-abcd-111111abcdef", + "accountId": "111111111111", + "data": { + "actionConfiguration": { + "configuration": { + "FunctionName": "MyLambdaFunctionForAWSCodePipeline", + "UserParameters": "some-input-such-as-a-URL" + } + }, + "inputArtifacts": [ + { + "location": { + "s3Location": { + "bucketName": "the name of the bucket configured as the pipeline artifact store in Amazon S3, for example codepipeline-us-east-2-1234567890", + "objectKey": "the name of the application, for example CodePipelineDemoApplication.zip" + }, + "type": "S3" + }, + "revision": null, + "name": "ArtifactName" + } + ], + "outputArtifacts": [], + "artifactCredentials": { + "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken": "MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcNMTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9TrDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpEIbb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0FkbFFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTbNYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE=", + "accessKeyId": "AKIAIOSFODNN7EXAMPLE" + }, + "continuationToken": "A continuation token if continuing job" + } + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json new file mode 100644 index 00000000..99acf0a2 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json @@ -0,0 +1,40 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "CreateAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "challengeName": "CUSTOM_CHALLENGE", + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "publicChallengeParameters": { + "a": "b" + }, + "privateChallengeParameters": { + "c": "d" + }, + "challengeMetadata": "challengeMetadata" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json new file mode 100644 index 00000000..90e8b68e --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json @@ -0,0 +1,28 @@ +{ + "version": "1", + "triggerSource": "CustomMessage_SignUp/CustomMessage_ResendCode/CustomMessage_ForgotPassword/CustomMessage_VerifyUserAttribute", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "phone_number_verified": true, + "email_verified": false + }, + "codeParameter": "####", + "usernameParameter": "{username}", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "smsMessage": "", + "emailMessage": "", + "emailSubject": "" + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json new file mode 100644 index 00000000..c878e661 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json @@ -0,0 +1,34 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "DefineAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": false + }, + "response": { + "challengeName": "challengeName" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json new file mode 100644 index 00000000..33106f88 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json @@ -0,0 +1,36 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "DefineAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": false + }, + "response": { + "challengeName": "challengeName", + "issueTokens": true, + "failAuthentication": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json b/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json new file mode 100644 index 00000000..958b40c3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json @@ -0,0 +1,34 @@ +{ + "version": "1", + "triggerSource": "UserMigration_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "password": "", + "validationData": { + "exampleMetadataKey": "example metadata value" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "userAttributes": { + "email": "", + "phone_number": "" + }, + "finalUserStatus": "", + "messageAction": "", + "desiredDeliveryMediums": [ + "", + "" + ], + "forceAliasCreation": true + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json b/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json new file mode 100644 index 00000000..1af00a20 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json @@ -0,0 +1,22 @@ +{ + "version": "1", + "triggerSource": "PostAuthentication_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "newDeviceUsed": true, + "userAttributes" : { + "email": "", + "phone_number": "" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json b/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json new file mode 100644 index 00000000..c4e4aa18 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json @@ -0,0 +1,21 @@ +{ + "version": "1", + "triggerSource": "PostConfirmation_ConfirmSignUp", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes" : { + "email": "", + "phone_number": "" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json b/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json new file mode 100644 index 00000000..45e76bff --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json @@ -0,0 +1,21 @@ +{ + "version": "1", + "triggerSource": "PreAuthentication_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "email": "" + }, + "validationData": { + "k1": "v1", + "k2": "v2" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json b/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json new file mode 100644 index 00000000..e579e3b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json @@ -0,0 +1,29 @@ +{ + "version": "1", + "triggerSource": "PreSignUp_SignUp", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "email": "", + "phone_number": "" + }, + "validationData": { + "k1": "v1", + "k2": "v2" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "autoConfirmUser": false, + "autoVerifyEmail": true, + "autoVerifyPhone": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json new file mode 100644 index 00000000..fed10a51 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json @@ -0,0 +1,29 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsOverrideDetails": null + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json new file mode 100644 index 00000000..7b851904 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json @@ -0,0 +1,40 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsOverrideDetails": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" + }, + "claimsToSuppress": ["email"], + "groupOverrideDetails": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } + } + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json new file mode 100644 index 00000000..70a973f4 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json @@ -0,0 +1,29 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json new file mode 100644 index 00000000..b1d88fee --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json @@ -0,0 +1,30 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "answerCorrect": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event.json b/lambda-events/src/fixtures/example-cognito-event.json new file mode 100644 index 00000000..eec738d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event.json @@ -0,0 +1,17 @@ +{ + "datasetName": "datasetName", + "eventType": "SyncTrigger", + "region": "us-east-1", + "identityId": "identityId", + "datasetRecords": + { + "SampleKey1": + { + "newValue": "newValue1", + "oldValue": "oldValue1", + "op": "replace" + } + }, + "identityPoolId": "identityPoolId", + "version": 2 +} diff --git a/lambda-events/src/fixtures/example-config-event.json b/lambda-events/src/fixtures/example-config-event.json new file mode 100644 index 00000000..e269c762 --- /dev/null +++ b/lambda-events/src/fixtures/example-config-event.json @@ -0,0 +1,13 @@ +{ + "configRuleId": "config-rule-0123456", + "version": "1.0", + "configRuleName": "periodic-config-rule", + "configRuleArn": "arn:aws:config:us-east-1:012345678912:config-rule/config-rule-0123456", + "invokingEvent": "{\"configSnapshotId\":\"00000000-0000-0000-0000-000000000000\",\"s3ObjectKey\":\"AWSLogs/000000000000/Config/us-east-1/2016/2/24/ConfigSnapshot/000000000000_Config_us-east-1_ConfigSnapshot_20160224T182319Z_00000000-0000-0000-0000-000000000000.json.gz\",\"s3Bucket\":\"config-bucket\",\"notificationCreationTime\":\"2016-02-24T18:23:20.328Z\",\"messageType\":\"ConfigurationSnapshotDeliveryCompleted\",\"recordVersion\":\"1.1\"}", + "resultToken": "myResultToken", + "eventLeftScope": false, + "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", + "executionRoleArn": "arn:aws:iam::012345678912:role/config-role", + "accountId": "012345678912" +} + diff --git a/lambda-events/src/fixtures/example-connect-event-without-queue.json b/lambda-events/src/fixtures/example-connect-event-without-queue.json new file mode 100644 index 00000000..c3d626db --- /dev/null +++ b/lambda-events/src/fixtures/example-connect-event-without-queue.json @@ -0,0 +1,29 @@ +{ + "Name": "ContactFlowExecution", + "Details": { + "Parameters": { + "key1": "value1", + "key2": "value2" + }, + "ContactData": { + "ContactId": "ASDAcxcasDFSSDFs", + "InitialContactId": "Acxsada-asdasdaxA", + "PreviousContactId": "Acxsada-asdasdaxA", + "Channel": "Voice", + "InstanceARN": "", + "InitiationMethod": "INBOUND/OUTBOUND/TRANSFER/CALLBACK", + "SystemEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "01234567" + }, + "CustomerEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "+14802021091" + }, + "Attributes": { + "key1": "value", + "key2": "value" + } + } + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-connect-event.json b/lambda-events/src/fixtures/example-connect-event.json new file mode 100644 index 00000000..2480de70 --- /dev/null +++ b/lambda-events/src/fixtures/example-connect-event.json @@ -0,0 +1,33 @@ +{ + "Name": "ContactFlowExecution", + "Details": { + "Parameters": { + "key1": "value1", + "key2": "value2" + }, + "ContactData": { + "ContactId": "ASDAcxcasDFSSDFs", + "InitialContactId": "Acxsada-asdasdaxA", + "PreviousContactId": "Acxsada-asdasdaxA", + "Channel": "Voice", + "InstanceARN": "", + "InitiationMethod": "INBOUND/OUTBOUND/TRANSFER/CALLBACK", + "SystemEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "01234567" + }, + "CustomerEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "+14802021091" + }, + "Queue": { + "Name": "PrimaryPhoneQueue", + "ARN": "" + }, + "Attributes": { + "key1": "value", + "key2": "value" + } + } + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json new file mode 100644 index 00000000..873cccce --- /dev/null +++ b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json @@ -0,0 +1,26 @@ +{ + "awsRegion":"eu-west-1", + "eventID":"00000000-0000-0000-0000-000000000000", + "eventName":"INSERT", + "userIdentity":null, + "recordFormat":"application/json", + "tableName":"examples", + "dynamodb":{ + "ApproximateCreationDateTime":1649809356015, + "Keys":{ + "id":{ + "S":"00000000-0000-0000-0000-000000000000" + } + }, + "NewImage":{ + "id":{ + "S":"00000000-0000-0000-0000-000000000000" + }, + "created":{ + "S":"2022-02-16T15:12:00.14Z" + } + }, + "SizeBytes":292 + }, + "eventSource":"aws:dynamodb" +} diff --git a/lambda-events/src/fixtures/example-dynamodb-event.json b/lambda-events/src/fixtures/example-dynamodb-event.json new file mode 100644 index 00000000..46a17695 --- /dev/null +++ b/lambda-events/src/fixtures/example-dynamodb-event.json @@ -0,0 +1,153 @@ +{ + "Records": [ + { + "eventID": "f07f8ca4b0b26cb9c4e5e77e69f274ee", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "userIdentity":{ + "type":"Service", + "principalId":"dynamodb.amazonaws.com" + }, + "dynamodb": { + "ApproximateCreationDateTime": 1480642020, + "Keys": { + "val": { + "S": "data" + }, + "key": { + "S": "binary" + } + }, + "NewImage": { + "val": { + "S": "data" + }, + "asdf1": { + "B": "AAEqQQ==" + }, + "asdf2": { + "BS": [ + "AAEqQQ==", + "QSoBAA==" + ] + }, + "key": { + "S": "binary" + } + }, + "SequenceNumber": "1405400000000002063282832", + "SizeBytes": 54, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000" + }, + { + "eventID": "f07f8ca4b0b26cb9c4e5e77e42f274ee", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "ApproximateCreationDateTime": 1480642020, + "Keys": { + "val": { + "S": "data" + }, + "key": { + "S": "binary" + } + }, + "NewImage": { + "val": { + "S": "data" + }, + "asdf1": { + "B": "AAEqQQ==" + }, + "b2": { + "B": "test" + }, + "asdf2": { + "BS": [ + "AAEqQQ==", + "QSoBAA==", + "AAEqQQ==" + ] + }, + "key": { + "S": "binary" + }, + "Binary": { + "B": "AAEqQQ==" + }, + "Boolean": { + "BOOL": true + }, + "BinarySet": { + "BS": [ + "AAEqQQ==", + "AAEqQQ==" + ] + }, + "List": { + "L": [ + { + "S": "Cookies" + }, + { + "S": "Coffee" + }, + { + "N": "3.14159" + } + ] + }, + "Map": { + "M": { + "Name": { + "S": "Joe" + }, + "Age": { + "N": "35" + } + } + }, + "FloatNumber": { + "N": "123.45" + }, + "IntegerNumber": { + "N": "123" + }, + "NumberSet": { + "NS": [ + "1234", + "567.8" + ] + }, + "Null": { + "NULL": true + }, + "String": { + "S": "Hello" + }, + "StringSet": { + "SS": [ + "Giraffe", + "Zebra" + ] + }, + "EmptyStringSet": { + "SS": [] + } + }, + "SequenceNumber": "1405400000000002063282832", + "SizeBytes": 54, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-ecr-image-scan-event.json b/lambda-events/src/fixtures/example-ecr-image-scan-event.json new file mode 100644 index 00000000..219b13a3 --- /dev/null +++ b/lambda-events/src/fixtures/example-ecr-image-scan-event.json @@ -0,0 +1,24 @@ +{ + "version": "0", + "id": "01234567-0123-0123-0123-012345678901", + "detail-type": "ECR Image Scan", + "source": "aws.ecr", + "account": "123456789012", + "time": "2019-10-30T21:32:27Z", + "region": "eu-north-1", + "resources": ["arn:aws:ecr:eu-north-1:123456789012:repository/tribble-image-scan-test"], + "detail": { + "scan-status": "COMPLETE", + "repository-name": "tribble-image-scan-test", + "finding-severity-counts": { + "CRITICAL": 10, + "HIGH": 2, + "MEDIUM": 9, + "LOW": 3, + "INFORMATIONAL": 0, + "UNDEFINED": 0 + }, + "image-digest": "sha256:d4a96ee9443e641fc100e763a0c10928720b50c6e3ea3342d05d7c3435fc5355", + "image-tags": ["1572471135"] + } +} diff --git a/lambda-events/src/fixtures/example-firehose-event.json b/lambda-events/src/fixtures/example-firehose-event.json new file mode 100644 index 00000000..8874a568 --- /dev/null +++ b/lambda-events/src/fixtures/example-firehose-event.json @@ -0,0 +1,33 @@ +{ + "invocationId": "invoked123", + "deliveryStreamArn": "aws:lambda:events", + "sourceKinesisStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/test", + "region": "us-west-2", + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000000", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c317a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "subsequenceNumber": 123456 + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000001", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c318a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785155", + "subsequenceNumber": 123457 + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-iot-custom-auth-request.json b/lambda-events/src/fixtures/example-iot-custom-auth-request.json new file mode 100644 index 00000000..582c0e23 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot-custom-auth-request.json @@ -0,0 +1,25 @@ +{ + "token" :"aToken", + "signatureVerified": true, + "protocols": ["tls", "http", "mqtt"], + "protocolData": { + "tls" : { + "serverName": "serverName" + }, + "http": { + "headers": { + "X-Request-ID": "abc123" + }, + "queryString": "?foo=bar" + }, + "mqtt": { + "username": "myUserName", + "password": "bXlQYXNzd29yZA==", + "clientId": "myClientId" + } + }, + "connectionMetadata": { + "id": "e56f08c3-c559-490f-aa9f-7e8427d0f57b" + } +} + diff --git a/lambda-events/src/fixtures/example-iot-custom-auth-response.json b/lambda-events/src/fixtures/example-iot-custom-auth-response.json new file mode 100644 index 00000000..61983975 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot-custom-auth-response.json @@ -0,0 +1,19 @@ +{ + "isAuthenticated":true, + "principalId": "xxxxxxxx", + "disconnectAfterInSeconds": 86400, + "refreshAfterInSeconds": 300, + "policyDocuments": [ + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["iot:Publish"], + "Effect": "Allow", + "Resource": ["arn:aws:iot:us-east-1::topic/customauthtesting"] + } + ] + } + ] +} + diff --git a/lambda-events/src/fixtures/example-iot_1_click-event.json b/lambda-events/src/fixtures/example-iot_1_click-event.json new file mode 100644 index 00000000..1f7eefea --- /dev/null +++ b/lambda-events/src/fixtures/example-iot_1_click-event.json @@ -0,0 +1,30 @@ +{ + "deviceEvent": { + "buttonClicked": { + "clickType": "SINGLE", + "reportedTime": "2018-05-04T23:26:33.747Z" + } + }, + "deviceInfo": { + "attributes": { + "key3": "value3", + "key1": "value1", + "key4": "value4" + }, + "type": "button", + "deviceId": "G030PMXXXXXXXXXX", + "remainingLife": 5.00 + }, + "placementInfo": { + "projectName": "test", + "placementName": "myPlacement", + "attributes": { + "location": "Seattle", + "equipment": "printer" + }, + "devices": { + "myButton": "G030PMXXXXXXXXXX" + } + } +} + diff --git a/lambda-events/src/fixtures/example-iot_button-event.json b/lambda-events/src/fixtures/example-iot_button-event.json new file mode 100644 index 00000000..1ffcef52 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot_button-event.json @@ -0,0 +1,5 @@ +{ + "serialNumber": "ABCDEFG12345", + "clickType": "SINGLE", + "batteryVoltage": "2000 mV" +} diff --git a/lambda-events/src/fixtures/example-kafka-event.json b/lambda-events/src/fixtures/example-kafka-event.json new file mode 100644 index 00000000..97491c88 --- /dev/null +++ b/lambda-events/src/fixtures/example-kafka-event.json @@ -0,0 +1,24 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-west-2:012345678901:cluster/ExampleMSKCluster/e9f754c6-d29a-4430-a7db-958a19fd2c54-4", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "AWSKafkaTopic-0": [ + { + "topic": "AWSKafkaTopic", + "partition": 0, + "offset": 0, + "timestamp": 1595035749700, + "timestampType": "CREATE_TIME", + "key": "OGQ1NTk2YjQtMTgxMy00MjM4LWIyNGItNmRhZDhlM2QxYzBj", + "value": "OGQ1NTk2YjQtMTgxMy00MjM4LWIyNGItNmRhZDhlM2QxYzBj", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + } + ] + } + } + diff --git a/lambda-events/src/fixtures/example-kinesis-event.json b/lambda-events/src/fixtures/example-kinesis-event.json new file mode 100644 index 00000000..be20b6f2 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-event.json @@ -0,0 +1,36 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333333333333333333333333333333333333333333", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480641523.477 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333333333333333333333333333333333333333333", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + }, + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333334444444444444444444444444444444444444", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480841523.477 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333334444444444444444444444444444444444444", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + } + ] +} diff --git a/lambda-events/src/fixtures/example-kinesis-firehose-event.json b/lambda-events/src/fixtures/example-kinesis-firehose-event.json new file mode 100644 index 00000000..8874a568 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-firehose-event.json @@ -0,0 +1,33 @@ +{ + "invocationId": "invoked123", + "deliveryStreamArn": "aws:lambda:events", + "sourceKinesisStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/test", + "region": "us-west-2", + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000000", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c317a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "subsequenceNumber": 123456 + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000001", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c318a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785155", + "subsequenceNumber": 123457 + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-kinesis-firehose-response.json b/lambda-events/src/fixtures/example-kinesis-firehose-response.json new file mode 100644 index 00000000..31950770 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-firehose-response.json @@ -0,0 +1,32 @@ +{ + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "result": "TRANSFORMED_STATE_OK", + "metadata": { + "partitionKeys": {} + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "result": "TRANSFORMED_STATE_DROPPED", + "metadata": { + "partitionKeys": {} + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record3", + "result": "TransformedStateOk", + "metadata": { + "partitionKeys": { + "iamKey1": "iamValue1", + "iamKey2": "iamValue2" + } + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-lex-event.json b/lambda-events/src/fixtures/example-lex-event.json new file mode 100644 index 00000000..ae995d9a --- /dev/null +++ b/lambda-events/src/fixtures/example-lex-event.json @@ -0,0 +1,44 @@ +{ + "currentIntent": { + "name": "intent-name", + "slots": { + "slot name1": "value1", + "slot name2": "value2" + }, + "slotDetails": { + "slot name1": { + "resolutions": [ + { "value1": "resolved value1" }, + { "value2": "resolved value2" } + ], + "originalValue": "original text" + }, + "slot name2": { + "resolutions": [ + { "value1": "resolved value1" }, + { "value2": "resolved value2" } + ], + "originalValue": "original text" + } + }, + "confirmationStatus": "None, Confirmed, or Denied (intent confirmation, if configured)" + }, + "bot": { + "name": "bot name", + "alias": "bot alias", + "version": "bot version" + }, + "userId": "User ID specified in the POST request to Amazon Lex.", + "inputTranscript": "Text used to process the request", + "invocationSource": "FulfillmentCodeHook or DialogCodeHook", + "outputDialogMode": "Text or Voice, based on ContentType request header in runtime API request", + "messageVersion": "1.0", + "sessionAttributes": { + "key1": "value1", + "key2": "value2" + }, + "requestAttributes": { + "key1": "value1", + "key2": "value2" + } +} diff --git a/lambda-events/src/fixtures/example-lex-response.json b/lambda-events/src/fixtures/example-lex-response.json new file mode 100644 index 00000000..b83cea2e --- /dev/null +++ b/lambda-events/src/fixtures/example-lex-response.json @@ -0,0 +1,40 @@ +{ + "sessionAttributes": { + "key1": "value1", + "key2": "value2" + }, + "dialogAction": { + "type": "ElicitIntent, ElicitSlot, ConfirmIntent, Delegate, or Close", + "fulfillmentState": "Fulfilled or Failed", + "message": { + "contentType": "PlainText or SSML", + "content": "message to convey to the user" + }, + "intentName": "intent-name", + "slots": { + "slot-name1": "value1", + "slot-name2": "value2", + "slot-name3": "value3" + }, + "slotToElicit": "slot-name", + "responseCard": { + "version": 3, + "contentType": "application/vnd.amazonaws.card.generic", + "genericAttachments": [ + { + "title": "card-title", + "subTitle": "card-sub-title", + "imageUrl": "URL of the image to be shown", + "attachmentLinkUrl": "URL of the attachment to be associated with the card", + "buttons": [ + { + "text": "button-text", + "value": "value sent to server on button click" + } + ] + } + ] + } + } +} + diff --git a/lambda-events/src/fixtures/example-rabbitmq-event.json b/lambda-events/src/fixtures/example-rabbitmq-event.json new file mode 100644 index 00000000..49a8b5ef --- /dev/null +++ b/lambda-events/src/fixtures/example-rabbitmq-event.json @@ -0,0 +1,52 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:112556298976:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "test::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "eyJ0aW1lb3V0IjowLCJkYXRhIjoiQ1pybWYwR3c4T3Y0YnFMUXhENEUifQ==" + } + ] + } +} + diff --git a/lambda-events/src/fixtures/example-s3-event-with-decoded.json b/lambda-events/src/fixtures/example-s3-event-with-decoded.json new file mode 100644 index 00000000..a9c21c8c --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-event-with-decoded.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.123Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "C3D13FE58DE4C810", + "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "sourcebucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::mybucket" + }, + "object": { + "key": "Happy%20Face.jpg", + "urlDecodedKey": "Happy Face.jpg", + "size": 1024, + "versionId": "version", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "Happy Sequencer" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-s3-event.json b/lambda-events/src/fixtures/example-s3-event.json new file mode 100644 index 00000000..d35f8a3d --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-event.json @@ -0,0 +1,40 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.123Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "C3D13FE58DE4C810", + "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "sourcebucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::mybucket" + }, + "object": { + "key": "Happy%20Face.jpg", + "size": 1024, + "versionId": "version", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "Happy Sequencer" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json new file mode 100644 index 00000000..34aa55b1 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json @@ -0,0 +1,42 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "AssumedRole", + "principalId": "principalId", + "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", + "accountId": "111122223333", + "accessKeyId": "accessKeyId", + "sessionContext": { + "attributes": { + "mfaAuthenticated": "false", + "creationDate": "Wed Mar 10 23:41:52 UTC 2021" + }, + "sessionIssuer": { + "type": "Role", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:role/Admin", + "accountId": "111122223333", + "userName": "Admin" + } + } + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json new file mode 100644 index 00000000..81b0ec71 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json @@ -0,0 +1,29 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.00" +} diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json new file mode 100644 index 00000000..ec920871 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "headObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json new file mode 100644 index 00000000..e1c6692c --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json new file mode 100644 index 00000000..715dffa0 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsV2Context": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-ses-event.json b/lambda-events/src/fixtures/example-ses-event.json new file mode 100644 index 00000000..77bee26a --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-event.json @@ -0,0 +1,101 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "mail": { + "commonHeaders": { + "from": [ + "Amazon Web Services " + ], + "to": [ + "lambda@amazon.com" + ], + "returnPath": "aws@amazon.com", + "messageId": "", + "date": "Mon, 5 Dec 2016 18:40:08 -0800", + "subject": "Test Subject" + }, + "source": "aws@amazon.com", + "timestamp": "1970-01-01T00:00:00.123Z", + "destination": [ + "lambda@amazon.com" + ], + "headers": [ + { + "name": "Return-Path", + "value": "" + }, + { + "name": "Received", + "value": "from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY=" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "From", + "value": "Amazon Web Services " + }, + { + "name": "Date", + "value": "Mon, 5 Dec 2016 18:40:08 -0800" + }, + { + "name": "Message-ID", + "value": "" + }, + { + "name": "Subject", + "value": "Test Subject" + }, + { + "name": "To", + "value": "lambda@amazon.com" + }, + { + "name": "Content-Type", + "value": "multipart/alternative; boundary=94eb2c0742269658b10542f452a9" + } + ], + "headersTruncated": false, + "messageId": "6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1" + }, + "receipt": { + "recipients": [ + "lambda@amazon.com" + ], + "timestamp": "1970-01-01T00:00:00.123Z", + "spamVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "processingTimeMillis": 574, + "action": { + "type": "Lambda", + "invocationType": "Event", + "functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function" + }, + "spfVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + } + } + }, + "eventSource": "aws:ses" + } + ] +} diff --git a/lambda-events/src/fixtures/example-ses-lambda-event.json b/lambda-events/src/fixtures/example-ses-lambda-event.json new file mode 100644 index 00000000..77bee26a --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-lambda-event.json @@ -0,0 +1,101 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "mail": { + "commonHeaders": { + "from": [ + "Amazon Web Services " + ], + "to": [ + "lambda@amazon.com" + ], + "returnPath": "aws@amazon.com", + "messageId": "", + "date": "Mon, 5 Dec 2016 18:40:08 -0800", + "subject": "Test Subject" + }, + "source": "aws@amazon.com", + "timestamp": "1970-01-01T00:00:00.123Z", + "destination": [ + "lambda@amazon.com" + ], + "headers": [ + { + "name": "Return-Path", + "value": "" + }, + { + "name": "Received", + "value": "from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY=" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "From", + "value": "Amazon Web Services " + }, + { + "name": "Date", + "value": "Mon, 5 Dec 2016 18:40:08 -0800" + }, + { + "name": "Message-ID", + "value": "" + }, + { + "name": "Subject", + "value": "Test Subject" + }, + { + "name": "To", + "value": "lambda@amazon.com" + }, + { + "name": "Content-Type", + "value": "multipart/alternative; boundary=94eb2c0742269658b10542f452a9" + } + ], + "headersTruncated": false, + "messageId": "6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1" + }, + "receipt": { + "recipients": [ + "lambda@amazon.com" + ], + "timestamp": "1970-01-01T00:00:00.123Z", + "spamVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "processingTimeMillis": 574, + "action": { + "type": "Lambda", + "invocationType": "Event", + "functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function" + }, + "spfVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + } + } + }, + "eventSource": "aws:ses" + } + ] +} diff --git a/lambda-events/src/fixtures/example-ses-s3-event.json b/lambda-events/src/fixtures/example-ses-s3-event.json new file mode 100644 index 00000000..1f2deb2e --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-s3-event.json @@ -0,0 +1,115 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "receipt": { + "timestamp": "2015-09-11T20:32:33.936Z", + "processingTimeMillis": 406, + "recipients": [ + "recipient@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "action": { + "type": "S3", + "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic", + "bucketName": "my-S3-bucket", + "objectKey": "email" + } + }, + "mail": { + "timestamp": "2015-09-11T20:32:33.936Z", + "source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "messageId": "d6iitobk75ur44p8kdnnp7g2n800", + "destination": [ + "recipient@example.com" + ], + "headersTruncated": false, + "headers": [ + { + "name": "Return-Path", + "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" + }, + { + "name": "Received", + "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" + }, + { + "name": "From", + "value": "sender@example.com" + }, + { + "name": "To", + "value": "recipient@example.com" + }, + { + "name": "Subject", + "value": "Example subject" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "7bit" + }, + { + "name": "Date", + "value": "Fri, 11 Sep 2015 20:32:32 +0000" + }, + { + "name": "Message-ID", + "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" + }, + { + "name": "X-SES-Outgoing", + "value": "2015.09.11-54.240.9.183" + }, + { + "name": "Feedback-ID", + "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" + } + ], + "commonHeaders": { + "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "from": [ + "sender@example.com" + ], + "date": "Fri, 11 Sep 2015 20:32:32 +0000", + "to": [ + "recipient@example.com" + ], + "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", + "subject": "Example subject" + } + } + }, + "eventSource": "aws:ses" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-ses-sns-event.json b/lambda-events/src/fixtures/example-ses-sns-event.json new file mode 100644 index 00000000..bfededc1 --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-sns-event.json @@ -0,0 +1,113 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "receipt": { + "timestamp": "2015-09-11T20:32:33.936Z", + "processingTimeMillis": 222, + "recipients": [ + "recipient@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "action": { + "type": "SNS", + "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic" + } + }, + "mail": { + "timestamp": "2015-09-11T20:32:33.936Z", + "source": "61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com", + "messageId": "d6iitobk75ur44p8kdnnp7g2n800", + "destination": [ + "recipient@example.com" + ], + "headersTruncated": false, + "headers": [ + { + "name": "Return-Path", + "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" + }, + { + "name": "Received", + "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" + }, + { + "name": "From", + "value": "sender@example.com" + }, + { + "name": "To", + "value": "recipient@example.com" + }, + { + "name": "Subject", + "value": "Example subject" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "7bit" + }, + { + "name": "Date", + "value": "Fri, 11 Sep 2015 20:32:32 +0000" + }, + { + "name": "Message-ID", + "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" + }, + { + "name": "X-SES-Outgoing", + "value": "2015.09.11-54.240.9.183" + }, + { + "name": "Feedback-ID", + "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" + } + ], + "commonHeaders": { + "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "from": [ + "sender@example.com" + ], + "date": "Fri, 11 Sep 2015 20:32:32 +0000", + "to": [ + "recipient@example.com" + ], + "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", + "subject": "Example subject" + } + } + }, + "eventSource": "aws:ses" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event-obj.json b/lambda-events/src/fixtures/example-sns-event-obj.json new file mode 100644 index 00000000..c3144618 --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event-obj.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "{\"foo\":\"Hello world!\",\"bar\":123}", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event-pascal-case.json b/lambda-events/src/fixtures/example-sns-event-pascal-case.json new file mode 100644 index 00000000..ec1b125e --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event-pascal-case.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "Hello\r\nworld!", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertUrl" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeUrl" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event.json b/lambda-events/src/fixtures/example-sns-event.json new file mode 100644 index 00000000..091aa579 --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "Hello\r\nworld!", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sqs-batch-response.json b/lambda-events/src/fixtures/example-sqs-batch-response.json new file mode 100644 index 00000000..50cb377b --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-batch-response.json @@ -0,0 +1,10 @@ +{ + "batchItemFailures": [ + { + "itemIdentifier": "id2" + }, + { + "itemIdentifier": "id4" + } + ] +} diff --git a/lambda-events/src/fixtures/example-sqs-event-obj.json b/lambda-events/src/fixtures/example-sqs-event-obj.json new file mode 100644 index 00000000..76428275 --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-event-obj.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "messageId" : "MessageID_1", + "receiptHandle" : "MessageReceiptHandle", + "body" : "{\"a\":\"Test\",\"b\":123}", + "md5OfBody" : "fce0ea8dd236ccb3ed9b37dae260836f", + "md5OfMessageAttributes" : "582c92c5c5b6ac403040a4f3ab3115c9", + "eventSourceARN": "arn:aws:sqs:us-west-2:123456789012:SQSQueue", + "eventSource": "aws:sqs", + "awsRegion": "us-west-2", + "attributes" : { + "ApproximateReceiveCount" : "2", + "SentTimestamp" : "1520621625029", + "SenderId" : "AROAIWPX5BD2BHG722MW4:sender", + "ApproximateFirstReceiveTimestamp" : "1520621634884" + }, + "messageAttributes" : { + "Attribute3" : { + "binaryValue" : "MTEwMA==", + "stringListValues" : ["abc", "123"], + "binaryListValues" : ["MA==", "MQ==", "MA=="], + "dataType" : "Binary" + }, + "Attribute2" : { + "stringValue" : "123", + "stringListValues" : [ ], + "binaryListValues" : ["MQ==", "MA=="], + "dataType" : "Number" + }, + "Attribute1" : { + "stringValue" : "AttributeValue1", + "stringListValues" : [ ], + "binaryListValues" : [ ], + "dataType" : "String" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sqs-event.json b/lambda-events/src/fixtures/example-sqs-event.json new file mode 100644 index 00000000..732b65c1 --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-event.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "messageId" : "MessageID_1", + "receiptHandle" : "MessageReceiptHandle", + "body" : "Message Body", + "md5OfBody" : "fce0ea8dd236ccb3ed9b37dae260836f", + "md5OfMessageAttributes" : "582c92c5c5b6ac403040a4f3ab3115c9", + "eventSourceARN": "arn:aws:sqs:us-west-2:123456789012:SQSQueue", + "eventSource": "aws:sqs", + "awsRegion": "us-west-2", + "attributes" : { + "ApproximateReceiveCount" : "2", + "SentTimestamp" : "1520621625029", + "SenderId" : "AROAIWPX5BD2BHG722MW4:sender", + "ApproximateFirstReceiveTimestamp" : "1520621634884" + }, + "messageAttributes" : { + "Attribute3" : { + "binaryValue" : "MTEwMA==", + "stringListValues" : ["abc", "123"], + "binaryListValues" : ["MA==", "MQ==", "MA=="], + "dataType" : "Binary" + }, + "Attribute2" : { + "stringValue" : "123", + "stringListValues" : [ ], + "binaryListValues" : ["MQ==", "MA=="], + "dataType" : "Number" + }, + "Attribute1" : { + "stringValue" : "AttributeValue1", + "stringListValues" : [ ], + "binaryListValues" : [ ], + "dataType" : "String" + } + } + } + ] +} + diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs new file mode 100644 index 00000000..fa6cce05 --- /dev/null +++ b/lambda-events/src/lib.rs @@ -0,0 +1,175 @@ +extern crate base64; +extern crate http_serde; +#[cfg(test)] +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate serde_derive; +#[cfg(test)] +#[macro_use] +extern crate serde_json; + +// Crates with types that we use publicly. Reexported for ease of interoperability. +pub extern crate bytes; +pub extern crate chrono; +pub extern crate http; +pub extern crate http_body; +pub extern crate query_map; +pub extern crate serde; +#[cfg(not(test))] +pub extern crate serde_json; + +mod custom_serde; +/// Encodings used in AWS Lambda json event values. +pub mod encodings; +pub mod time_window; + +/// AWS Lambda event definitions. +pub mod event; + +/// AWS Lambda event definitions for activemq. +#[cfg(feature = "activemq")] +pub use event::activemq; + +/// AWS Lambda event definitions for alb. +#[cfg(feature = "alb")] +pub use event::alb; +/// AWS Lambda event definitions for apigw. +#[cfg(feature = "apigw")] +pub use event::apigw; + +/// AWS Lambda event definitions for appsync. +#[cfg(feature = "appsync")] +pub use event::appsync; + +/// AWS Lambda event definitions for autoscaling. +#[cfg(feature = "autoscaling")] +pub use event::autoscaling; + +/// AWS Lambda event definitions for chime_bot. +#[cfg(feature = "chime_bot")] +pub use event::chime_bot; + +/// AWS Lambda event definitions for clientvpn. +#[cfg(feature = "clientvpn")] +pub use event::clientvpn; + +/// CloudWatch Events payload +#[cfg(feature = "cloudwatch_events")] +pub use event::cloudwatch_events; + +/// AWS Lambda event definitions for cloudwatch_logs. +#[cfg(feature = "cloudwatch_logs")] +pub use event::cloudwatch_logs; + +/// AWS Lambda event definitions for code_commit. +#[cfg(feature = "code_commit")] +pub use event::code_commit; + +/// AWS Lambda event definitions for codebuild. +#[cfg(feature = "codebuild")] +pub use event::codebuild; + +/// AWS Lambda event definitions for codedeploy. +#[cfg(feature = "codedeploy")] +pub use event::codedeploy; + +/// AWS Lambda event definitions for codepipeline_cloudwatch. +#[cfg(feature = "codepipeline_cloudwatch")] +pub use event::codepipeline_cloudwatch; + +/// AWS Lambda event definitions for codepipeline_job. +#[cfg(feature = "codepipeline_job")] +pub use event::codepipeline_job; + +/// AWS Lambda event definitions for cognito. +#[cfg(feature = "cognito")] +pub use event::cognito; + +/// AWS Lambda event definitions for config. +#[cfg(feature = "config")] +pub use event::config; + +/// AWS Lambda event definitions for connect. +#[cfg(feature = "connect")] +pub use event::connect; + +/// AWS Lambda event definitions for dynamodb. +#[cfg(feature = "dynamodb")] +pub use event::dynamodb; + +/// AWS Lambda event definitions for ecr_scan. +#[cfg(feature = "ecr_scan")] +pub use event::ecr_scan; + +/// AWS Lambda event definitions for firehose. +#[cfg(feature = "firehose")] +pub use event::firehose; + +/// AWS Lambda event definitions for iam. +#[cfg(feature = "iam")] +pub use event::iam; + +/// AWS Lambda event definitions for iot. +#[cfg(feature = "iot")] +pub use event::iot; + +/// AWS Lambda event definitions for iot_1_click. +#[cfg(feature = "iot_1_click")] +pub use event::iot_1_click; + +/// AWS Lambda event definitions for iot_button. +#[cfg(feature = "iot_button")] +pub use event::iot_button; + +/// AWS Lambda event definitions for iot_deprecated. +#[cfg(feature = "iot_deprecated")] +pub use event::iot_deprecated; + +/// AWS Lambda event definitions for kafka. +#[cfg(feature = "kafka")] +pub use event::kafka; + +/// AWS Lambda event definitions for kinesis. +#[cfg(feature = "kinesis")] +pub use event::kinesis; + +/// AWS Lambda event definitions for kinesis_analytics. +#[cfg(feature = "kinesis_analytics")] +pub use event::kinesis::analytics as kinesis_analytics; + +/// AWS Lambda event definitions for lambda_function_urls. +#[cfg(feature = "lambda_function_urls")] +pub use event::lambda_function_urls; + +/// AWS Lambda event definitions for lex. +#[cfg(feature = "lex")] +pub use event::lex; + +/// AWS Lambda event definitions for rabbitmq. +#[cfg(feature = "rabbitmq")] +pub use event::rabbitmq; + +/// AWS Lambda event definitions for s3. +#[cfg(feature = "s3")] +pub use event::s3; + +/// AWS Lambda event definitions for s3_batch_job. +#[cfg(feature = "s3")] +pub use event::s3::batch_job as s3_batch_job; + +/// AWS Lambda event definitions for ses. +#[cfg(feature = "ses")] +pub use event::ses; + +/// AWS Lambda event definitions for SNS. +#[cfg(feature = "sns")] +pub use event::sns; + +/// AWS Lambda event definitions for SQS. +#[cfg(feature = "sqs")] +pub use event::sqs; + +/// AWS Lambda event definitions for streams. +#[cfg(feature = "streams")] +pub use event::streams; diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs new file mode 100644 index 00000000..9418dc8c --- /dev/null +++ b/lambda-events/src/time_window.rs @@ -0,0 +1,81 @@ +use chrono::{DateTime, Utc}; +use std::collections::HashMap; + +use crate::custom_serde::deserialize_lambda_map; + +/// `Window` is the object that captures the time window for the records in the event when using the tumbling windows feature +/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Window { + pub start: DateTime, + pub end: DateTime, +} + +impl Default for Window { + fn default() -> Self { + Window { + start: Utc::now(), + end: Utc::now(), + } + } +} + +/// `TimeWindowProperties` is the object that captures properties that relate to the tumbling windows feature +/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowProperties { + /// Time window for the records in the event. + pub window: Window, + /// State being built up to this invoke in the time window. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub state: HashMap, + /// Shard id of the records + #[serde(default)] + pub shard_id: Option, + /// The event source ARN of the service that generated the event (eg. DynamoDB or Kinesis) + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + /// Set to true for the last invoke of the time window. + /// Subsequent invoke will start a new time window along with a fresh state. + pub is_final_invoke_for_window: bool, + /// Set to true if window is terminated prematurely. + /// Subsequent invoke will continue the same window with a fresh state. + pub is_window_terminated_early: bool, +} + +/// `TimeWindowEventResponseProperties` is the object that captures response properties that relate to the tumbling windows feature +/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEventResponseProperties { + /// State being built up to this invoke in the time window. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub state: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + fn test_window_deserializer() { + let v = serde_json::json!({ + "start": "2020-12-09T07:04:00Z", + "end": "2020-12-09T07:06:00Z", + }); + + let parsed: Window = serde_json::from_value(v).unwrap(); + assert_eq!("2020-12-09T07:04:00+00:00", &parsed.start.to_rfc3339()); + assert_eq!("2020-12-09T07:06:00+00:00", &parsed.end.to_rfc3339()); + } +} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 73d05675..289aec17 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -39,7 +39,8 @@ url = "2.2" percent-encoding = "2.2" [dependencies.aws_lambda_events] -version = "0.8.3" +path = "../lambda-events" +version = "0.9.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 2711a343..fe9b5fb1 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -192,7 +192,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { headers.extend(ag.headers); update_xray_trace_id_header(&mut headers); - let base64 = ag.is_base64_encoded.unwrap_or_default(); + let base64 = ag.is_base64_encoded; let mut req = builder .body( ag.body @@ -306,7 +306,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< headers.extend(ag.headers); update_xray_trace_id_header(&mut headers); - let base64 = ag.is_base64_encoded.unwrap_or_default(); + let base64 = ag.is_base64_encoded; let mut req = builder .body( ag.body diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 5a5f3e9f..1a2ede5c 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -65,8 +65,8 @@ impl LambdaResponse { #[cfg(feature = "apigw_rest")] RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), headers: headers.clone(), multi_value_headers: headers, }), @@ -86,8 +86,8 @@ impl LambdaResponse { LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), cookies, headers: headers.clone(), multi_value_headers: headers, @@ -109,8 +109,8 @@ impl LambdaResponse { #[cfg(feature = "apigw_websockets")] RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), headers: headers.clone(), multi_value_headers: headers, }), From fbf212f4eef8c0fd8bd87f87998239fa17bc2b23 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 5 May 2023 14:39:50 -0700 Subject: [PATCH 182/394] Copy license from old events repo (#648) As stated in the license, the copyright notice should be included in copies. Signed-off-by: David Calavera --- lambda-events/LICENSE | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 lambda-events/LICENSE diff --git a/lambda-events/LICENSE b/lambda-events/LICENSE new file mode 100644 index 00000000..2329b573 --- /dev/null +++ b/lambda-events/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2018 Sam Rijs and Christian Legnitto +Copyright 2023 Amazon.com, Inc. or its affiliates + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 82bd8aa64633d6670cc342fdbef827198b09278a Mon Sep 17 00:00:00 2001 From: Jakub Wieczorek Date: Wed, 10 May 2023 21:53:29 +0200 Subject: [PATCH 183/394] Upgrade dependencies in lambda-events (#650) * Upgrade base64 to 0.21. * Upgrade serde_with. * Fix a test compilation issue. --- lambda-events/Cargo.toml | 4 ++-- lambda-events/src/custom_serde/mod.rs | 8 +++++--- lambda-events/src/encodings.rs | 12 ++++++++++-- lambda-events/src/event/cloudwatch_logs/mod.rs | 17 +++++++++++------ lambda-events/src/event/dynamodb/attributes.rs | 7 +++++-- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 225d9d8f..57be6bde 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -15,13 +15,13 @@ keywords = ["lambda", "aws", "amazon", "events", "S3"] categories = ["api-bindings", "encoding", "web-programming"] [dependencies] -base64 = "0.13" +base64 = "0.21" http = "0.2" http-body = "0.4" http-serde = "^1" serde = "^1" serde_derive = "^1" -serde_with = { version = "^2", features = ["json"], optional = true } +serde_with = { version = "^3", features = ["json"], optional = true } serde_json = "^1" serde_dynamo = { version = "^4.1", optional = true } bytes = { version = "1", features = ["serde"] } diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 964f460e..e2c44af9 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -1,5 +1,5 @@ #[allow(unused)] -use base64::{decode, encode}; +use base64::Engine; use chrono::{DateTime, Duration, TimeZone, Utc}; use serde; use serde::de::{Deserialize, Deserializer, Error as DeError}; @@ -122,14 +122,16 @@ where D: Deserializer<'de>, { let s: String = String::deserialize(deserializer)?; - decode(s).map_err(DeError::custom) + base64::engine::general_purpose::STANDARD + .decode(s) + .map_err(DeError::custom) } pub(crate) fn serialize_base64(value: &[u8], serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&encode(value)) + serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(value)) } /// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. diff --git a/lambda-events/src/encodings.rs b/lambda-events/src/encodings.rs index ecd32340..42dd15a7 100644 --- a/lambda-events/src/encodings.rs +++ b/lambda-events/src/encodings.rs @@ -192,8 +192,14 @@ impl Body { /// Panics when aws communicates to handler that request is base64 encoded but /// it can not be base64 decoded pub fn from_maybe_encoded(is_base64_encoded: bool, body: &str) -> Body { + use base64::Engine; + if is_base64_encoded { - Body::from(::base64::decode(body).expect("failed to decode aws base64 encoded body")) + Body::from( + ::base64::engine::general_purpose::STANDARD + .decode(body) + .expect("failed to decode aws base64 encoded body"), + ) } else { Body::from(body) } @@ -289,7 +295,9 @@ impl Serialize for Body { Body::Text(data) => { serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?) } - Body::Binary(data) => serializer.collect_str(&Base64Display::with_config(data, base64::STANDARD)), + Body::Binary(data) => { + serializer.collect_str(&Base64Display::new(data, &base64::engine::general_purpose::STANDARD)) + } Body::Empty => serializer.serialize_unit(), } } diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index e8371c8f..053974ec 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -67,13 +67,17 @@ impl<'de> Deserialize<'de> for AwsLogs { where V: MapAccess<'de>, { + use base64::Engine; + let mut data = None; while let Some(key) = map.next_key()? { match key { "data" => { - let bytes = map - .next_value::() - .and_then(|string| base64::decode(string).map_err(Error::custom))?; + let bytes = map.next_value::().and_then(|string| { + base64::engine::general_purpose::STANDARD + .decode(string) + .map_err(Error::custom) + })?; let bytes = flate2::read::GzDecoder::new(&bytes[..]); let mut de = serde_json::Deserializer::from_reader(BufReader::new(bytes)); @@ -98,7 +102,7 @@ impl Serialize for AwsLogs { where S: Serializer, { - let base = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD_NO_PAD); + let base = base64::write::EncoderWriter::new(Vec::new(), &base64::engine::general_purpose::STANDARD_NO_PAD); let mut gzip = flate2::write::GzEncoder::new(base, flate2::Compression::default()); serde_json::to_writer(&mut gzip, &self.data).map_err(SeError::custom)?; @@ -125,7 +129,8 @@ mod test { } }"#; let event: LogsEvent = serde_json::from_str(json).expect("failed to deserialize"); - let data = event.aws_logs.data.clone(); + + let data = event.clone().aws_logs.data; assert_eq!("DATA_MESSAGE", data.message_type); assert_eq!("123456789012", data.owner); assert_eq!("/aws/lambda/echo-nodejs", data.log_group); @@ -140,7 +145,7 @@ mod test { assert_eq!(1552518348220, data.log_events[0].timestamp); assert_eq!("REPORT RequestId: 6234bffe-149a-b642-81ff-2e8e376d8aff\tDuration: 46.84 ms\tBilled Duration: 47 ms \tMemory Size: 192 MB\tMax Memory Used: 72 MB\t\n", data.log_events[0].message); - let new_json = serde_json::to_string_pretty(&event).unwrap(); + let new_json: String = serde_json::to_string_pretty(&event).unwrap(); let new_event: LogsEvent = serde_json::from_str(&new_json).expect("failed to deserialize"); assert_eq!(new_event, event); } diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index 74449fe8..d2f32caf 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -1,3 +1,4 @@ +use base64::Engine; use event::serde_dynamo::AttributeValue; use std::collections::HashMap; @@ -62,7 +63,9 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::B(ref b) => { - let expected = base64::decode("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk").unwrap(); + let expected = base64::engine::general_purpose::STANDARD + .decode("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk") + .unwrap(); assert_eq!(&expected, b) } other => panic!("unexpected value {:?}", other), @@ -137,7 +140,7 @@ mod test { AttributeValue::Bs(ref s) => { let expected = vec!["U3Vubnk=", "UmFpbnk=", "U25vd3k="] .into_iter() - .flat_map(base64::decode) + .flat_map(|s| base64::engine::general_purpose::STANDARD.decode(s)) .collect::>(); assert_eq!(&expected, s); } From 7c932659c74744d97f8a02de749e4fbf88008057 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Tue, 16 May 2023 00:22:59 +0900 Subject: [PATCH 184/394] Tag `apigw` struct fields with proper `serde(skip_serializing_if)` (#654) --- lambda-events/src/event/apigw/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 80063911..88b44fec 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -81,6 +81,7 @@ where pub account_id: Option, #[serde(default)] pub resource_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub operation_name: Option, #[serde(default)] pub stage: Option, @@ -125,6 +126,7 @@ pub struct ApiGatewayV2httpRequest { pub raw_path: Option, #[serde(default)] pub raw_query_string: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub cookies: Option>, #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] @@ -133,14 +135,17 @@ pub struct ApiGatewayV2httpRequest { default, deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" )] + #[serde(skip_serializing_if = "QueryMap::is_empty")] pub query_string_parameters: QueryMap, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub path_parameters: HashMap, pub request_context: ApiGatewayV2httpRequestContext, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, #[serde(default)] pub is_base64_encoded: bool, @@ -163,6 +168,7 @@ where #[serde(default)] pub request_id: Option, #[serde(bound = "", default)] + #[serde(skip_serializing_if = "Option::is_none")] pub authorizer: Option>, /// The API Gateway HTTP API Id #[serde(default)] @@ -176,6 +182,7 @@ where pub time: Option, pub time_epoch: i64, pub http: ApiGatewayV2httpRequestContextHttpDescription, + #[serde(skip_serializing_if = "Option::is_none")] pub authentication: Option, } @@ -187,11 +194,14 @@ where T1: DeserializeOwned, T1: Serialize, { + #[serde(skip_serializing_if = "Option::is_none")] pub jwt: Option, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] #[serde(bound = "")] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub lambda: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub iam: Option, } @@ -202,6 +212,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub claims: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub scopes: Option>, } @@ -215,6 +226,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { pub account_id: Option, #[serde(default)] pub caller_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub cognito_identity: Option, #[serde(default)] pub principal_org_id: Option, From b19a79ef64e6b5682cedcd22b82a33b49eadbfb3 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:23 -0700 Subject: [PATCH 185/394] Fix support for APIGW console requests (#657) Test requests from the APIGW console don't include time epoch. This change makes that field to be deserialized with a default value in those cases. Signed-off-by: David Calavera --- lambda-events/src/event/apigw/mod.rs | 12 ++++ .../example-apigw-console-request.json | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-console-request.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 88b44fec..196bff86 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -107,6 +107,7 @@ where pub http_method: Method, #[serde(default)] pub request_time: Option, + #[serde(default)] pub request_time_epoch: i64, /// The API Gateway rest API Id #[serde(default)] @@ -180,6 +181,7 @@ where pub domain_prefix: Option, #[serde(default)] pub time: Option, + #[serde(default)] pub time_epoch: i64, pub http: ApiGatewayV2httpRequestContextHttpDescription, #[serde(skip_serializing_if = "Option::is_none")] @@ -920,4 +922,14 @@ mod test { let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_console_request() { + let data = include_bytes!("../../fixtures/example-apigw-console-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-apigw-console-request.json b/lambda-events/src/fixtures/example-apigw-console-request.json new file mode 100644 index 00000000..e56f4cd7 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-console-request.json @@ -0,0 +1,57 @@ +{ + "body": "{\"test\":\"body\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.{dns_suffix}", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "apiKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890" + } +} \ No newline at end of file From dbdf822c16914c53f787f3cdba13589ebc2fc93e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:36 -0700 Subject: [PATCH 186/394] Fix doc about curl command (#653) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 157c1c98..12dbf523 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ An simpler alternative is to cURL the following endpoint based on the address an ```bash curl -v -X POST \ - 'http://127.0.0.1:9001/lambda-url/' \ + 'http://127.0.0.1:9001/lambda-url//' \ -H 'content-type: application/json' \ -d '{ "command": "hi" }' ``` From 007f91565138f94e5efbfc84368a28adf2803cfd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:50 -0700 Subject: [PATCH 187/394] Add missing RawHttpPath extension to WebSockets (#656) Signed-off-by: David Calavera --- lambda-http/src/request.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index fe9b5fb1..5ed3effe 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -277,7 +277,8 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< .get(http::header::HOST) .and_then(|s| s.to_str().ok()) .or(ag.request_context.domain_name.as_deref()); - let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + let raw_path = ag.path.unwrap_or_default(); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); let builder = http::Request::builder() .uri(build_request_uri( @@ -286,6 +287,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< host, Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)), )) + .extension(RawHttpPath(raw_path)) // multi-valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred From a7329a452b34dc8e4927ba30605c3d8716d1db2f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 28 May 2023 14:34:23 -0700 Subject: [PATCH 188/394] Improvements in event compilation (#659) * Make compiling chrono conditional It's not necessary for all event types. Signed-off-by: David Calavera * Make compiling query_map conditional. It's not used in all events. Signed-off-by: David Calavera * Test individual features - Convert package to 2021 edition. - Format imports. Signed-off-by: David Calavera * Remove old idioms So people don't have to learn 2015 idioms like `extern crate` and implicit macro imports. Signed-off-by: David Calavera * Bump crate version Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 9 + Makefile | 44 +++ lambda-events/Cargo.toml | 57 ++- .../src/custom_serde/codebuild_time.rs | 11 +- .../src/custom_serde/float_unix_epoch.rs | 6 +- lambda-events/src/custom_serde/headers.rs | 13 +- lambda-events/src/custom_serde/http_method.rs | 11 +- lambda-events/src/custom_serde/mod.rs | 277 +------------ .../src/{encodings.rs => encodings/http.rs} | 121 +----- lambda-events/src/encodings/mod.rs | 37 ++ lambda-events/src/encodings/time.rs | 363 ++++++++++++++++++ lambda-events/src/event/activemq/mod.rs | 6 +- lambda-events/src/event/alb/mod.rs | 13 +- lambda-events/src/event/apigw/mod.rs | 4 +- lambda-events/src/event/appsync/mod.rs | 7 +- lambda-events/src/event/autoscaling/mod.rs | 7 +- lambda-events/src/event/chime_bot/mod.rs | 1 + lambda-events/src/event/clientvpn/mod.rs | 4 +- .../src/event/cloudwatch_events/cloudtrail.rs | 3 +- .../src/event/cloudwatch_events/codedeploy.rs | 3 +- .../event/cloudwatch_events/codepipeline.rs | 3 +- .../src/event/cloudwatch_events/ec2.rs | 3 +- .../src/event/cloudwatch_events/emr.rs | 3 +- .../src/event/cloudwatch_events/gamelift.rs | 3 +- .../src/event/cloudwatch_events/glue.rs | 3 +- .../src/event/cloudwatch_events/health.rs | 4 +- .../src/event/cloudwatch_events/kms.rs | 3 +- .../src/event/cloudwatch_events/macie.rs | 4 +- .../src/event/cloudwatch_events/mod.rs | 2 +- .../src/event/cloudwatch_events/opsworks.rs | 3 +- .../src/event/cloudwatch_events/signin.rs | 3 +- .../src/event/cloudwatch_events/sms.rs | 3 +- .../src/event/cloudwatch_events/ssm.rs | 4 +- .../src/event/cloudwatch_events/tag.rs | 3 +- .../event/cloudwatch_events/trustedadvisor.rs | 4 +- .../src/event/cloudwatch_logs/mod.rs | 2 +- lambda-events/src/event/code_commit/mod.rs | 3 +- lambda-events/src/event/codebuild/mod.rs | 6 +- lambda-events/src/event/codedeploy/mod.rs | 3 +- .../src/event/codepipeline_cloudwatch/mod.rs | 3 +- .../src/event/codepipeline_job/mod.rs | 4 +- lambda-events/src/event/cognito/mod.rs | 7 +- lambda-events/src/event/config/mod.rs | 4 +- lambda-events/src/event/connect/mod.rs | 6 +- .../src/event/dynamodb/attributes.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 6 +- lambda-events/src/event/ecr_scan/mod.rs | 4 +- lambda-events/src/event/firehose/mod.rs | 9 +- lambda-events/src/event/iam/mod.rs | 2 + lambda-events/src/event/iot/mod.rs | 5 +- lambda-events/src/event/iot_1_click/mod.rs | 7 +- lambda-events/src/event/iot_button/mod.rs | 4 +- lambda-events/src/event/iot_deprecated/mod.rs | 1 + lambda-events/src/event/kafka/mod.rs | 6 +- lambda-events/src/event/kinesis/analytics.rs | 1 + lambda-events/src/event/kinesis/event.rs | 3 +- .../src/event/lambda_function_urls/mod.rs | 4 +- lambda-events/src/event/lex/mod.rs | 6 +- lambda-events/src/event/mod.rs | 2 - lambda-events/src/event/rabbitmq/mod.rs | 7 +- lambda-events/src/event/s3/batch_job.rs | 2 + lambda-events/src/event/s3/event.rs | 6 +- lambda-events/src/event/s3/object_lambda.rs | 7 +- lambda-events/src/event/ses/mod.rs | 3 +- lambda-events/src/event/sns/mod.rs | 5 +- lambda-events/src/event/sqs/mod.rs | 5 +- lambda-events/src/event/streams/mod.rs | 2 + lambda-events/src/lib.rs | 26 +- lambda-events/src/time_window.rs | 3 +- lambda-http/Cargo.toml | 2 +- 70 files changed, 653 insertions(+), 560 deletions(-) rename lambda-events/src/{encodings.rs => encodings/http.rs} (75%) create mode 100644 lambda-events/src/encodings/mod.rs create mode 100644 lambda-events/src/encodings/time.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index dbf9a0ae..3a56e597 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -26,3 +26,12 @@ jobs: with: package: aws_lambda_events toolchain: ${{ matrix.toolchain}} + check-event-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Test individual event features + run: make check-event-features diff --git a/Makefile b/Makefile index cb00545c..544d08b7 100644 --- a/Makefile +++ b/Makefile @@ -60,3 +60,47 @@ invoke-integration-api-%: curl -X POST -d '{"command": "hello"}' $(API_URL)/trait/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2-trait/post + +# Test individual event features to ensure optional dependencies +# are correctly loaded when all default features are disabled. +check-event-features: + cargo test --package aws_lambda_events --no-default-features --features activemq + cargo test --package aws_lambda_events --no-default-features --features alb + cargo test --package aws_lambda_events --no-default-features --features apigw + cargo test --package aws_lambda_events --no-default-features --features appsync + cargo test --package aws_lambda_events --no-default-features --features autoscaling + cargo test --package aws_lambda_events --no-default-features --features chime_bot + cargo test --package aws_lambda_events --no-default-features --features clientvpn + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_logs + cargo test --package aws_lambda_events --no-default-features --features code_commit + cargo test --package aws_lambda_events --no-default-features --features codebuild + cargo test --package aws_lambda_events --no-default-features --features codedeploy + cargo test --package aws_lambda_events --no-default-features --features codepipeline_cloudwatch + cargo test --package aws_lambda_events --no-default-features --features codepipeline_job + cargo test --package aws_lambda_events --no-default-features --features cognito + cargo test --package aws_lambda_events --no-default-features --features config + cargo test --package aws_lambda_events --no-default-features --features connect + cargo test --package aws_lambda_events --no-default-features --features dynamodb + cargo test --package aws_lambda_events --no-default-features --features ecr_scan + cargo test --package aws_lambda_events --no-default-features --features firehose + cargo test --package aws_lambda_events --no-default-features --features iam + cargo test --package aws_lambda_events --no-default-features --features iot + cargo test --package aws_lambda_events --no-default-features --features iot_1_click + cargo test --package aws_lambda_events --no-default-features --features iot_button + cargo test --package aws_lambda_events --no-default-features --features iot_deprecated + cargo test --package aws_lambda_events --no-default-features --features kafka + cargo test --package aws_lambda_events --no-default-features --features kinesis + cargo test --package aws_lambda_events --no-default-features --features kinesis_analytics + cargo test --package aws_lambda_events --no-default-features --features lambda_function_urls + cargo test --package aws_lambda_events --no-default-features --features lex + cargo test --package aws_lambda_events --no-default-features --features rabbitmq + cargo test --package aws_lambda_events --no-default-features --features s3 + cargo test --package aws_lambda_events --no-default-features --features s3_batch_job + cargo test --package aws_lambda_events --no-default-features --features ses + cargo test --package aws_lambda_events --no-default-features --features sns + cargo test --package aws_lambda_events --no-default-features --features sqs + cargo test --package aws_lambda_events --no-default-features --features streams + +fmt: + cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 57be6bde..b1108c63 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.9.0" +version = "0.10.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", @@ -13,29 +13,26 @@ repository = "https://github.com/awslabs/aws-lambda-rust-runtime" readme = "README.md" keywords = ["lambda", "aws", "amazon", "events", "S3"] categories = ["api-bindings", "encoding", "web-programming"] +edition = "2021" [dependencies] base64 = "0.21" -http = "0.2" -http-body = "0.4" -http-serde = "^1" -serde = "^1" -serde_derive = "^1" +http = { version = "0.2", optional = true } +http-body = { version = "0.4", optional = true } +http-serde = { version = "^1", optional = true } +serde = { version = "^1", features = ["derive"] } serde_with = { version = "^3", features = ["json"], optional = true } serde_json = "^1" serde_dynamo = { version = "^4.1", optional = true } -bytes = { version = "1", features = ["serde"] } +bytes = { version = "1", features = ["serde"], optional = true } chrono = { version = "0.4.23", default-features = false, features = [ "clock", "serde", "std", -] } -query_map = { version = "^0.6", features = ["serde", "url-query"] } +], optional = true } +query_map = { version = "^0.6", features = ["serde", "url-query"], optional = true } flate2 = { version = "1.0.24", optional = true } -[dev-dependencies] -pretty_assertions = "1.3" - [features] default = [ "activemq", @@ -78,40 +75,40 @@ default = [ ] activemq = [] -alb = [] -apigw = [] +alb = ["bytes", "http", "http-body", "http-serde", "query_map"] +apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] appsync = [] -autoscaling = [] -chime_bot = [] +autoscaling = ["chrono"] +chime_bot = ["chrono"] clientvpn = [] -cloudwatch_events = [] +cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] -code_commit = [] -codebuild = [] -codedeploy = [] +code_commit = ["chrono"] +codebuild = ["chrono"] +codedeploy = ["chrono"] codepipeline = [] -codepipeline_cloudwatch = [] +codepipeline_cloudwatch = ["chrono"] codepipeline_job = [] cognito = [] config = [] connect = [] -dynamodb = ["streams", "serde_dynamo"] +dynamodb = ["chrono", "serde_dynamo", "streams"] ecr_scan = [] -firehose = [] +firehose = ["chrono"] iam = [] -iot = ["iam"] +iot = ["bytes", "http", "http-body", "http-serde", "iam"] iot_1_click = [] iot_button = [] iot_deprecated = ["iot"] -kafka = [] -kinesis = [] +kafka = ["chrono"] +kinesis = ["chrono"] kinesis_analytics = ["kinesis"] -lambda_function_urls = [] +lambda_function_urls = ["bytes", "http", "http-body", "http-serde"] lex = [] rabbitmq = [] -s3 = [] +s3 = ["bytes", "chrono", "http", "http-body", "http-serde"] s3_batch_job = ["s3"] -ses = [] -sns = ["serde_with"] +ses = ["chrono"] +sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index c57ccd6a..94d0e2f5 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,6 +1,9 @@ use chrono::{DateTime, TimeZone, Utc}; -use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; use serde::ser::Serializer; +use serde::{ + de::{Deserializer, Error as DeError, Visitor}, + Deserialize, +}; use std::fmt; // Jan 2, 2006 3:04:05 PM @@ -10,7 +13,7 @@ struct TimeVisitor; impl<'de> Visitor<'de> for TimeVisitor { type Value = DateTime; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) } @@ -74,7 +77,7 @@ mod tests { #[serde(with = "str_time")] pub date: TestTime, } - let data = json!({ + let data = serde_json::json!({ "date": "Sep 1, 2017 4:12:29 PM" }); @@ -92,7 +95,7 @@ mod tests { #[serde(with = "optional_time")] pub date: Option, } - let data = json!({ + let data = serde_json::json!({ "date": "Sep 1, 2017 4:12:29 PM" }); diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 54fc64e4..82fd51df 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -14,13 +14,13 @@ fn ne_timestamp(ts: T) -> SerdeError { } impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ChronoSerdeError({})", self) } } impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SerdeError::NonExistent { ref timestamp } => { write!(f, "value is not a legal timestamp: {}", timestamp) @@ -77,7 +77,7 @@ where impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { type Value = DateTime; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a unix timestamp as a float") } diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 904ccd9b..9cb89e40 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -49,7 +49,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { type Value = HeaderMap; // Format a message stating what data this Visitor expects to receive. - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("lots of things can go wrong with HeaderMap") } @@ -81,7 +81,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { let mut map = HeaderMap::with_capacity(access.size_hint().unwrap_or(0)); if !self.is_human_readable { - while let Some((key, arr)) = access.next_entry::, Vec>>()? { + while let Some((key, arr)) = access.next_entry::, Vec>>()? { let key = HeaderName::from_bytes(key.as_bytes()) .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; for val in arr { @@ -91,7 +91,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { } } } else { - while let Some((key, val)) = access.next_entry::, OneOrMore>()? { + while let Some((key, val)) = access.next_entry::, OneOrMore<'_>>()? { let key = HeaderName::from_bytes(key.as_bytes()) .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; match val { @@ -135,6 +135,7 @@ where #[cfg(test)] mod tests { use super::*; + use serde::{Deserialize, Serialize}; #[test] fn test_deserialize_missing_http_headers() { @@ -143,7 +144,7 @@ mod tests { #[serde(deserialize_with = "deserialize_headers", default)] pub headers: HeaderMap, } - let data = json!({ + let data = serde_json::json!({ "not_headers": {} }); @@ -161,7 +162,7 @@ mod tests { #[serde(serialize_with = "serialize_multi_value_headers")] headers: HeaderMap, } - let data = json!({ + let data = serde_json::json!({ "headers": { "Accept": ["*/*"] } @@ -181,7 +182,7 @@ mod tests { #[serde(deserialize_with = "deserialize_headers")] headers: HeaderMap, } - let data = json!({ "headers": null }); + let data = serde_json::json!({ "headers": null }); let decoded: Test = serde_json::from_value(data).unwrap(); assert!(decoded.headers.is_empty()); diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 6060a429..63a98eb4 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -11,7 +11,7 @@ struct MethodVisitor; impl<'de> Visitor<'de> for MethodVisitor { type Value = Method; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "valid method name") } @@ -56,6 +56,7 @@ pub fn serialize_optional(method: &Option, ser: S) -> Res #[cfg(test)] mod tests { use super::*; + use serde::{Deserialize, Serialize}; #[test] fn test_http_method_serializer() { @@ -64,7 +65,7 @@ mod tests { #[serde(with = "crate::custom_serde::http_method")] pub method: http::Method, } - let data = json!({ + let data = serde_json::json!({ "method": "DELETE" }); let decoded: Test = serde_json::from_value(data.clone()).unwrap(); @@ -83,7 +84,7 @@ mod tests { #[serde(default)] pub method: Option, } - let data = json!({ + let data = serde_json::json!({ "method": "DELETE" }); let decoded: Test = serde_json::from_value(data.clone()).unwrap(); @@ -92,11 +93,11 @@ mod tests { let recoded = serde_json::to_value(decoded).unwrap(); assert_eq!(data, recoded); - let data = json!({ "method": null }); + let data = serde_json::json!({ "method": null }); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(None, decoded.method); - let data = json!({}); + let data = serde_json::json!({}); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(None, decoded.method); } diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index e2c44af9..65f0f89a 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -1,7 +1,4 @@ -#[allow(unused)] use base64::Engine; -use chrono::{DateTime, Duration, TimeZone, Utc}; -use serde; use serde::de::{Deserialize, Deserializer, Error as DeError}; use serde::ser::Serializer; use std::collections::HashMap; @@ -34,89 +31,6 @@ pub(crate) mod float_unix_epoch; #[cfg(any(feature = "alb", feature = "apigw"))] pub(crate) mod http_method; -fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum StringOrNumber { - String(String), - Float(f64), - Int(u64), - } - - let input: f64 = match StringOrNumber::deserialize(deserializer)? { - StringOrNumber::String(s) => s.parse::().map_err(DeError::custom)?, - StringOrNumber::Float(f) => f, - StringOrNumber::Int(i) => i as f64, - }; - - // We need to do this due to floating point issues. - let input_as_string = format!("{}", input); - let parts: Result, _> = input_as_string - .split('.') - .map(|x| x.parse::().map_err(DeError::custom)) - .collect(); - let parts = parts?; - if parts.len() > 1 { - Ok((parts[0], parts[1])) - } else { - Ok((parts[0], 0)) - } -} - -pub(crate) fn serialize_milliseconds(date: &DateTime, serializer: S) -> Result -where - S: Serializer, -{ - let ts_with_millis = date.timestamp_millis(); - serializer.serialize_str(&ts_with_millis.to_string()) -} - -pub(crate) fn deserialize_milliseconds<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let (whole, frac) = normalize_timestamp(deserializer)?; - assert_eq!(frac, 0); - let seconds: f64 = whole as f64 / 1000.0; - let milliseconds: u32 = (seconds.fract() * 1000f64) as u32; - let nanos = milliseconds * 1_000_000; - Utc.timestamp_opt(seconds as i64, nanos) - .latest() - .ok_or_else(|| D::Error::custom("invalid timestamp")) -} - -pub(crate) fn serialize_seconds(date: &DateTime, serializer: S) -> Result -where - S: Serializer, -{ - let seconds = date.timestamp(); - let milliseconds = date.timestamp_subsec_millis(); - let whole_seconds = seconds + (milliseconds as i64 / 1000); - let subsec_millis = milliseconds % 1000; - if milliseconds > 0 { - let combined = format!("{}.{:03}", whole_seconds, subsec_millis); - serializer.serialize_str(&combined) - } else { - serializer.serialize_str(&whole_seconds.to_string()) - } -} - -#[allow(dead_code)] -pub(crate) fn deserialize_seconds<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let (whole, frac) = normalize_timestamp(deserializer)?; - let seconds = whole; - let nanos = frac * 1_000_000; - Utc.timestamp_opt(seconds as i64, nanos as u32) - .latest() - .ok_or_else(|| D::Error::custom("invalid timestamp")) -} - pub(crate) fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -159,46 +73,13 @@ where Ok(opt.unwrap_or_default()) } -pub(crate) fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - let seconds = duration.num_seconds(); - - serializer.serialize_i64(seconds) -} - -pub(crate) fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let seconds = f64::deserialize(deserializer)?; - Ok(Duration::seconds(seconds as i64)) -} - -pub(crate) fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - let minutes = duration.num_minutes(); - - serializer.serialize_i64(minutes) -} - -pub(crate) fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let minutes = f64::deserialize(deserializer)?; - Ok(Duration::minutes(minutes as i64)) -} - /// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. #[cfg(any( feature = "alb", feature = "apigw", feature = "cloudwatch_events", feature = "code_commit", + feature = "cognito", test ))] pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result @@ -214,7 +95,7 @@ where #[allow(deprecated)] mod test { use super::*; - use chrono::TimeZone; + use serde::{Deserialize, Serialize}; use serde_json; #[test] @@ -224,7 +105,7 @@ mod test { #[serde(deserialize_with = "deserialize_base64")] v: Vec, } - let data = json!({ + let data = serde_json::json!({ "v": "SGVsbG8gV29ybGQ=", }); let decoded: Test = serde_json::from_value(data).unwrap(); @@ -245,76 +126,6 @@ mod test { assert_eq!(encoded, r#"{"v":"SGVsbG8gV29ybGQ="}"#.to_string()); } - #[test] - fn test_deserialize_milliseconds() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_milliseconds")] - v: DateTime, - } - let expected = Utc.ymd(2017, 10, 5).and_hms_nano(15, 33, 44, 302_000_000); - - // Test parsing strings. - let data = json!({ - "v": "1507217624302", - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - // Test parsing ints. - let decoded: Test = serde_json::from_slice(r#"{"v":1507217624302}"#.as_bytes()).unwrap(); - assert_eq!(expected, decoded.v,); - // Test parsing floats. - let data = json!({ - "v": 1507217624302.0, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_milliseconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_milliseconds")] - v: DateTime, - } - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99_888_777), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600099"}"#)); - } - - #[test] - fn test_serialize_seconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_seconds")] - v: DateTime, - } - - // Make sure nanoseconds are chopped off. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600"}"#)); - - // Make sure milliseconds are included. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 2_000_000), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); - - // Make sure milliseconds are included. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); - } - #[test] fn test_deserialize_map() { #[derive(Deserialize)] @@ -322,13 +133,13 @@ mod test { #[serde(deserialize_with = "deserialize_lambda_map")] v: HashMap, } - let input = json!({ + let input = serde_json::json!({ "v": {}, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(HashMap::new(), decoded.v); - let input = json!({ + let input = serde_json::json!({ "v": null, }); let decoded: Test = serde_json::from_value(input).unwrap(); @@ -343,93 +154,19 @@ mod test { #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] v: serde_dynamo::Item, } - let input = json!({ + let input = serde_json::json!({ "v": {}, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); - let input = json!({ + let input = serde_json::json!({ "v": null, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); } - #[test] - fn test_deserialize_duration_seconds() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_duration_seconds")] - v: Duration, - } - - let expected = Duration::seconds(36); - - let data = json!({ - "v": 36, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - - let data = json!({ - "v": 36.1, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_duration_seconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_duration_seconds")] - v: Duration, - } - let instance = Test { - v: Duration::seconds(36), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":36}"#)); - } - - #[test] - fn test_deserialize_duration_minutes() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_duration_minutes")] - v: Duration, - } - - let expected = Duration::minutes(36); - - let data = json!({ - "v": 36, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - - let data = json!({ - "v": 36.1, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_duration_minutes() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_duration_minutes")] - v: Duration, - } - let instance = Test { - v: Duration::minutes(36), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":36}"#)); - } - #[test] fn test_deserialize_nullish_boolean() { #[derive(Deserialize)] diff --git a/lambda-events/src/encodings.rs b/lambda-events/src/encodings/http.rs similarity index 75% rename from lambda-events/src/encodings.rs rename to lambda-events/src/encodings/http.rs index 42dd15a7..effb48f4 100644 --- a/lambda-events/src/encodings.rs +++ b/lambda-events/src/encodings/http.rs @@ -1,124 +1,9 @@ -use super::custom_serde::*; -use chrono::{DateTime, Duration, Utc}; -use std::{borrow::Cow, mem::take, ops::Deref, ops::DerefMut, pin::Pin, task::Poll}; - use base64::display::Base64Display; use bytes::Bytes; use http_body::{Body as HttpBody, SizeHint}; use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; use serde::ser::{Error as SerError, Serialize, Serializer}; - -pub type Error = Box; - -/// Binary data encoded in base64. -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct Base64Data( - #[serde(deserialize_with = "deserialize_base64")] - #[serde(serialize_with = "serialize_base64")] - pub Vec, -); - -impl Deref for Base64Data { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Base64Data { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Timestamp with millisecond precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct MillisecondTimestamp( - #[serde(deserialize_with = "deserialize_milliseconds")] - #[serde(serialize_with = "serialize_milliseconds")] - pub DateTime, -); - -impl Deref for MillisecondTimestamp { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MillisecondTimestamp { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Timestamp with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct SecondTimestamp( - #[serde(deserialize_with = "deserialize_seconds")] - #[serde(serialize_with = "serialize_seconds")] - pub DateTime, -); - -impl Deref for SecondTimestamp { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SecondTimestamp { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Duration with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct SecondDuration( - #[serde(deserialize_with = "deserialize_duration_seconds")] - #[serde(serialize_with = "serialize_duration_seconds")] - pub Duration, -); - -impl Deref for SecondDuration { - type Target = Duration; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SecondDuration { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Duration with minute precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct MinuteDuration( - #[serde(deserialize_with = "deserialize_duration_minutes")] - #[serde(serialize_with = "serialize_duration_minutes")] - pub Duration, -); - -impl Deref for MinuteDuration { - type Target = Duration; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MinuteDuration { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} +use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// Representation of http request and response bodies as supported /// by API Gateway and ALBs. @@ -313,7 +198,7 @@ impl<'de> Deserialize<'de> for Body { impl<'de> Visitor<'de> for BodyVisitor { type Value = Body; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter.write_str("string") } @@ -331,7 +216,7 @@ impl<'de> Deserialize<'de> for Body { impl HttpBody for Body { type Data = Bytes; - type Error = Error; + type Error = super::Error; fn poll_data( self: Pin<&mut Self>, diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs new file mode 100644 index 00000000..ccc92684 --- /dev/null +++ b/lambda-events/src/encodings/mod.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; +use std::{ops::Deref, ops::DerefMut}; + +#[cfg(feature = "chrono")] +mod time; +use crate::custom_serde::{deserialize_base64, serialize_base64}; + +#[cfg(feature = "chrono")] +pub use self::time::*; +#[cfg(feature = "http")] +mod http; +#[cfg(feature = "http")] +pub use self::http::*; + +pub type Error = Box; + +/// Binary data encoded in base64. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Base64Data( + #[serde(deserialize_with = "deserialize_base64")] + #[serde(serialize_with = "serialize_base64")] + pub Vec, +); + +impl Deref for Base64Data { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Base64Data { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs new file mode 100644 index 00000000..390927ca --- /dev/null +++ b/lambda-events/src/encodings/time.rs @@ -0,0 +1,363 @@ +use chrono::{DateTime, Duration, TimeZone, Utc}; +use serde::ser::Serializer; +use serde::{ + de::{Deserializer, Error as DeError}, + Deserialize, Serialize, +}; +use std::ops::{Deref, DerefMut}; + +/// Timestamp with millisecond precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MillisecondTimestamp( + #[serde(deserialize_with = "deserialize_milliseconds")] + #[serde(serialize_with = "serialize_milliseconds")] + pub DateTime, +); + +impl Deref for MillisecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MillisecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Timestamp with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondTimestamp( + #[serde(deserialize_with = "deserialize_seconds")] + #[serde(serialize_with = "serialize_seconds")] + pub DateTime, +); + +impl Deref for SecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondDuration( + #[serde(deserialize_with = "deserialize_duration_seconds")] + #[serde(serialize_with = "serialize_duration_seconds")] + pub Duration, +); + +impl Deref for SecondDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with minute precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MinuteDuration( + #[serde(deserialize_with = "deserialize_duration_minutes")] + #[serde(serialize_with = "serialize_duration_minutes")] + pub Duration, +); + +impl Deref for MinuteDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MinuteDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +fn serialize_milliseconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let ts_with_millis = date.timestamp_millis(); + serializer.serialize_str(&ts_with_millis.to_string()) +} + +fn deserialize_milliseconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + assert_eq!(frac, 0); + let seconds: f64 = whole as f64 / 1000.0; + let milliseconds: u32 = (seconds.fract() * 1000f64) as u32; + let nanos = milliseconds * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +fn serialize_seconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = date.timestamp(); + let milliseconds = date.timestamp_subsec_millis(); + let whole_seconds = seconds + (milliseconds as i64 / 1000); + let subsec_millis = milliseconds % 1000; + if milliseconds > 0 { + let combined = format!("{}.{:03}", whole_seconds, subsec_millis); + serializer.serialize_str(&combined) + } else { + serializer.serialize_str(&whole_seconds.to_string()) + } +} + +fn deserialize_seconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + let seconds = whole; + let nanos = frac * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos as u32) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = duration.num_seconds(); + + serializer.serialize_i64(seconds) +} + +fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let seconds = f64::deserialize(deserializer)?; + Ok(Duration::seconds(seconds as i64)) +} + +fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let minutes = duration.num_minutes(); + + serializer.serialize_i64(minutes) +} + +fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let minutes = f64::deserialize(deserializer)?; + Ok(Duration::minutes(minutes as i64)) +} + +fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrNumber { + String(String), + Float(f64), + Int(u64), + } + + let input: f64 = match StringOrNumber::deserialize(deserializer)? { + StringOrNumber::String(s) => s.parse::().map_err(DeError::custom)?, + StringOrNumber::Float(f) => f, + StringOrNumber::Int(i) => i as f64, + }; + + // We need to do this due to floating point issues. + let input_as_string = format!("{}", input); + let parts: Result, _> = input_as_string + .split('.') + .map(|x| x.parse::().map_err(DeError::custom)) + .collect(); + let parts = parts?; + if parts.len() > 1 { + Ok((parts[0], parts[1])) + } else { + Ok((parts[0], 0)) + } +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + use serde_json; + + #[test] + fn test_deserialize_milliseconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_milliseconds")] + v: DateTime, + } + let expected = Utc.ymd(2017, 10, 5).and_hms_nano(15, 33, 44, 302_000_000); + + // Test parsing strings. + let data = serde_json::json!({ + "v": "1507217624302", + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing ints. + let decoded: Test = serde_json::from_slice(r#"{"v":1507217624302}"#.as_bytes()).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing floats. + let data = serde_json::json!({ + "v": 1507217624302.0, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_milliseconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_milliseconds")] + v: DateTime, + } + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99_888_777), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600099"}"#)); + } + + #[test] + fn test_serialize_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_seconds")] + v: DateTime, + } + + // Make sure nanoseconds are chopped off. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 2_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); + } + + #[test] + fn test_deserialize_duration_seconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_seconds")] + v: Duration, + } + + let expected = Duration::seconds(36); + + let data = serde_json::json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = serde_json::json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_seconds")] + v: Duration, + } + let instance = Test { + v: Duration::seconds(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } + + #[test] + fn test_deserialize_duration_minutes() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_minutes")] + v: Duration, + } + + let expected = Duration::minutes(36); + + let data = serde_json::json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = serde_json::json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_minutes() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_minutes")] + v: Duration, + } + let instance = Test { + v: Duration::minutes(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } +} diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index fcb490ec..9469ece4 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { @@ -52,7 +54,7 @@ pub struct ActiveMqDestination { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "activemq")] diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index b9a69ce5..259dce23 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,7 +1,10 @@ -use crate::custom_serde::{http_method, serialize_headers, serialize_multi_value_headers}; +use crate::custom_serde::{ + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, +}; use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; +use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -15,13 +18,14 @@ pub struct AlbTargetGroupRequest { pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap, - #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] pub headers: HeaderMap, - #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_multi_value_headers")] pub multi_value_headers: HeaderMap, pub request_context: AlbTargetGroupRequestContext, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub body: Option, } @@ -57,6 +61,7 @@ pub struct AlbTargetGroupResponse { pub multi_value_headers: HeaderMap, #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, } @@ -64,7 +69,7 @@ pub struct AlbTargetGroupResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "alb")] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 196bff86..917f06aa 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -6,7 +6,7 @@ use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; @@ -751,7 +751,7 @@ pub struct IamPolicyStatement { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "apigw")] diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 0ef67b7b..120fc9e3 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use map[string]string, json.RawMessage, interface{}, etc.. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -121,7 +122,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "appsync")] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index ce0128c2..cc003daf 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -1,10 +1,11 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -47,7 +48,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "autoscaling")] diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index ef57c6f9..6581ed2c 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index f0e61dda..0e188704 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { @@ -47,7 +49,7 @@ pub struct ClientVpnConnectionHandlerResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "clientvpn")] diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index aefa7f4a..36d071ea 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index 0dd2b540..1bd44297 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index 86a1de15..ce5fa47c 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c4e26b4e..c8eb7834 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 942e5984..87fb8085 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index 1369a793..fb5c50a7 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use crate::custom_serde::deserialize_nullish_boolean; diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index f752f53e..08f05929 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index 3c8acbf9..2a6a82e3 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Event { diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index ac6f8926..74a76e70 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index 4ce78b71..37d18d7a 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 3c39e3d3..425de865 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; pub mod cloudtrail; diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index c75f1b5e..d1c192e5 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 4d256e3b..1cd73e6e 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 33092b76..7d161822 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index a826ed07..fa6ffc3b 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationStepStatusChange { diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index 573a99ea..d5bc9681 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index ce6cf79f..6a7e25d3 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CheckItemRefreshNotification { diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 053974ec..0c9ad4a8 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -59,7 +59,7 @@ impl<'de> Deserialize<'de> for AwsLogs { impl<'de> Visitor<'de> for AwsLogsVisitor { type Value = AwsLogs; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a base64 gzipped string") } diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 8b2e617f..87687cfd 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use crate::custom_serde::deserialize_nullish_boolean; @@ -68,7 +69,7 @@ pub struct CodeCommitReference { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "code_commit")] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 2c6ddf39..a3839f92 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -1,8 +1,8 @@ -use crate::custom_serde::*; +use crate::custom_serde::{codebuild_time, CodeBuildNumber}; use crate::encodings::{MinuteDuration, SecondDuration}; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; pub type CodeBuildPhaseStatus = String; @@ -213,7 +213,7 @@ pub type CodeBuildTime = DateTime; mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codebuild")] diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 61bfc665..2ab37a82 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; pub type CodeDeployDeploymentState = String; @@ -68,7 +69,7 @@ pub struct CodeDeployEventDetail { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codedeploy")] diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index d4d3477b..f26aa54f 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; pub type CodePipelineStageState = String; @@ -80,7 +81,7 @@ pub struct CodePipelineEventDetailType { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codepipeline_cloudwatch")] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 0767b272..6c5d75f6 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -115,7 +117,7 @@ pub struct CodePipelineArtifactCredentials { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codepipeline_job")] diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 99bc682b..6874ee24 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; + /// `CognitoEvent` contains data from an event sent from AWS Cognito Sync #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -457,7 +458,7 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "cognito")] diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index bb5d0c11..0b03ecc5 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `ConfigEvent` contains data from an event sent from AWS Config #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -38,7 +40,7 @@ pub struct ConfigEvent { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "config")] diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 62e86b52..bc640930 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `ConnectEvent` contains the data structure for a Connect event. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -92,7 +94,7 @@ pub type ConnectResponse = HashMap; mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "connect")] diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index d2f32caf..aad2cd4b 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -1,5 +1,5 @@ use base64::Engine; -use event::serde_dynamo::AttributeValue; +use serde_dynamo::AttributeValue; use std::collections::HashMap; #[cfg(test)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 00ff08e4..398f2dd5 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -1,6 +1,6 @@ -use crate::custom_serde::*; -use crate::streams::DynamoDbBatchItemFailure; +use crate::custom_serde::deserialize_lambda_dynamodb_item; use crate::time_window::*; +use crate::{custom_serde::float_unix_epoch, streams::DynamoDbBatchItemFailure}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -252,7 +252,7 @@ mod test { use super::*; use chrono::TimeZone; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "dynamodb")] diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 87dede6f..1ed91896 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { @@ -59,7 +61,7 @@ pub struct EcrScanEventFindingSeverityCounts { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "ecr_scan")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 63ef3e1f..352342ec 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -1,5 +1,8 @@ -use crate::custom_serde::*; -use crate::encodings::{Base64Data, MillisecondTimestamp}; +use crate::{ + custom_serde::deserialize_lambda_map, + encodings::{Base64Data, MillisecondTimestamp}, +}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. @@ -73,7 +76,7 @@ pub struct KinesisFirehoseRecordMetadata { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "firehose")] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 1b73e44b..12bf7ba9 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `IamPolicyDocument` represents an IAM policy document. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 9f45899f..31220b17 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,7 +1,8 @@ -use crate::custom_serde::*; +use crate::custom_serde::serialize_headers; use crate::encodings::Base64Data; use crate::iam::IamPolicyDocument; use http::HeaderMap; +use serde::{Deserialize, Serialize}; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html @@ -75,7 +76,7 @@ pub struct IoTCoreCustomAuthorizerResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot")] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 0e1c11b6..4ec47d71 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -57,8 +59,7 @@ pub struct IoTOneClickPlacementInfo { #[cfg(test)] mod test { use super::*; - - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot_1_click")] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 32ba8d5a..ac79e34b 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { @@ -13,7 +15,7 @@ pub struct IoTButtonEvent { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot_button")] diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 4304d7cd..12c1df99 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -1,4 +1,5 @@ use crate::iot::*; +use serde::{Deserialize, Serialize}; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 6c4d78fa..07299859 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -1,5 +1,5 @@ -use crate::custom_serde::*; -use crate::encodings::MillisecondTimestamp; +use crate::{custom_serde::deserialize_lambda_map, encodings::MillisecondTimestamp}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -35,7 +35,7 @@ pub struct KafkaRecord { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "kafka")] diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index 1704009e..74c95606 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -1,4 +1,5 @@ use crate::encodings::Base64Data; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index c401fa72..0c43ae10 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -1,5 +1,6 @@ use crate::encodings::{Base64Data, SecondTimestamp}; use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -74,7 +75,7 @@ pub struct KinesisRecord { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "kinesis")] diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index d1567b56..37ddfe39 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::*; use http::HeaderMap; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; + /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index a3593dfd..a058a249 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { @@ -106,7 +108,7 @@ pub struct Attachment { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "lex")] diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index e7b8c7f7..1aa56697 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -67,8 +67,6 @@ pub mod connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] -extern crate serde_dynamo; -#[cfg(feature = "dynamodb")] pub mod dynamodb; /// AWS Lambda event definitions for ecr_scan. diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index c8af802f..14a379a5 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { @@ -62,7 +63,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "rabbitmq")] diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index 8db71896..e3eb691e 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index b25cfdd2..13d514ad 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `S3Event` which wrap an array of `S3Event`Record #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -90,7 +92,7 @@ pub struct S3Object { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "s3")] diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index e31a751e..1eec2c0d 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -1,10 +1,11 @@ -use crate::custom_serde::*; use http::HeaderMap; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::{deserialize_headers, serialize_headers}; + /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas /// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -117,7 +118,7 @@ pub struct SessionIssuer { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "s3")] diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 3570652f..a4a97039 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -121,7 +122,7 @@ pub struct SimpleEmailDisposition { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "ses")] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 78193fcf..3c859d3e 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) @@ -178,7 +179,7 @@ pub struct MessageAttribute { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "sns")] diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 5dc178b2..af4d3f21 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,4 +1,5 @@ -use crate::{custom_serde::*, encodings::Base64Data}; +use crate::custom_serde::deserialize_lambda_map; +use crate::encodings::Base64Data; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -115,7 +116,7 @@ pub struct BatchItemFailure { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "sqs")] diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 51a77121..9e0fd76f 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index fa6cce05..564debd7 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,27 +1,13 @@ -extern crate base64; -extern crate http_serde; -#[cfg(test)] -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate serde_derive; -#[cfg(test)] -#[macro_use] -extern crate serde_json; - -// Crates with types that we use publicly. Reexported for ease of interoperability. -pub extern crate bytes; -pub extern crate chrono; -pub extern crate http; -pub extern crate http_body; -pub extern crate query_map; -pub extern crate serde; -#[cfg(not(test))] -pub extern crate serde_json; +#![deny(rust_2018_idioms)] +#[cfg(feature = "http")] +pub use http; +#[cfg(feature = "query_map")] +pub use query_map; mod custom_serde; /// Encodings used in AWS Lambda json event values. pub mod encodings; +#[cfg(feature = "chrono")] pub mod time_window; /// AWS Lambda event definitions. diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 9418dc8c..9f035995 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -65,7 +66,7 @@ pub struct TimeWindowEventResponseProperties { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] fn test_window_deserializer() { diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 289aec17..1409555e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -40,7 +40,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.9.0" +version = "0.10.0" default-features = false features = ["alb", "apigw"] From 19b83261017dfb7b05bb90ddea0b617fee303c3b Mon Sep 17 00:00:00 2001 From: mack <24418071+heavens@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:58:12 -0700 Subject: [PATCH 189/394] change(example/basic-lambda-external-runtime): minimal example showcasing lambda_runtime usage alongside another runtime thread/thread pool. (#661) --- .../basic-lambda-external-runtime/Cargo.toml | 15 +++ .../basic-lambda-external-runtime/README.md | 11 ++ .../basic-lambda-external-runtime/src/main.rs | 104 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 examples/basic-lambda-external-runtime/Cargo.toml create mode 100644 examples/basic-lambda-external-runtime/README.md create mode 100644 examples/basic-lambda-external-runtime/src/main.rs diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml new file mode 100644 index 00000000..9c732b2f --- /dev/null +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "basic-lambda-external-runtime" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-channel = "1.8.0" +futures-lite = "1.13.0" +lambda_runtime = "0.8.0" +lambda_runtime_api_client = "0.8.0" +serde = "1.0.163" +tokio = "1.28.2" +tokio-test = "0.4.2" +tracing = "0.1.37" +tracing-subscriber = "0.3.17" diff --git a/examples/basic-lambda-external-runtime/README.md b/examples/basic-lambda-external-runtime/README.md new file mode 100644 index 00000000..498f8a50 --- /dev/null +++ b/examples/basic-lambda-external-runtime/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs new file mode 100644 index 00000000..71bd123b --- /dev/null +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -0,0 +1,104 @@ +use std::{io, thread}; + +use futures_lite::future; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; +use tokio::runtime::Builder; + +/// This is also a made-up example. Requests come into the runtime as unicode +/// strings in json format, which can map to any structure that implements `serde::Deserialize` +/// The runtime pays no attention to the contents of the request payload. +#[derive(Deserialize)] +struct Request { + command: String, +} + +/// This is a made-up example of what a response structure may look like. +/// There is no restriction on what it can be. The runtime requires responses +/// to be serialized into json. The runtime pays no attention +/// to the contents of the response payload. +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +fn main() -> Result<(), io::Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + // Create a channel used to send and receive outputs from our lambda handler. Realistically, this would be either an unbounded channel + // or a bounded channel with a higher capacity as needed. + let (lambda_tx, lambda_rx) = async_channel::bounded(1); + + // Create a bounded channel used to communicate our shutdown signal across threads. + let (shutdown_tx, shutdown_rx) = async_channel::bounded(1); + + // Build a single-threaded (or multi-threaded using Builder::new_multi_thread) runtime to spawn our lambda work onto. + let tokio_runtime = Builder::new_current_thread() + .thread_name("lambda-runtime") + .enable_all() + .build() + .expect("build lambda runtime"); + + // Run the lambda runtime worker thread to completion. The response is sent to the other "runtime" to be processed as needed. + thread::spawn(move || { + let func = service_fn(my_handler); + if let Ok(response) = tokio_runtime.block_on(lambda_runtime::run(func)) { + lambda_tx.send_blocking(response).expect("send lambda result"); + }; + }); + + // Run the mock runtime to completion. + my_runtime(move || future::block_on(app_runtime_task(lambda_rx.clone(), shutdown_tx.clone()))); + + // Block the main thread until a shutdown signal is received. + future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) +} + +pub(crate) async fn my_handler(event: LambdaEvent) -> Result { + // extract some useful info from the request + let command = event.payload.command; + + // prepare the response + let resp = Response { + req_id: event.context.request_id, + msg: format!("Command {} executed.", command), + }; + + // return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(resp) +} + +/// A task to be ran on the custom runtime. Once a response from the lambda runtime is received then a shutdown signal +/// is sent to the main thread notifying the process to exit. +pub(crate) async fn app_runtime_task(lambda_rx: async_channel::Receiver<()>, shutdown_tx: async_channel::Sender<()>) { + loop { + // Receive the response sent by the lambda handle and process as needed. + if let Ok(result) = lambda_rx.recv().await { + tracing::debug!(?result); + // We're ready to shutdown our app. Send the shutdown signal notifying the main thread to exit the process. + shutdown_tx.send(()).await.expect("send shutdown signal"); + break; + } + + // more app logic would be here... + } +} + +/// Construct the mock runtime worker thread(s) to spawn some work onto. +fn my_runtime(func: impl Fn() + Send + 'static) { + thread::Builder::new() + .name("my-runtime".into()) + .spawn(func) + .expect("spawn my_runtime worker"); +} From fdc471459dc4f8b89b45d32dcfc74472a887242a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 10 Jun 2023 12:41:49 -0700 Subject: [PATCH 190/394] Release version 0.8.1 (#662) There are some patch improvements for http and generic lambda functions. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1409555e..edc68650 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.0" +version = "0.8.1" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 96506022..1de6f361 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.0" +version = "0.8.1" authors = [ "David Calavera ", "Harold Sun ", From b2451e9a47c1f58657a9d08a006c443e6cadf8e1 Mon Sep 17 00:00:00 2001 From: mark-keaton Date: Thu, 15 Jun 2023 18:13:38 -0500 Subject: [PATCH 191/394] fix: Makes two properties optional on MigrateUserResponse. (#663) --- lambda-events/src/event/cognito/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 6874ee24..49f2eebd 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -254,7 +254,9 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { pub final_user_status: Option, #[serde(default)] pub message_action: Option, - pub desired_delivery_mediums: Vec, + #[serde(default)] + pub desired_delivery_mediums: Option>, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub force_alias_creation: bool, } From aa80e74d30ac7d14264291bfee3e302c3b597e4d Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Tue, 20 Jun 2023 04:59:54 +0200 Subject: [PATCH 192/394] Examples basic s3 object lambda thumbnail (#664) * fix example basic-s3-thumbnail test * basic-s3-object-lambda-thumbnail example (#625) Forwards a thumbnail to the user instead of the requested file --- .../Cargo.toml | 34 ++++ .../README.md | 40 ++++ .../src/main.rs | 178 ++++++++++++++++++ .../src/s3.rs | 90 +++++++++ .../testdata/image.png | Bin 0 -> 282 bytes .../testdata/thumbnail.png | Bin 0 -> 82 bytes examples/basic-s3-thumbnail/Cargo.toml | 1 + examples/basic-s3-thumbnail/src/main.rs | 6 +- 8 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 examples/basic-s3-object-lambda-thumbnail/Cargo.toml create mode 100644 examples/basic-s3-object-lambda-thumbnail/README.md create mode 100644 examples/basic-s3-object-lambda-thumbnail/src/main.rs create mode 100644 examples/basic-s3-object-lambda-thumbnail/src/s3.rs create mode 100644 examples/basic-s3-object-lambda-thumbnail/testdata/image.png create mode 100644 examples/basic-s3-object-lambda-thumbnail/testdata/thumbnail.png diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml new file mode 100644 index 00000000..493846ad --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "basic-s3-object-lambda-thumbnail" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws_lambda_events = "0.8.3" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +aws-config = "0.55.3" +aws-sdk-s3 = "0.28.0" +thumbnailer = "0.4.0" +mime = "0.3.16" +async-trait = "0.1.66" +ureq = "2.6.2" +aws-smithy-http = "0.55.3" + +[dev-dependencies] +mockall = "0.11.3" +tokio-test = "0.4.2" diff --git a/examples/basic-s3-object-lambda-thumbnail/README.md b/examples/basic-s3-object-lambda-thumbnail/README.md new file mode 100644 index 00000000..e9347fbb --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/README.md @@ -0,0 +1,40 @@ +# AWS S3 Object Lambda Function + +It uses a GetObject event and it returns with a thumbnail instead of the real +object from the S3 bucket. +The thumbnail was tested only witn PNG files. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release --arm64 --output-format zip` +3. Upload the bootstrap.zip file from the directory:`target/lambda/basic-s3-object-lambda-thumbnail/` + +## Setup on AWS S3 + +1. You need a bucket and upload a PNG file to that bucket +2. Set Access Point for that bucket +3. Set Object Lambda Access Point for the access point and use the uploaded lambda function as a transformer + +## Set Up on AWS Lambda + +0. Click on Code tab +1. Runtime settings - runtime: Custom runtime on Amazon Linux 2 +2. Runtime settings - Architecture: arm64 + +## Set Up on AWS IAM + +1. Click on Roles +2. Search the lambda function name +3. Add the permission: AmazonS3ObjectLambdaExecutionRolePolicy + +## How to check this lambda + +1. Go to S3 +2. Click on Object Lambda Access Point +3. Click on your object lambda access point name +4. click on one uploaded PNG file +5. Click on the activated Open button + +### Expected: +A new browser tab opens with a 128x128 thumbnail diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs new file mode 100644 index 00000000..7786f56e --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -0,0 +1,178 @@ +use std::{error, io::Cursor}; + +use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; +use aws_sdk_s3::Client as S3Client; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use s3::{GetFile, SendFile}; +use thumbnailer::{create_thumbnails, ThumbnailSize}; + +mod s3; + +/** +This s3 object lambda handler + * downloads the asked file + * creates a PNG thumbnail from it + * forwards it to the browser +*/ +pub(crate) async fn function_handler( + event: LambdaEvent, + size: u32, + client: &T, +) -> Result> { + tracing::info!("handler starts"); + + let context: GetObjectContext = event.payload.get_object_context.unwrap(); + + let route = context.output_route; + let token = context.output_token; + let s3_url = context.input_s3_url; + + tracing::info!("Route: {}, s3_url: {}", route, s3_url); + + let image = client.get_file(s3_url)?; + tracing::info!("Image loaded. Length: {}", image.len()); + + let thumbnail = get_thumbnail(image, size); + tracing::info!("thumbnail created. Length: {}", thumbnail.len()); + + // It sends the thumbnail back to the user + + client.send_file(route, token, thumbnail).await + + /* + match client.send_file(route, token, thumbnail).await { + Ok(msg) => tracing::info!(msg), + Err(msg) => tracing::info!(msg) + }; + + tracing::info!("handler ends"); + + Ok(()) + */ +} + +fn get_thumbnail(vec: Vec, size: u32) -> Vec { + let reader = Cursor::new(vec); + let mut thumbnails = create_thumbnails(reader, mime::IMAGE_PNG, [ThumbnailSize::Custom((size, size))]).unwrap(); + + let thumbnail = thumbnails.pop().unwrap(); + let mut buf = Cursor::new(Vec::new()); + thumbnail.write_png(&mut buf).unwrap(); + + buf.into_inner() +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let shared_config = aws_config::load_from_env().await; + let client = S3Client::new(&shared_config); + let client_ref = &client; + + let func = service_fn(move |event| async move { function_handler(event, 128, client_ref).await }); + + let _ = run(func).await; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::fs::File; + use std::io::BufReader; + use std::io::Read; + + use super::*; + use async_trait::async_trait; + use aws_lambda_events::s3::object_lambda::Configuration; + use aws_lambda_events::s3::object_lambda::HeadObjectContext; + use aws_lambda_events::s3::object_lambda::ListObjectsContext; + use aws_lambda_events::s3::object_lambda::ListObjectsV2Context; + use aws_lambda_events::s3::object_lambda::UserIdentity; + use aws_lambda_events::s3::object_lambda::UserRequest; + use aws_lambda_events::serde_json::json; + use lambda_runtime::{Context, LambdaEvent}; + use mockall::mock; + use s3::GetFile; + use s3::SendFile; + + #[tokio::test] + async fn response_is_good() { + mock! { + FakeS3Client {} + + #[async_trait] + impl GetFile for FakeS3Client { + pub fn get_file(&self, url: String) -> Result, Box>; + } + #[async_trait] + impl SendFile for FakeS3Client { + pub async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; + } + } + + let mut mock = MockFakeS3Client::new(); + + mock.expect_get_file() + .withf(|u: &String| u.eq("S3_URL")) + .returning(|_1| Ok(get_file("testdata/image.png"))); + + mock.expect_send_file() + .withf(|r: &String, t: &String, by| { + let thumbnail = get_file("testdata/thumbnail.png"); + return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == &thumbnail; + }) + .returning(|_1, _2, _3| Ok("File sent.".to_string())); + + let payload = get_s3_event(); + let context = Context::default(); + let event = LambdaEvent { payload, context }; + + let result = function_handler(event, 10, &mock).await.unwrap(); + + assert_eq!(("File sent."), result); + } + + fn get_file(name: &str) -> Vec { + let f = File::open(name); + let mut reader = BufReader::new(f.unwrap()); + let mut buffer = Vec::new(); + + reader.read_to_end(&mut buffer).unwrap(); + + return buffer; + } + + fn get_s3_event() -> S3ObjectLambdaEvent { + return S3ObjectLambdaEvent { + x_amz_request_id: ("ID".to_string()), + head_object_context: (Some(HeadObjectContext::default())), + list_objects_context: (Some(ListObjectsContext::default())), + get_object_context: (Some(GetObjectContext { + input_s3_url: ("S3_URL".to_string()), + output_route: ("O_ROUTE".to_string()), + output_token: ("O_TOKEN".to_string()), + })), + list_objects_v2_context: (Some(ListObjectsV2Context::default())), + protocol_version: ("VERSION".to_string()), + user_identity: (UserIdentity::default()), + user_request: (UserRequest::default()), + configuration: (Configuration { + access_point_arn: ("APRN".to_string()), + supporting_access_point_arn: ("SAPRN".to_string()), + payload: (json!(null)), + }), + }; + } +} diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs new file mode 100644 index 00000000..71e03ffc --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -0,0 +1,90 @@ +use async_trait::async_trait; +use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; +use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use std::{error, io::Read}; + +pub trait GetFile { + fn get_file(&self, url: String) -> Result, Box>; +} + +#[async_trait] +pub trait SendFile { + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; +} + +impl GetFile for S3Client { + fn get_file(&self, url: String) -> Result, Box> { + tracing::info!("get file url {}", url); + + let resp = ureq::get(&url).call()?; + let len: usize = resp.header("Content-Length").unwrap().parse()?; + + let mut bytes: Vec = Vec::with_capacity(len); + + std::io::Read::take(resp.into_reader(), 10_000_000).read_to_end(&mut bytes)?; + + tracing::info!("got {} bytes", bytes.len()); + + Ok(bytes) + } +} + +#[async_trait] +impl SendFile for S3Client { + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result> { + tracing::info!("send file route {}, token {}, length {}", route, token, vec.len()); + + let bytes = ByteStream::from(vec); + + let write = self + .write_get_object_response() + .request_route(route) + .request_token(token) + .status_code(200) + .body(bytes) + .send() + .await; + + if write.is_err() { + let sdk_error = write.err().unwrap(); + check_error(sdk_error); + Err("WriteGetObjectResponse creation error".into()) + } else { + Ok("File sent.".to_string()) + } + } +} + +fn check_error(error: SdkError) { + match error { + SdkError::ConstructionFailure(_err) => { + tracing::info!("ConstructionFailure"); + } + SdkError::DispatchFailure(err) => { + tracing::info!("DispatchFailure"); + if err.is_io() { + tracing::info!("IO error"); + }; + if err.is_timeout() { + tracing::info!("Timeout error"); + }; + if err.is_user() { + tracing::info!("User error"); + }; + if err.is_other().is_some() { + tracing::info!("Other error"); + }; + } + SdkError::ResponseError(_err) => tracing::info!("ResponseError"), + SdkError::TimeoutError(_err) => tracing::info!("TimeoutError"), + SdkError::ServiceError(err) => { + tracing::info!("ServiceError"); + let wgore = err.into_err(); + let meta = wgore.meta(); + let code = meta.code().unwrap_or_default(); + let msg = meta.message().unwrap_or_default(); + tracing::info!("code: {}, message: {}, meta: {}", code, msg, meta); + } + _ => tracing::info!("other error"), + } +} diff --git a/examples/basic-s3-object-lambda-thumbnail/testdata/image.png b/examples/basic-s3-object-lambda-thumbnail/testdata/image.png new file mode 100644 index 0000000000000000000000000000000000000000..078d155f6bf6735eb087eb0195b3e35f9f424d04 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-UBp4!QuJ{S0SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+uenM@oty!5+IMg#M9T6{W-Ikn3DL(-uF5{ArVg(#}JFt$q5pyixWh8ngSjC85meA z7#KARcm4s&tCqM%l%ynf4NqV|ChEy=Vy|59;VQAR!XXWJ= d863$M85tR8F)&*H Date: Thu, 22 Jun 2023 07:54:56 -0700 Subject: [PATCH 193/394] Implement custom deserializer for LambdaRequest (#666) This deserializer gives us full control over the error message that we return for invalid payloads. The default message that Serde returns is usually very confusing, and it's been reported many times as something people don't understand. This code is a copy of the code that Serde generates when it expands the Deserialize macro. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-events/src/event/alb/mod.rs | 1 + lambda-events/src/event/apigw/mod.rs | 23 ++- .../src/fixtures/example-apigw-request.json | 145 +++++++++++------- lambda-http/Cargo.toml | 2 +- lambda-http/src/deserializer.rs | 117 ++++++++++++++ lambda-http/src/lib.rs | 1 + lambda-http/src/request.rs | 5 +- 8 files changed, 238 insertions(+), 58 deletions(-) create mode 100644 lambda-http/src/deserializer.rs diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b1108c63..28df6b4a 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.10.0" +version = "0.11.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 259dce23..7bb1eb7f 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct AlbTargetGroupRequest { #[serde(with = "http_method")] pub http_method: Method, diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 917f06aa..b595d825 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,6 +13,7 @@ use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayProxyRequest where T1: DeserializeOwned, @@ -118,12 +119,25 @@ where /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayV2httpRequest { + #[serde(default, rename = "type")] + pub kind: Option, + #[serde(default)] + pub method_arn: Option, + #[serde(with = "http_method", default = "default_http_method")] + pub http_method: Method, + #[serde(default)] + pub identity_source: Option, + #[serde(default)] + pub authorization_token: Option, + #[serde(default)] + pub resource: Option, #[serde(default)] pub version: Option, #[serde(default)] pub route_key: Option, - #[serde(default)] + #[serde(default, alias = "path")] pub raw_path: Option, #[serde(default)] pub raw_query_string: Option, @@ -319,6 +333,7 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayWebsocketProxyRequest where T1: DeserializeOwned, @@ -747,6 +762,10 @@ pub struct IamPolicyStatement { pub resource: Vec, } +fn default_http_method() -> Method { + Method::GET +} + #[cfg(test)] mod test { use super::*; @@ -901,6 +920,8 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + assert_eq!("REQUEST", parsed.kind.unwrap()); + assert_eq!(Method::GET, parsed.http_method); } #[test] diff --git a/lambda-events/src/fixtures/example-apigw-request.json b/lambda-events/src/fixtures/example-apigw-request.json index 570f785b..d91e9609 100644 --- a/lambda-events/src/fixtures/example-apigw-request.json +++ b/lambda-events/src/fixtures/example-apigw-request.json @@ -1,55 +1,95 @@ { "resource": "/{proxy+}", - "path": "/hello/world", - "httpMethod": "POST", - "headers": { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate", - "cache-control": "no-cache", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Content-Type": "application/json", - "headerName": "headerValue", - "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", - "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", - "User-Agent": "PostmanRuntime/2.4.5", - "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", - "X-Forwarded-For": "54.240.196.186, 54.182.214.83", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "multiValueHeaders": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "cache-control": ["no-cache"], - "CloudFront-Forwarded-Proto": ["https"], - "CloudFront-Is-Desktop-Viewer": ["true"], - "CloudFront-Is-Mobile-Viewer": ["false"], - "CloudFront-Is-SmartTV-Viewer": ["false"], - "CloudFront-Is-Tablet-Viewer": ["false"], - "CloudFront-Viewer-Country": ["US"], - "Content-Type": ["application/json"], - "headerName": ["headerValue"], - "Host": ["gy415nuibc.execute-api.us-east-1.amazonaws.com"], - "Postman-Token": ["9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"], - "User-Agent": ["PostmanRuntime/2.4.5"], - "Via": ["1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"], - "X-Amz-Cf-Id": ["pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="], - "X-Forwarded-For": ["54.240.196.186, 54.182.214.83"], - "X-Forwarded-Port": ["443"], - "X-Forwarded-Proto": ["https"] - }, + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, "queryStringParameters": { "name": "me" - }, - "multiValueQueryStringParameters": { - "name": ["me"] - }, + }, + "multiValueQueryStringParameters": { + "name": [ + "me" + ] + }, "pathParameters": { "proxy": "hello/world" }, @@ -70,9 +110,9 @@ "accountId": "theAccountId", "cognitoIdentityId": "theCognitoIdentityId", "caller": "theCaller", - "apiKey": "theApiKey", - "apiKeyId": "theApiKeyId", - "accessKey": "ANEXAMPLEOFACCESSKEY", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", "sourceIp": "192.168.196.186", "cognitoAuthenticationType": "theCognitoAuthenticationType", "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", @@ -92,5 +132,4 @@ "apiId": "gy415nuibc" }, "body": "{\r\n\t\"a\": 1\r\n}" -} - +} \ No newline at end of file diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index edc68650..be111092 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -40,7 +40,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.10.0" +version = "0.11.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs new file mode 100644 index 00000000..1771ea7b --- /dev/null +++ b/lambda-http/src/deserializer.rs @@ -0,0 +1,117 @@ +use crate::request::LambdaRequest; +use aws_lambda_events::{ + alb::AlbTargetGroupRequest, + apigw::{ApiGatewayProxyRequest, ApiGatewayV2httpRequest, ApiGatewayWebsocketProxyRequest}, +}; +use serde::{de::Error, Deserialize}; + +const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; + +impl<'de> Deserialize<'de> for LambdaRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let content = match serde::__private::de::Content::deserialize(deserializer) { + Ok(content) => content, + Err(err) => return Err(err), + }; + #[cfg(feature = "apigw_rest")] + if let Ok(res) = + ApiGatewayProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + { + return Ok(LambdaRequest::ApiGatewayV1(res)); + } + #[cfg(feature = "apigw_http")] + if let Ok(res) = ApiGatewayV2httpRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { + return Ok(LambdaRequest::ApiGatewayV2(res)); + } + #[cfg(feature = "alb")] + if let Ok(res) = + AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + { + return Ok(LambdaRequest::Alb(res)); + } + #[cfg(feature = "apigw_websockets")] + if let Ok(res) = ApiGatewayWebsocketProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::< + D::Error, + >::new(&content)) + { + return Ok(LambdaRequest::WebSocket(res)); + } + + Err(Error::custom(ERROR_CONTEXT)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_apigw_rest() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw rest data"); + match req { + LambdaRequest::ApiGatewayV1(req) => { + assert_eq!("12345678912", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_apigw_http() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-v2-request-iam.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw http data"); + match req { + LambdaRequest::ApiGatewayV2(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_alb() { + let data = include_bytes!( + "../../lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json" + ); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze alb rest data"); + match req { + LambdaRequest::Alb(req) => { + assert_eq!( + "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh", + req.request_context.elb.target_group_arn.unwrap() + ); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_apigw_websocket() { + let data = + include_bytes!("../../lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw websocket data"); + match req { + LambdaRequest::WebSocket(req) => { + assert_eq!("CONNECT", req.request_context.event_type.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_error() { + let err = serde_json::from_str::("{\"command\": \"hi\"}").unwrap_err(); + + assert_eq!(ERROR_CONTEXT, err.to_string()); + } +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 37c167a0..bc9e753d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -70,6 +70,7 @@ pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; use request::RequestFuture; use response::ResponseFuture; +mod deserializer; pub mod ext; pub mod request; mod response; diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 5ed3effe..ea418595 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -20,8 +20,10 @@ use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsoc use aws_lambda_events::{encodings::Body, query_map::QueryMap}; use http::header::HeaderName; use http::{HeaderMap, HeaderValue}; + use serde::{Deserialize, Serialize}; use serde_json::error::Error as JsonError; + use std::future::Future; use std::pin::Pin; use std::{env, io::Read, mem}; @@ -33,8 +35,7 @@ use url::Url; /// This is not intended to be a type consumed by crate users directly. The order /// of the variants are notable. Serde will try to deserialize in this order. #[doc(hidden)] -#[derive(Deserialize, Debug)] -#[serde(untagged)] +#[derive(Debug)] pub enum LambdaRequest { #[cfg(feature = "apigw_rest")] ApiGatewayV1(ApiGatewayProxyRequest), From 6ca8a3670d813f90a1d65b73b578d573365863c5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jul 2023 08:40:03 -0700 Subject: [PATCH 194/394] Fix APIGW path with stage (#669) * Fix APIGW path with stage APIGW HTTP has started adding the stage to the path in the event. This change checks if the stage is already a prefix in the path, and skips adding it if so. Signed-off-by: David Calavera * Add env variable to shortcircuit stage behavior. There might be cases when you don't want the runtime to do anything with paths and stages. By setting AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH in the environment, we ignore this behavior completely. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-http/src/request.rs | 44 ++++++++++++-- lambda-http/tests/data/apigw_no_host.json | 2 +- .../tests/data/apigw_proxy_request.json | 2 +- ...w_v2_proxy_request_with_stage_in_path.json | 57 +++++++++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ea418595..bdb755ed 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -327,10 +327,21 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { - match stage { - None => path.into(), - Some(stage) if stage == "$default" => path.into(), - Some(stage) => format!("/{stage}{path}"), + if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() { + return path.into(); + } + + let stage = match stage { + None => return path.into(), + Some(stage) if stage == "$default" => return path.into(), + Some(stage) => stage, + }; + + let prefix = format!("/{stage}/"); + if path.starts_with(&prefix) { + path.into() + } else { + format!("/{stage}{path}") } } @@ -531,7 +542,7 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!( req.uri(), - "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/test/hello?name=me" + "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me" ); // Ensure this is an APIGW request @@ -733,7 +744,7 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "/test/test/hello?name=me"); + assert_eq!(req.uri(), "/test/hello?name=me"); } #[test] @@ -768,4 +779,25 @@ mod tests { let url = build_request_uri("/path with spaces/and multiple segments", &HeaderMap::new(), None, None); assert_eq!("/path%20with%20spaces/and%20multiple%20segments", url); } + + #[test] + fn deserializes_apigw_http_request_with_stage_in_path() { + let input = include_str!("../tests/data/apigw_v2_proxy_request_with_stage_in_path.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + assert_eq!("/Prod/my/path", req.uri().path()); + assert_eq!("/Prod/my/path", req.raw_http_path()); + } + + #[test] + fn test_apigw_path_with_stage() { + assert_eq!("/path", apigw_path_with_stage(&None, "/path")); + assert_eq!("/path", apigw_path_with_stage(&Some("$default".into()), "/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path")); + } } diff --git a/lambda-http/tests/data/apigw_no_host.json b/lambda-http/tests/data/apigw_no_host.json index 3143c81b..78a40dee 100644 --- a/lambda-http/tests/data/apigw_no_host.json +++ b/lambda-http/tests/data/apigw_no_host.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_proxy_request.json b/lambda-http/tests/data/apigw_proxy_request.json index 3b7cc9d2..61183846 100644 --- a/lambda-http/tests/data/apigw_proxy_request.json +++ b/lambda-http/tests/data/apigw_proxy_request.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json new file mode 100644 index 00000000..86e173c6 --- /dev/null +++ b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json @@ -0,0 +1,57 @@ +{ + "version": "2.0", + "routeKey": "Prod", + "rawPath": "/Prod/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1=value1", + "cookie2=value2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/Prod/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "Prod", + "stage": "Prod", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "body": "Hello from Lambda", + "pathParameters": { + "parameter1": "value1" + }, + "isBase64Encoded": false, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + } +} \ No newline at end of file From a00fe5d386d6700fb6e9bc92ae30bb2513f41d7e Mon Sep 17 00:00:00 2001 From: BMorinDrifter <108374412+BMorinDrifter@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:11:08 -0700 Subject: [PATCH 195/394] Fix check examples workflow to only run once in pull requests (#673) --- .github/workflows/check-examples.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml index ba7bc709..5ef1536a 100644 --- a/.github/workflows/check-examples.yml +++ b/.github/workflows/check-examples.yml @@ -1,6 +1,9 @@ name: Check examples -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: jobs: check: From 2b7d1619750d569a6876a4733d98992a02722fb6 Mon Sep 17 00:00:00 2001 From: BMorinDrifter <108374412+BMorinDrifter@users.noreply.github.com> Date: Sun, 9 Jul 2023 11:09:19 -0700 Subject: [PATCH 196/394] Exclude the ansi feature of tracing-subscriber instead of calling .with_ansi(false) in the builder (#674) * Disable the ansi feature for tracing_subscriber instead of building with_ansi(false) * use tracing::Level::INFO instead of via tracing-subscriber in README --- .../advanced-sqs-partial-batch-failures/Cargo.toml | 2 +- .../advanced-sqs-partial-batch-failures/src/main.rs | 11 ++--------- examples/basic-error-handling/Cargo.toml | 2 +- examples/basic-error-handling/src/main.rs | 3 --- examples/basic-lambda-external-runtime/Cargo.toml | 2 +- examples/basic-lambda-external-runtime/src/main.rs | 3 --- examples/basic-lambda/Cargo.toml | 2 +- examples/basic-lambda/src/main.rs | 3 --- examples/basic-s3-object-lambda-thumbnail/Cargo.toml | 2 +- .../basic-s3-object-lambda-thumbnail/src/main.rs | 3 --- examples/basic-s3-thumbnail/Cargo.toml | 2 +- examples/basic-s3-thumbnail/src/main.rs | 3 --- examples/basic-sdk/Cargo.toml | 2 +- examples/basic-sdk/src/main.rs | 3 --- examples/basic-shared-resource/Cargo.toml | 3 +-- examples/basic-shared-resource/src/main.rs | 3 --- examples/basic-sqs/Cargo.toml | 2 +- examples/basic-sqs/src/main.rs | 3 --- examples/basic-streaming-response/Cargo.toml | 2 +- examples/basic-streaming-response/src/main.rs | 3 --- examples/extension-basic/Cargo.toml | 2 +- examples/extension-basic/src/main.rs | 3 --- examples/extension-combined/Cargo.toml | 2 +- examples/extension-combined/src/main.rs | 3 --- examples/extension-custom-events/Cargo.toml | 2 +- examples/extension-custom-events/src/main.rs | 3 --- examples/extension-custom-service/Cargo.toml | 2 +- examples/extension-custom-service/src/main.rs | 3 --- examples/extension-logs-basic/Cargo.toml | 2 +- examples/extension-logs-basic/src/main.rs | 3 --- examples/extension-logs-custom-service/Cargo.toml | 2 +- examples/extension-logs-custom-service/src/main.rs | 3 --- examples/extension-logs-kinesis-firehose/Cargo.toml | 2 +- examples/extension-logs-kinesis-firehose/src/main.rs | 3 --- examples/extension-telemetry-basic/Cargo.toml | 2 +- examples/extension-telemetry-basic/src/main.rs | 3 --- examples/http-axum-diesel/Cargo.toml | 2 +- examples/http-axum-diesel/src/main.rs | 3 --- examples/http-axum/Cargo.toml | 2 +- examples/http-axum/src/main.rs | 3 --- examples/http-basic-lambda/Cargo.toml | 2 +- examples/http-basic-lambda/src/main.rs | 3 --- examples/http-cors/Cargo.toml | 2 +- examples/http-cors/src/main.rs | 3 --- examples/http-dynamodb/Cargo.toml | 2 +- examples/http-dynamodb/src/main.rs | 3 --- examples/http-query-parameters/Cargo.toml | 2 +- examples/http-query-parameters/src/main.rs | 3 --- examples/http-raw-path/Cargo.toml | 2 +- examples/http-raw-path/src/main.rs | 3 --- examples/http-shared-resource/Cargo.toml | 2 +- examples/http-shared-resource/src/main.rs | 3 --- examples/http-tower-trace/Cargo.toml | 2 +- examples/http-tower-trace/src/main.rs | 3 --- lambda-http/README.md | 12 ++++-------- lambda-integration-tests/Cargo.toml | 2 +- lambda-integration-tests/src/bin/extension-fn.rs | 3 --- lambda-integration-tests/src/bin/extension-trait.rs | 3 --- lambda-integration-tests/src/bin/http-fn.rs | 3 --- lambda-integration-tests/src/bin/http-trait.rs | 3 --- lambda-integration-tests/src/bin/logs-trait.rs | 3 --- lambda-integration-tests/src/bin/runtime-fn.rs | 3 --- lambda-integration-tests/src/bin/runtime-trait.rs | 3 --- 63 files changed, 34 insertions(+), 145 deletions(-) diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 0dfe49d9..04158320 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -13,4 +13,4 @@ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } futures = "0.3" tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 2923d2e4..23faa68f 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -33,9 +33,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); @@ -78,9 +75,7 @@ where tracing::trace!("Handling batch size {}", event.payload.records.len()); let create_task = |msg| { // We need to keep the message_id to report failures to SQS - let SqsMessageObj { - message_id, body, .. - } = msg; + let SqsMessageObj { message_id, body, .. } = msg; let span = tracing::span!(tracing::Level::INFO, "Handling SQS msg", message_id); let task = async { //TODO catch panics like the `run` function from lambda_runtime @@ -104,9 +99,7 @@ where } }, ) - .map(|id| BatchItemFailure { - item_identifier: id, - }) + .map(|id| BatchItemFailure { item_identifier: id }) .collect(); Ok(SqsBatchResponse { diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index 325b08e1..e8699141 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -17,6 +17,6 @@ serde_json = "1.0.81" simple-error = "0.2.3" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 8d317a24..0939d2d0 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -54,9 +54,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index 9c732b2f..0682efaf 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -12,4 +12,4 @@ serde = "1.0.163" tokio = "1.28.2" tokio-test = "0.4.2" tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index 71bd123b..9419b17b 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -29,9 +29,6 @@ fn main() -> Result<(), io::Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index fd6bd5b2..cd2efa42 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -15,5 +15,5 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index 2bb4aeb3..09145bb3 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -29,9 +29,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index 493846ad..79640cc2 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -20,7 +20,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 7786f56e..771a829c 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -69,9 +69,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::TRACE) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index dc5e67f8..7c788a00 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -20,7 +20,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.54.1" aws-sdk-s3 = "0.24.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index f9cb0716..d996de0c 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -111,9 +111,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 0ffea2da..0e930f7c 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -13,7 +13,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index 5838d7c8..6e2654a4 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -38,9 +38,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index 25637976..b3e2faa5 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -15,6 +15,5 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } - +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-shared-resource/src/main.rs b/examples/basic-shared-resource/src/main.rs index 15c38741..15ababa0 100644 --- a/examples/basic-shared-resource/src/main.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -49,9 +49,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index a1b11567..9d259218 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -20,4 +20,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs index 319e4519..63967893 100644 --- a/examples/basic-sqs/src/main.rs +++ b/examples/basic-sqs/src/main.rs @@ -25,9 +25,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index fc284674..4bbe66f4 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -14,5 +14,5 @@ hyper = { version = "0.14", features = [ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } serde_json = "1.0" \ No newline at end of file diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 04c7f8ec..d90ebd33 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -30,9 +30,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index 94ee4926..caf0818c 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-basic/src/main.rs b/examples/extension-basic/src/main.rs index 4af6a47f..f9838c6b 100644 --- a/examples/extension-basic/src/main.rs +++ b/examples/extension-basic/src/main.rs @@ -19,9 +19,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index e585516a..d776f488 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-combined/src/main.rs b/examples/extension-combined/src/main.rs index 60d0f9e1..e05b1b7d 100644 --- a/examples/extension-combined/src/main.rs +++ b/examples/extension-combined/src/main.rs @@ -34,9 +34,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index 90c5d322..a826a137 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-custom-events/src/main.rs b/examples/extension-custom-events/src/main.rs index b7574642..1d39e20f 100644 --- a/examples/extension-custom-events/src/main.rs +++ b/examples/extension-custom-events/src/main.rs @@ -21,9 +21,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index 5396c137..c9ff789a 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-custom-service/src/main.rs b/examples/extension-custom-service/src/main.rs index fd85c91d..ec8ca68f 100644 --- a/examples/extension-custom-service/src/main.rs +++ b/examples/extension-custom-service/src/main.rs @@ -38,9 +38,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index 30c09117..d1983db8 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-logs-basic/src/main.rs b/examples/extension-logs-basic/src/main.rs index 5543dec9..77065cca 100644 --- a/examples/extension-logs-basic/src/main.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -20,9 +20,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index 35a9e05d..cbbe20f6 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-logs-custom-service/src/main.rs b/examples/extension-logs-custom-service/src/main.rs index 9137c017..ebe1330d 100644 --- a/examples/extension-logs-custom-service/src/main.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -61,9 +61,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index 547ad48e..0e056b1c 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" lambda-extension = { path = "../../lambda-extension" } tokio = { version = "1.17.0", features = ["full"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.13.0" aws-sdk-firehose = "0.13.0" diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 68c9421c..8586e1a9 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -58,9 +58,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index bc426b68..869b604d 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs index f522808c..03974bf6 100644 --- a/examples/extension-telemetry-basic/src/main.rs +++ b/examples/extension-telemetry-basic/src/main.rs @@ -45,9 +45,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index dd37346f..5a97cfab 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -20,4 +20,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index 227e23dd..bb47152d 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -97,9 +97,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 6a0e8905..50db3ebf 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -15,7 +15,7 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } axum = "0.6.4" serde_json = "1.0" diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 7770d861..c2805be1 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -42,9 +42,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index ad53161d..1a218330 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index 88db4886..5794dc8b 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -24,9 +24,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 65df64d4..9fd7f25b 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -16,6 +16,6 @@ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.3", features = ["cors"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index ea1f0372..e60fb441 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -10,9 +10,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index 6b6a0205..c3f6d8be 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -20,6 +20,6 @@ aws-sdk-dynamodb = "0.21.0" aws-config = "0.51.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 6a0c8947..5a7030f9 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -59,9 +59,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 5089a85e..7aeb1189 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 0300df37..e189d12d 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -27,9 +27,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index 4cb3e23f..f4060428 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index 3694e61b..7fa6e6d5 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -7,9 +7,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 4d655489..207f253b 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index 16493452..d76ccec4 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -17,9 +17,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index bf4a5b0c..2b8f7a60 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -16,4 +16,4 @@ lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.4", features = ["trace"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index 678d79cd..072f8256 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -14,9 +14,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-http/README.md b/lambda-http/README.md index 161a5576..464be88b 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -50,9 +50,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -89,9 +88,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -131,9 +129,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -191,9 +188,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); let config = aws_config::from_env() diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 8dd28f0a..1b0fc3ef 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -19,4 +19,4 @@ lambda-extension = { path = "../lambda-extension" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = "0.3" +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs index ea5fc26c..5e9ec553 100644 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -20,9 +20,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index ecf46c81..e2c73fa3 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -80,9 +80,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index cd252280..8107f423 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -17,9 +17,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs index 765b0d66..d8e6f74f 100644 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -75,9 +75,6 @@ impl Service for MyHandler { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs index 3f5a4909..b474bc8d 100644 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ b/lambda-integration-tests/src/bin/logs-trait.rs @@ -64,9 +64,6 @@ impl Service> for MyLogsProcessor { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs index 1b3f3e0d..d16717aa 100644 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ b/lambda-integration-tests/src/bin/runtime-fn.rs @@ -28,9 +28,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs index b925e138..0bf31e43 100644 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -84,9 +84,6 @@ impl Service> for MyHandler { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); From 33cce7745373685fe4e615aa7621fe3272c0f371 Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Mon, 10 Jul 2023 16:53:55 +0200 Subject: [PATCH 197/394] Fix s3 tests for s3 examples (#675) * upgrade basic-s3-thumbnail example + refactor test I noticed that I am testing thumbnailer module when I compared the generated thumbnail with a stored one. This can lead test failures. So I mocked the thumbnail generation. * refactor test for basic-s3-object-lambda example --------- Co-authored-by: borkupe --- .../src/main.rs | 61 +++++++----------- examples/basic-s3-thumbnail/Cargo.toml | 13 ++-- examples/basic-s3-thumbnail/README.md | 16 +++++ examples/basic-s3-thumbnail/src/main.rs | 44 ++++++------- examples/basic-s3-thumbnail/src/s3.rs | 4 +- .../basic-s3-thumbnail/testdata/image.png | Bin 282 -> 0 bytes .../basic-s3-thumbnail/testdata/thumbnail.png | Bin 82 -> 0 bytes 7 files changed, 71 insertions(+), 67 deletions(-) delete mode 100644 examples/basic-s3-thumbnail/testdata/image.png delete mode 100644 examples/basic-s3-thumbnail/testdata/thumbnail.png diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 771a829c..328e7500 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -1,10 +1,9 @@ -use std::{error, io::Cursor}; +use std::error; use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; use aws_sdk_s3::Client as S3Client; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use s3::{GetFile, SendFile}; -use thumbnailer::{create_thumbnails, ThumbnailSize}; mod s3; @@ -35,28 +34,21 @@ pub(crate) async fn function_handler( let thumbnail = get_thumbnail(image, size); tracing::info!("thumbnail created. Length: {}", thumbnail.len()); - // It sends the thumbnail back to the user - client.send_file(route, token, thumbnail).await - - /* - match client.send_file(route, token, thumbnail).await { - Ok(msg) => tracing::info!(msg), - Err(msg) => tracing::info!(msg) - }; - - tracing::info!("handler ends"); - - Ok(()) - */ } +#[cfg(not(test))] fn get_thumbnail(vec: Vec, size: u32) -> Vec { - let reader = Cursor::new(vec); - let mut thumbnails = create_thumbnails(reader, mime::IMAGE_PNG, [ThumbnailSize::Custom((size, size))]).unwrap(); + let reader = std::io::Cursor::new(vec); + let mut thumbnails = thumbnailer::create_thumbnails( + reader, + mime::IMAGE_PNG, + [thumbnailer::ThumbnailSize::Custom((size, size))], + ) + .unwrap(); let thumbnail = thumbnails.pop().unwrap(); - let mut buf = Cursor::new(Vec::new()); + let mut buf = std::io::Cursor::new(Vec::new()); thumbnail.write_png(&mut buf).unwrap(); buf.into_inner() @@ -85,11 +77,17 @@ async fn main() -> Result<(), Error> { } #[cfg(test)] -mod tests { - use std::fs::File; - use std::io::BufReader; - use std::io::Read; +fn get_thumbnail(vec: Vec, _size: u32) -> Vec { + let s = unsafe { std::str::from_utf8_unchecked(&vec) }; + match s { + "IMAGE" => "THUMBNAIL".into(), + _ => "Input is not IMAGE".into(), + } +} + +#[cfg(test)] +mod tests { use super::*; use async_trait::async_trait; use aws_lambda_events::s3::object_lambda::Configuration; @@ -122,13 +120,12 @@ mod tests { let mut mock = MockFakeS3Client::new(); mock.expect_get_file() - .withf(|u: &String| u.eq("S3_URL")) - .returning(|_1| Ok(get_file("testdata/image.png"))); + .withf(|u| u.eq("S3_URL")) + .returning(|_1| Ok("IMAGE".into())); mock.expect_send_file() - .withf(|r: &String, t: &String, by| { - let thumbnail = get_file("testdata/thumbnail.png"); - return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == &thumbnail; + .withf(|r, t, by| { + return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes(); }) .returning(|_1, _2, _3| Ok("File sent.".to_string())); @@ -141,16 +138,6 @@ mod tests { assert_eq!(("File sent."), result); } - fn get_file(name: &str) -> Vec { - let f = File::open(name); - let mut reader = BufReader::new(f.unwrap()); - let mut buffer = Vec::new(); - - reader.read_to_end(&mut buffer).unwrap(); - - return buffer; - } - fn get_s3_event() -> S3ObjectLambdaEvent { return S3ObjectLambdaEvent { x_amz_request_id: ("ID".to_string()), diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 7c788a00..6bbe11b7 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -20,14 +20,15 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } -aws-config = "0.54.1" -aws-sdk-s3 = "0.24.0" +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +aws-config = "0.55" +aws-smithy-http = "0.55.3" +aws-sdk-s3 = "0.28" thumbnailer = "0.4.0" mime = "0.3.16" -async-trait = "0.1.66" +async-trait = "0.1.68" [dev-dependencies] -mockall = "0.11.3" -tokio-test = "0.4.2" +mockall = "0.11" +tokio-test = "0.4" chrono = "0.4" diff --git a/examples/basic-s3-thumbnail/README.md b/examples/basic-s3-thumbnail/README.md index de2d56f8..000874cc 100644 --- a/examples/basic-s3-thumbnail/README.md +++ b/examples/basic-s3-thumbnail/README.md @@ -5,6 +5,22 @@ it downloads the created file, generates a thumbnail from it (it assumes that the file is an image) and uploads it to S3 into a bucket named [original-bucket-name]-thumbs. +## Set up +1. Create a lambda function and upload the bootloader.zip +2. Go to aws services S3 +3. Create a bucket, let's say with name bucketx +4. Create another bucket bucketx-thumbs +5. Got to the bucketx properties tab, event notifications +6. Create lambda event notification for "all object create event" and select your lambda function +7. Go to the lambda function, configuration and open the role name +8. Add "AmazonS3FullAccess" permission + +## Test + +1. Go to S3 and upload a png picture into bucketx. Beware to not have spaces or any special characters in the file name +2. Go to S3 bucketx-thumbs and check if an image is created there. + + ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d996de0c..d92c822b 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -1,10 +1,7 @@ -use std::io::Cursor; - use aws_lambda_events::{event::s3::S3Event, s3::S3EventRecord}; use aws_sdk_s3::Client as S3Client; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use s3::{GetFile, PutFile}; -use thumbnailer::{create_thumbnails, ThumbnailSize}; mod s3; @@ -86,7 +83,12 @@ fn get_file_props(record: S3EventRecord) -> Result<(String, String), String> { Ok((bucket, key)) } +#[cfg(not(test))] fn get_thumbnail(vec: Vec, size: u32) -> Result, String> { + use std::io::Cursor; + + use thumbnailer::{create_thumbnails, ThumbnailSize}; + let reader = Cursor::new(vec); let mime = mime::IMAGE_PNG; let sizes = [ThumbnailSize::Custom((size, size))]; @@ -126,12 +128,19 @@ async fn main() -> Result<(), Error> { Ok(()) } +#[cfg(test)] +fn get_thumbnail(vec: Vec, _size: u32) -> Result, String> { + let s = unsafe { std::str::from_utf8_unchecked(&vec) }; + + match s { + "IMAGE" => Ok("THUMBNAIL".into()), + _ => Err("Input is not IMAGE".to_string()), + } +} + #[cfg(test)] mod tests { use std::collections::HashMap; - use std::fs::File; - use std::io::BufReader; - use std::io::Read; use super::*; use async_trait::async_trait; @@ -141,7 +150,7 @@ mod tests { use aws_lambda_events::s3::S3Object; use aws_lambda_events::s3::S3RequestParameters; use aws_lambda_events::s3::S3UserIdentity; - use aws_sdk_s3::error::GetObjectError; + use aws_sdk_s3::operation::get_object::GetObjectError; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; use s3::GetFile; @@ -171,15 +180,14 @@ mod tests { let mut mock = MockFakeS3Client::new(); mock.expect_get_file() - .withf(|b: &str, k: &str| b.eq(bucket) && k.eq(key)) - .returning(|_1, _2| Ok(get_file("testdata/image.png"))); + .withf(|b, k| b.eq(bucket) && k.eq(key)) + .returning(|_1, _2| Ok("IMAGE".into())); mock.expect_put_file() - .withf(|bu: &str, ke: &str, by| { - let thumbnail = get_file("testdata/thumbnail.png"); - return bu.eq("test-bucket-thumbs") && ke.eq(key) && by == &thumbnail; + .withf(|bu, ke, by| { + return bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes()); }) - .returning(|_1, _2, _3| Ok("Done".to_string())); + .return_const(Ok("Done".to_string())); let payload = get_s3_event("ObjectCreated", bucket, key); let event = LambdaEvent { payload, context }; @@ -189,16 +197,6 @@ mod tests { assert_eq!((), result); } - fn get_file(name: &str) -> Vec { - let f = File::open(name); - let mut reader = BufReader::new(f.unwrap()); - let mut buffer = Vec::new(); - - reader.read_to_end(&mut buffer).unwrap(); - - return buffer; - } - fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { return S3Event { records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 83ef7bc7..17d7f975 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; -use aws_sdk_s3::{error::GetObjectError, types::ByteStream, Client as S3Client}; +use aws_sdk_s3::operation::get_object::GetObjectError; +use aws_sdk_s3::Client as S3Client; +use aws_smithy_http::byte_stream::ByteStream; #[async_trait] pub trait GetFile { diff --git a/examples/basic-s3-thumbnail/testdata/image.png b/examples/basic-s3-thumbnail/testdata/image.png deleted file mode 100644 index 078d155f6bf6735eb087eb0195b3e35f9f424d04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-UBp4!QuJ{S0SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+uenM@oty!5+IMg#M9T6{W-Ikn3DL(-uF5{ArVg(#}JFt$q5pyixWh8ngSjC85meA z7#KARcm4s&tCqM%l%ynf4NqV|ChEy=Vy|59;VQAR!XXWJ= d863$M85tR8F)&*H Date: Sun, 16 Jul 2023 23:53:01 +0300 Subject: [PATCH 198/394] Serialize APIGW queryStringParameters properly (#676) Reference: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html 'queryStringParameters' were serialized incorrectly to format like queryStringParameters:{"key":["value"]} when it should be like queryStringParameters:{"key":"value"} --- lambda-events/Cargo.toml | 2 +- lambda-events/src/event/apigw/mod.rs | 30 ++++ ...-apigw-request-multi-value-parameters.json | 136 ++++++++++++++++++ ...igw-v2-request-multi-value-parameters.json | 61 ++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 28df6b4a..27b577cb 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -30,7 +30,7 @@ chrono = { version = "0.4.23", default-features = false, features = [ "serde", "std", ], optional = true } -query_map = { version = "^0.6", features = ["serde", "url-query"], optional = true } +query_map = { version = "^0.7", features = ["serde", "url-query"], optional = true } flate2 = { version = "1.0.24", optional = true } [features] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index b595d825..9119bdc7 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -34,6 +34,7 @@ where #[serde(serialize_with = "serialize_multi_value_headers")] pub multi_value_headers: HeaderMap, #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + #[serde(serialize_with = "query_map::serde::aws_api_gateway_v1::serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] pub multi_value_query_string_parameters: QueryMap, @@ -151,6 +152,7 @@ pub struct ApiGatewayV2httpRequest { deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" )] #[serde(skip_serializing_if = "QueryMap::is_empty")] + #[serde(serialize_with = "query_map::serde::aws_api_gateway_v2::serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] @@ -832,6 +834,21 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request_multi_value_parameters() { + let data = include_bytes!("../../fixtures/example-apigw-request-multi-value-parameters.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert!(output.contains(r#""multiValueQueryStringParameters":{"name":["me","me2"]}"#)); + assert!(output.contains(r#""queryStringParameters":{"name":"me"}"#)); + assert!(output.contains(r#""headername":["headerValue","headerValue2"]"#)); + assert!(output.contains(r#""headername":"headerValue2""#)); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_restapi_openapi_request() { @@ -872,6 +889,19 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_multi_value_parameters() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-multi-value-parameters.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert!(output.contains(r#""header2":"value1,value2""#)); + assert!(output.contains(r#""queryStringParameters":{"Parameter1":"value1,value2"}"#)); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_v2_request_no_authorizer() { diff --git a/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json b/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json new file mode 100644 index 00000000..340b1df1 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json @@ -0,0 +1,136 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue2", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue", + "headerValue2" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": [ + "me", "me2" + ] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser" + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json b/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json new file mode 100644 index 00000000..5094d6c2 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json @@ -0,0 +1,61 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "Parameter1=value1&Parameter1=value2", + "cookies": [ + "cookie1", + "cookie2" + ], + "headers": { + "Header2": "value1,value2" + }, + "queryStringParameters": { + "Parameter1": "value1,value2" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "lambda": { + "key": "value" + } + }, + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "time": "12/Mar/2020:19:03:58+0000", + "timeEpoch": 1583348638390, + "http": { + "method": "GET", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + } + }, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} + From b7f198d4437470273a751f4c12ad153a5049149b Mon Sep 17 00:00:00 2001 From: sumagowda Date: Mon, 31 Jul 2023 08:26:55 -0700 Subject: [PATCH 199/394] Example that uses Axum and Diesel to connect to a PostgreSQL database with SSLmode ON (#682) Co-authored-by: Suma Gowda --- examples/http-axum-diesel-ssl/.DS_Store | Bin 0 -> 6148 bytes examples/http-axum-diesel-ssl/Cargo.toml | 28 +++ examples/http-axum-diesel-ssl/README.md | 13 ++ .../2023-04-07-231632_create_posts/down.sql | 2 + .../2023-04-07-231632_create_posts/up.sql | 7 + examples/http-axum-diesel-ssl/src/main.rs | 165 ++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 examples/http-axum-diesel-ssl/.DS_Store create mode 100755 examples/http-axum-diesel-ssl/Cargo.toml create mode 100755 examples/http-axum-diesel-ssl/README.md create mode 100755 examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql create mode 100755 examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql create mode 100755 examples/http-axum-diesel-ssl/src/main.rs diff --git a/examples/http-axum-diesel-ssl/.DS_Store b/examples/http-axum-diesel-ssl/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..06fbd0f6d1e6ab54258b97a348681bc810b1d5ae GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8O({YS3Oz1(Etp6_#Y>3w1&ruHr6#6mFlI}V+CwSitS{t~_&m<+ zZop#BB6bFLzxmzGevtiPjB$S+yNub4F$)?ZN2Ni~-56?_WJHc*6tgIm5m+D5%*6gW z;J3F~!E*KxExvz$niQqueDYSiv$fr|x>n!13!da6D8q7I_+fs7)}@qbRO?}Ml}r~C zd-qIcWte2sTooko6jE-llPs1CU(U0*P_=;$SUsyZu?NfL$T=Pg*L7Ayu{;@#hT`bZ zU9Ec7-u}Vq#pF4C$>f`+lLOmIb_~|=4vJaLYmjHD%pSp0XV+PT!~iis3=jjG&44)< zto~-xK&vMPh=B$MaDNcc5IuvHMzwW7hu3F}cMwrP$F~HcFz6YqG(rS~>ry~n%FPpl z>vHf5ljj+%H0pB3)yy!CnYn(va5X#lg-U1K(?~rrKn$!i(AK7n=l?nUGL4V?^%Am( z0b<~vF~B?HVC=)9%-Q;5d3e?eX!p=iFt0)d1oX8_02sKB3{+6Z1?rIJ8LTwoDCk$^ PfOHX1giuEe`~m}CkhDpD literal 0 HcmV?d00001 diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml new file mode 100755 index 00000000..cdcdd4ef --- /dev/null +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "http-axum-diesel" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +axum = "0.6.4" +bb8 = "0.8.0" +diesel = "2.0.3" +diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.159" +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +futures-util = "0.3.21" +rustls = "0.20.8" +rustls-native-certs = "0.6.2" +tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio-postgres = "0.7.7" +tokio-postgres-rustls = "0.9.0" \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/README.md b/examples/http-axum-diesel-ssl/README.md new file mode 100755 index 00000000..8b2330f5 --- /dev/null +++ b/examples/http-axum-diesel-ssl/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function example + +This example shows how to develop a REST API with Axum and Diesel that connects to a Postgres database. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql new file mode 100755 index 00000000..e00da655 --- /dev/null +++ b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE posts \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql new file mode 100755 index 00000000..aa684de6 --- /dev/null +++ b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR NOT NULL, + content TEXT NOT NULL, + published BOOLEAN NOT NULL DEFAULT FALSE +) \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs new file mode 100755 index 00000000..c2f6b933 --- /dev/null +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -0,0 +1,165 @@ +use diesel::{ConnectionError, ConnectionResult}; +use futures_util::future::BoxFuture; +use futures_util::FutureExt; +use std::time::Duration; + +use axum::{ + extract::{Path, State}, + response::Json, + routing::get, + Router, +}; +use bb8::Pool; +use diesel::prelude::*; +use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use lambda_http::{http::StatusCode, run, Error}; +use serde::{Deserialize, Serialize}; + +table! { + posts (id) { + id -> Integer, + title -> Text, + content -> Text, + published -> Bool, + } +} + +#[derive(Default, Queryable, Selectable, Serialize)] +struct Post { + id: i32, + title: String, + content: String, + published: bool, +} + +#[derive(Deserialize, Insertable)] +#[diesel(table_name = posts)] +struct NewPost { + title: String, + content: String, + published: bool, +} + +type AsyncPool = Pool>; +type ServerError = (StatusCode, String); + +async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = diesel::insert_into(posts::table) + .values(post) + .returning(Post::as_returning()) + .get_result(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn list_posts(State(pool): State) -> Result>, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let posts = posts::table + .filter(posts::dsl::published.eq(true)) + .load(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(posts)) +} + +async fn get_post(State(pool): State, Path(post_id): Path) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = posts::table + .find(post_id) + .first(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn delete_post(State(pool): State, Path(post_id): Path) -> Result<(), ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + diesel::delete(posts::table.find(post_id)) + .execute(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(()) +} + +fn internal_server_error(err: E) -> ServerError { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + // Set up the database connection + // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require + let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); + + let mgr = AsyncDieselConnectionManager::::new_with_setup( + db_url, + establish_connection, + ); + + let pool = Pool::builder() + .max_size(10) + .min_idle(Some(5)) + .max_lifetime(Some(Duration::from_secs(60 * 60 * 24))) + .idle_timeout(Some(Duration::from_secs(60 * 2))) + .build(mgr) + .await?; + + // Set up the API routes + let posts_api = Router::new() + .route("/", get(list_posts).post(create_post)) + .route("/:id", get(get_post).delete(delete_post)) + .route("/get", get(list_posts)) + .route("/get/:id", get(get_post)); + let app = Router::new().nest("/posts", posts_api).with_state(pool); + + run(app).await +} + + +fn establish_connection(config: &str) -> BoxFuture> { + let fut = async { + // We first set up the way we want rustls to work. + let rustls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_certs()) + .with_no_client_auth(); + let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config); + let (client, conn) = tokio_postgres::connect(config, tls) + .await + .map_err(|e| ConnectionError::BadConnection(e.to_string()))?; + tokio::spawn(async move { + if let Err(e) = conn.await { + eprintln!("Database connection: {e}"); + } + }); + AsyncPgConnection::try_from(client).await + }; + fut.boxed() +} + +fn root_certs() -> rustls::RootCertStore { + let mut roots = rustls::RootCertStore::empty(); + let certs = rustls_native_certs::load_native_certs().expect("Certs not loadable!"); + let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect(); + roots.add_parsable_certificates(&certs); + roots +} From 15dbde0a9c77d741e58578662a8cd36987f915a5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 28 Aug 2023 17:59:24 -0700 Subject: [PATCH 200/394] Add history section to the readme. (#687) Signed-off-by: David Calavera --- lambda-events/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lambda-events/README.md b/lambda-events/README.md index 0813c63a..8446ce55 100644 --- a/lambda-events/README.md +++ b/lambda-events/README.md @@ -27,6 +27,12 @@ This crate divides all Lambda Events into features named after the service that cargo add aws_lambda_events --no-default-features --features apigw,alb ``` +## History + +The AWS Lambda Events crate was created by [Christian Legnitto](https://github.com/LegNeato). Without all his work and dedication, this project could have not been possible. + +In 2023, the AWS Lambda Event crate was moved into this repository to continue its support for all AWS customers that use Rust on AWS Lambda. + [//]: # 'badges' [crate-image]: https://img.shields.io/crates/v/aws_lambda_events.svg [crate-link]: https://crates.io/crates/aws_lambda_events From 1781f890a3e63fd73b5a00b8d62bce057a19e65a Mon Sep 17 00:00:00 2001 From: FalkWoldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:21:14 +0200 Subject: [PATCH 201/394] Make Kafka header values i8 instead of u8 (#689) --- lambda-events/src/event/kafka/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 07299859..8cd92bdf 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -28,7 +28,7 @@ pub struct KafkaRecord { pub timestamp_type: Option, pub key: Option, pub value: Option, - pub headers: Vec>>, + pub headers: Vec>>, } #[cfg(test)] From e6ac88fe43edf1fcb59ad0c91487f23f6b267e88 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 4 Sep 2023 19:59:44 -0700 Subject: [PATCH 202/394] Fix streaming prelude serialization (#692) * Fix streaming prelude serialization HTTP headers can be multi-value, but the current implementation ignores this fact and only serializes the first value for each header. This changes uses http-serde to serialize the prelude correctly. Signed-off-by: David Calavera * Update MSRV Some dependencies have dropped support for 1.62 already. Signed-off-by: David Calavera * Remove unwrap Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime/Cargo.toml | 1 + lambda-runtime/src/streaming.rs | 68 ++++++++++++++------------- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 3a56e597..4e5fb34d 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 0905f289..7365bc64 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 68913c95..9657a840 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index 12dbf523..5f6f899a 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.62, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.64, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1de6f361..197265ab 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -42,3 +42,4 @@ tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" +http-serde = "1.1.3" diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs index e541f3d6..5ea369ad 100644 --- a/lambda-runtime/src/streaming.rs +++ b/lambda-runtime/src/streaming.rs @@ -5,13 +5,11 @@ use crate::{ use bytes::Bytes; use futures::FutureExt; use http::header::{CONTENT_TYPE, SET_COOKIE}; -use http::{Method, Request, Response, Uri}; +use http::{HeaderMap, Method, Request, Response, StatusCode, Uri}; use hyper::body::HttpBody; use hyper::{client::connect::Connection, Body}; use lambda_runtime_api_client::{build_request, Client}; -use serde::Deserialize; -use serde_json::json; -use std::collections::HashMap; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::{ env, @@ -203,6 +201,16 @@ pub(crate) struct EventCompletionStreamingRequest<'a, B> { pub(crate) body: Response, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct MetadataPrelude { + #[serde(serialize_with = "http_serde::status_code::serialize")] + status_code: StatusCode, + #[serde(serialize_with = "http_serde::header_map::serialize")] + headers: HeaderMap, + cookies: Vec, +} + impl<'a, B> IntoRequest for EventCompletionStreamingRequest<'a, B> where B: HttpBody + Unpin + Send + 'static, @@ -216,45 +224,39 @@ where let (parts, mut body) = self.body.into_parts(); let mut builder = build_request().method(Method::POST).uri(uri); - let headers = builder.headers_mut().unwrap(); + let req_headers = builder.headers_mut().unwrap(); - headers.insert("Transfer-Encoding", "chunked".parse()?); - headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); - headers.insert( + req_headers.insert("Transfer-Encoding", "chunked".parse()?); + req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); + req_headers.insert( "Content-Type", "application/vnd.awslambda.http-integration-response".parse()?, ); - let (mut tx, rx) = Body::channel(); + let mut prelude_headers = parts.headers; + // default Content-Type + prelude_headers + .entry(CONTENT_TYPE) + .or_insert("application/octet-stream".parse()?); - tokio::spawn(async move { - let mut header_map = parts.headers; - // default Content-Type - header_map - .entry(CONTENT_TYPE) - .or_insert("application/octet-stream".parse().unwrap()); + let cookies = prelude_headers.get_all(SET_COOKIE); + let cookies = cookies + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + prelude_headers.remove(SET_COOKIE); - let cookies = header_map.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); + let metadata_prelude = serde_json::to_string(&MetadataPrelude { + status_code: parts.status, + headers: prelude_headers, + cookies, + })?; - let headers = header_map - .iter() - .filter(|(k, _)| *k != SET_COOKIE) - .map(|(k, v)| (k.as_str(), String::from_utf8_lossy(v.as_bytes()).to_string())) - .collect::>(); + trace!(?metadata_prelude); - let metadata_prelude = json!({ - "statusCode": parts.status.as_u16(), - "headers": headers, - "cookies": cookies, - }) - .to_string(); - - trace!("metadata_prelude: {}", metadata_prelude); + let (mut tx, rx) = Body::channel(); + tokio::spawn(async move { tx.send_data(metadata_prelude.into()).await.unwrap(); tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); From d1d58235a48511448c47f1314a75ed397cbf94d2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 5 Sep 2023 07:54:30 -0700 Subject: [PATCH 203/394] Release runtime and events new versions (#693) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 27b577cb..a21973b2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.11.0" +version = "0.11.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 197265ab..1137f845 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.1" +version = "0.8.2" authors = [ "David Calavera ", "Harold Sun ", From e2d51ad4e3bb2c04162049eaf4b50cbdff0fe06c Mon Sep 17 00:00:00 2001 From: Chris Leach <7308018+chris-leach@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:26:28 +0100 Subject: [PATCH 204/394] Add event definitions for CloudFormation custom resources (#695) * Add event definitions for CloudFormation custom resources * Remove let else statements * Fix codebuild_time for chrono ^0.4.29 by parsing via NaiveDateTime --------- Co-authored-by: Chris Leach --- lambda-events/Cargo.toml | 2 + .../src/custom_serde/codebuild_time.rs | 17 ++- lambda-events/src/event/cloudformation/mod.rs | 143 ++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + ...mation-custom-resource-create-request.json | 14 ++ ...mation-custom-resource-delete-request.json | 15 ++ ...mation-custom-resource-update-request.json | 20 +++ lambda-events/src/lib.rs | 5 + 8 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 lambda-events/src/event/cloudformation/mod.rs create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index a21973b2..e401bfff 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -42,6 +42,7 @@ default = [ "autoscaling", "chime_bot", "clientvpn", + "cloudformation", "cloudwatch_events", "cloudwatch_logs", "code_commit", @@ -81,6 +82,7 @@ appsync = [] autoscaling = ["chrono"] chime_bot = ["chrono"] clientvpn = [] +cloudformation = [] cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] code_commit = ["chrono"] diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 94d0e2f5..bd132b23 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError, Visitor}, @@ -18,7 +18,8 @@ impl<'de> Visitor<'de> for TimeVisitor { } fn visit_str(self, val: &str) -> Result { - Utc.datetime_from_str(val, CODEBUILD_TIME_FORMAT) + NaiveDateTime::parse_from_str(val, CODEBUILD_TIME_FORMAT) + .map(|naive| naive.and_utc()) .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) } } @@ -81,9 +82,9 @@ mod tests { "date": "Sep 1, 2017 4:12:29 PM" }); - let expected = Utc - .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) - .unwrap(); + let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap() + .and_utc(); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(expected, decoded.date); } @@ -99,9 +100,9 @@ mod tests { "date": "Sep 1, 2017 4:12:29 PM" }); - let expected = Utc - .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) - .unwrap(); + let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap() + .and_utc(); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(Some(expected), decoded.date); } diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs new file mode 100644 index 00000000..e2d51745 --- /dev/null +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -0,0 +1,143 @@ +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(tag = "RequestType")] +pub enum CloudFormationCustomResourceRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(bound = "")] + Create(CreateRequest), + #[serde(bound = "")] + Update(UpdateRequest), + #[serde(bound = "")] + Delete(DeleteRequest), +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct CreateRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct UpdateRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + pub physical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, + #[serde(bound = "")] + pub old_resource_properties: P1, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct DeleteRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + pub physical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use super::CloudFormationCustomResourceRequest::*; + use super::*; + + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] + #[serde(rename_all = "PascalCase")] + #[serde(deny_unknown_fields)] + struct TestProperties { + key_1: String, + key_2: Vec, + key_3: HashMap, + } + + type TestRequest = CloudFormationCustomResourceRequest; + + #[test] + fn example_cloudformation_custom_resource_create_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-create-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Create(_) => (), + _ => panic!("expected Create request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_cloudformation_custom_resource_update_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-update-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Update(_) => (), + _ => panic!("expected Update request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_cloudformation_custom_resource_delete_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-delete-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Delete(_) => (), + _ => panic!("expected Delete request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 1aa56697..4ce71dfc 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -25,6 +25,10 @@ pub mod chime_bot; #[cfg(feature = "clientvpn")] pub mod clientvpn; +/// AWS Lambda event definitions for cloudformation. +#[cfg(feature = "cloudformation")] +pub mod cloudformation; + /// CloudWatch Events payload #[cfg(feature = "cloudwatch_events")] pub mod cloudwatch_events; diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json new file mode 100644 index 00000000..d35dd6f7 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json @@ -0,0 +1,14 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Create", + "RequestId" : "82304eb2-bdda-469f-a33b-a3f1406d0a52", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json new file mode 100644 index 00000000..bd788c99 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json @@ -0,0 +1,15 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Delete", + "RequestId" : "ef70561d-d4ba-42a4-801b-33ad88dafc37", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json new file mode 100644 index 00000000..4fc4378a --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json @@ -0,0 +1,20 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Update", + "RequestId" : "49347ca5-c603-44e5-a34b-10cf1854a887", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "new-string", + "Key2" : [ "new-list" ], + "Key3" : { "Key4" : "new-map" } + }, + "OldResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 564debd7..7402a8f4 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -20,6 +20,7 @@ pub use event::activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] pub use event::alb; + /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] pub use event::apigw; @@ -40,6 +41,10 @@ pub use event::chime_bot; #[cfg(feature = "clientvpn")] pub use event::clientvpn; +/// AWS Lambda event definitions for cloudformation +#[cfg(feature = "cloudformation")] +pub use event::cloudformation; + /// CloudWatch Events payload #[cfg(feature = "cloudwatch_events")] pub use event::cloudwatch_events; From cf72bb05c59c3ca094d169e924774f17972f4b2d Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Fri, 15 Sep 2023 10:46:17 +0800 Subject: [PATCH 205/394] Refactor Lambda response streaming. (#696) * Refactor Lambda response streaming. Remove the separate streaming.rs from lambda-runtime crate. Merge into the `run` method. Added FunctionResponse enum to capture both buffered response and streaming response. Added IntoFunctionResponse trait to convert `Serialize` response into FunctionResponse::BufferedResponse, and convert `Stream` response into FunctionResponse::StreamingResponse. Existing handler functions should continue to work. Improved error handling in response streaming. Return trailers to report errors instead of panic. * Add comments for reporting midstream errors using error trailers * Remove "pub" from internal run method --- examples/basic-streaming-response/README.md | 2 +- examples/basic-streaming-response/src/main.rs | 18 +- lambda-http/Cargo.toml | 1 + lambda-http/src/streaming.rs | 67 ++++- lambda-runtime/Cargo.toml | 2 + lambda-runtime/src/lib.rs | 29 +- lambda-runtime/src/requests.rs | 94 +++++- lambda-runtime/src/streaming.rs | 272 ------------------ lambda-runtime/src/types.rs | 89 +++++- 9 files changed, 266 insertions(+), 308 deletions(-) delete mode 100644 lambda-runtime/src/streaming.rs diff --git a/examples/basic-streaming-response/README.md b/examples/basic-streaming-response/README.md index 3b68f518..ac744a33 100644 --- a/examples/basic-streaming-response/README.md +++ b/examples/basic-streaming-response/README.md @@ -6,7 +6,7 @@ 2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` 4. Enable Lambda streaming response on Lambda console: change the function url's invoke mode to `RESPONSE_STREAM` -5. Verify the function works: `curl `. The results should be streamed back with 0.5 second pause between each word. +5. Verify the function works: `curl -v -N `. The results should be streamed back with 0.5 second pause between each word. ## Build for ARM 64 diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index d90ebd33..9d505206 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,9 +1,9 @@ -use hyper::{body::Body, Response}; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use hyper::body::Body; +use lambda_runtime::{service_fn, Error, LambdaEvent, StreamResponse}; use serde_json::Value; use std::{thread, time::Duration}; -async fn func(_event: LambdaEvent) -> Result, Error> { +async fn func(_event: LambdaEvent) -> Result, Error> { let messages = vec!["Hello", "world", "from", "Lambda!"]; let (mut tx, rx) = Body::channel(); @@ -15,12 +15,10 @@ async fn func(_event: LambdaEvent) -> Result, Error> { } }); - let resp = Response::builder() - .header("content-type", "text/html") - .header("CustomHeader", "outerspace") - .body(rx)?; - - Ok(resp) + Ok(StreamResponse { + metadata_prelude: Default::default(), + stream: rx, + }) } #[tokio::main] @@ -34,6 +32,6 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - lambda_runtime::run_with_streaming_response(service_fn(func)).await?; + lambda_runtime::run(service_fn(func)).await?; Ok(()) } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index be111092..ea4a5fba 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -33,6 +33,7 @@ lambda_runtime = { path = "../lambda-runtime", version = "0.8" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" +tokio-stream = "0.1.2" mime = "0.3" encoding_rs = "0.8" url = "2.2" diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 9a27d915..a59cf700 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,3 +1,4 @@ +use crate::http::header::SET_COOKIE; use crate::tower::ServiceBuilder; use crate::Request; use crate::{request::LambdaRequest, RequestExt}; @@ -5,9 +6,14 @@ pub use aws_lambda_events::encodings::Body as LambdaEventBody; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; -use lambda_runtime::LambdaEvent; -pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +pub use lambda_runtime::{ + self, service_fn, tower, tower::ServiceExt, Error, FunctionResponse, LambdaEvent, MetadataPrelude, Service, + StreamResponse, +}; use std::fmt::{Debug, Display}; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio_stream::Stream; /// Starts the Lambda Rust runtime and stream response back [Configure Lambda /// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). @@ -28,7 +34,60 @@ where let event: Request = req.payload.into(); event.with_lambda_context(req.context) }) - .service(handler); + .service(handler) + .map_response(|res| { + let (parts, body) = res.into_parts(); - lambda_runtime::run_with_streaming_response(svc).await + let mut prelude_headers = parts.headers; + + let cookies = prelude_headers.get_all(SET_COOKIE); + let cookies = cookies + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + + prelude_headers.remove(SET_COOKIE); + + let metadata_prelude = MetadataPrelude { + headers: prelude_headers, + status_code: parts.status, + cookies, + }; + + StreamResponse { + metadata_prelude, + stream: BodyStream { body }, + } + }); + + lambda_runtime::run(svc).await +} + +pub struct BodyStream { + pub(crate) body: B, +} + +impl BodyStream +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + fn project(self: Pin<&mut Self>) -> Pin<&mut B> { + unsafe { self.map_unchecked_mut(|s| &mut s.body) } + } +} + +impl Stream for BodyStream +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let body = self.project(); + body.poll_data(cx) + } } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1137f845..9202b1c1 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -43,3 +43,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" http-serde = "1.1.3" +base64 = "0.20.0" +http-body = "0.4" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e3ffd49d..18b1066e 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,6 +7,7 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. +use bytes::Bytes; use futures::FutureExt; use hyper::{ client::{connect::Connection, HttpConnector}, @@ -20,6 +21,7 @@ use std::{ env, fmt::{self, Debug, Display}, future::Future, + marker::PhantomData, panic, }; use tokio::io::{AsyncRead, AsyncWrite}; @@ -35,11 +37,8 @@ mod simulated; /// Types available to a Lambda function. mod types; -mod streaming; -pub use streaming::run_with_streaming_response; - use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; -pub use types::{Context, LambdaEvent}; +pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::Error; @@ -97,17 +96,21 @@ where C::Error: Into>, C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { - async fn run( + async fn run( &self, incoming: impl Stream, Error>> + Send, mut handler: F, ) -> Result<(), Error> where F: Service>, - F::Future: Future>, + F::Future: Future>, F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, + R: IntoFunctionResponse, B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { let client = &self.client; tokio::pin!(incoming); @@ -177,6 +180,8 @@ where EventCompletionRequest { request_id, body: response, + _unused_b: PhantomData, + _unused_s: PhantomData, } .into_req() } @@ -243,13 +248,17 @@ where /// Ok(event.payload) /// } /// ``` -pub async fn run(handler: F) -> Result<(), Error> +pub async fn run(handler: F) -> Result<(), Error> where F: Service>, - F::Future: Future>, + F::Future: Future>, F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, + R: IntoFunctionResponse, B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { trace!("Loading config from env"); let config = Config::from_env()?; @@ -293,7 +302,7 @@ mod endpoint_tests { use lambda_runtime_api_client::Client; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env}; + use std::{convert::TryFrom, env, marker::PhantomData}; use tokio::{ io::{self, AsyncRead, AsyncWrite}, select, @@ -430,6 +439,8 @@ mod endpoint_tests { let req = EventCompletionRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", body: "done", + _unused_b: PhantomData::<&str>, + _unused_s: PhantomData::, }; let req = req.into_req()?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 26257d20..8e72fc2d 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,9 +1,15 @@ -use crate::{types::Diagnostic, Error}; +use crate::types::ToStreamErrorTrailer; +use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; +use bytes::Bytes; +use http::header::CONTENT_TYPE; use http::{Method, Request, Response, Uri}; use hyper::Body; use lambda_runtime_api_client::build_request; use serde::Serialize; +use std::fmt::Debug; +use std::marker::PhantomData; use std::str::FromStr; +use tokio_stream::{Stream, StreamExt}; pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; @@ -65,23 +71,87 @@ fn test_next_event_request() { } // /runtime/invocation/{AwsRequestId}/response -pub(crate) struct EventCompletionRequest<'a, T> { +pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> +where + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ pub(crate) request_id: &'a str, - pub(crate) body: T, + pub(crate) body: R, + pub(crate) _unused_b: PhantomData, + pub(crate) _unused_s: PhantomData, } -impl<'a, T> IntoRequest for EventCompletionRequest<'a, T> +impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> where - T: for<'serialize> Serialize, + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { fn into_req(self) -> Result, Error> { - let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); - let uri = Uri::from_str(&uri)?; - let body = serde_json::to_vec(&self.body)?; - let body = Body::from(body); + match self.body.into_response() { + FunctionResponse::BufferedResponse(body) => { + let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); + let uri = Uri::from_str(&uri)?; - let req = build_request().method(Method::POST).uri(uri).body(body)?; - Ok(req) + let body = serde_json::to_vec(&body)?; + let body = Body::from(body); + + let req = build_request().method(Method::POST).uri(uri).body(body)?; + Ok(req) + } + FunctionResponse::StreamingResponse(mut response) => { + let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); + let uri = Uri::from_str(&uri)?; + + let mut builder = build_request().method(Method::POST).uri(uri); + let req_headers = builder.headers_mut().unwrap(); + + req_headers.insert("Transfer-Encoding", "chunked".parse()?); + req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); + // Report midstream errors using error trailers. + // See the details in Lambda Developer Doc: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-response-streaming + req_headers.append("Trailer", "Lambda-Runtime-Function-Error-Type".parse()?); + req_headers.append("Trailer", "Lambda-Runtime-Function-Error-Body".parse()?); + req_headers.insert( + "Content-Type", + "application/vnd.awslambda.http-integration-response".parse()?, + ); + + // default Content-Type + let preloud_headers = &mut response.metadata_prelude.headers; + preloud_headers + .entry(CONTENT_TYPE) + .or_insert("application/octet-stream".parse()?); + + let metadata_prelude = serde_json::to_string(&response.metadata_prelude)?; + + tracing::trace!(?metadata_prelude); + + let (mut tx, rx) = Body::channel(); + + tokio::spawn(async move { + tx.send_data(metadata_prelude.into()).await.unwrap(); + tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); + + while let Some(chunk) = response.stream.next().await { + let chunk = match chunk { + Ok(chunk) => chunk.into(), + Err(err) => err.into().to_tailer().into(), + }; + tx.send_data(chunk).await.unwrap(); + } + }); + + let req = builder.body(rx)?; + Ok(req) + } + } } } @@ -90,6 +160,8 @@ fn test_event_completion_request() { let req = EventCompletionRequest { request_id: "id", body: "hello, world!", + _unused_b: PhantomData::<&str>, + _unused_s: PhantomData::, }; let req = req.into_req().unwrap(); let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/response"); diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs deleted file mode 100644 index 5ea369ad..00000000 --- a/lambda-runtime/src/streaming.rs +++ /dev/null @@ -1,272 +0,0 @@ -use crate::{ - build_event_error_request, deserializer, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, - IntoRequest, LambdaEvent, Runtime, -}; -use bytes::Bytes; -use futures::FutureExt; -use http::header::{CONTENT_TYPE, SET_COOKIE}; -use http::{HeaderMap, Method, Request, Response, StatusCode, Uri}; -use hyper::body::HttpBody; -use hyper::{client::connect::Connection, Body}; -use lambda_runtime_api_client::{build_request, Client}; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; -use std::{ - env, - fmt::{self, Debug, Display}, - future::Future, - panic, -}; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio_stream::{Stream, StreamExt}; -use tower::{Service, ServiceExt}; -use tracing::{error, trace, Instrument}; - -/// Starts the Lambda Rust runtime and stream response back [Configure Lambda -/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). -/// -/// # Example -/// ```no_run -/// use hyper::{body::Body, Response}; -/// use lambda_runtime::{service_fn, Error, LambdaEvent}; -/// use std::{thread, time::Duration}; -/// use serde_json::Value; -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Error> { -/// lambda_runtime::run_with_streaming_response(service_fn(func)).await?; -/// Ok(()) -/// } -/// async fn func(_event: LambdaEvent) -> Result, Error> { -/// let messages = vec!["Hello ", "world ", "from ", "Lambda!"]; -/// -/// let (mut tx, rx) = Body::channel(); -/// -/// tokio::spawn(async move { -/// for message in messages.iter() { -/// tx.send_data((*message).into()).await.unwrap(); -/// thread::sleep(Duration::from_millis(500)); -/// } -/// }); -/// -/// let resp = Response::builder() -/// .header("content-type", "text/plain") -/// .header("CustomHeader", "outerspace") -/// .body(rx)?; -/// -/// Ok(resp) -/// } -/// ``` -pub async fn run_with_streaming_response(handler: F) -> Result<(), Error> -where - F: Service>, - F::Future: Future, F::Error>>, - F::Error: Debug + Display, - A: for<'de> Deserialize<'de>, - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - trace!("Loading config from env"); - let config = Config::from_env()?; - let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client, config }; - - let client = &runtime.client; - let incoming = incoming(client); - runtime.run_with_streaming_response(incoming, handler).await -} - -impl Runtime -where - C: Service + Clone + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send, - C::Error: Into>, - C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, -{ - async fn run_with_streaming_response( - &self, - incoming: impl Stream, Error>> + Send, - mut handler: F, - ) -> Result<(), Error> - where - F: Service>, - F::Future: Future, F::Error>>, - F::Error: fmt::Debug + fmt::Display, - A: for<'de> Deserialize<'de>, - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, - { - let client = &self.client; - tokio::pin!(incoming); - while let Some(next_event_response) = incoming.next().await { - trace!("New event arrived (run loop)"); - let event = next_event_response?; - let (parts, body) = event.into_parts(); - - #[cfg(debug_assertions)] - if parts.status == http::StatusCode::NO_CONTENT { - // Ignore the event if the status code is 204. - // This is a way to keep the runtime alive when - // there are no events pending to be processed. - continue; - } - - let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(&self.config); - let request_id = &ctx.request_id.clone(); - - let request_span = match &ctx.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = request_id) - } - }; - - // Group the handling in one future and instrument it with the span - async { - let body = hyper::body::to_bytes(body).await?; - trace!("incoming request payload - {}", std::str::from_utf8(&body)?); - - #[cfg(debug_assertions)] - if parts.status.is_server_error() { - error!("Lambda Runtime server returned an unexpected error"); - return Err(parts.status.to_string().into()); - } - - let lambda_event = match deserializer::deserialize(&body, ctx) { - Ok(lambda_event) => lambda_event, - Err(err) => { - let req = build_event_error_request(request_id, err)?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); - return Ok(()); - } - }; - - let req = match handler.ready().await { - Ok(handler) => { - // Catches panics outside of a `Future` - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); - - let task = match task { - // Catches panics inside of the `Future` - Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, - Err(err) => Err(err), - }; - - match task { - Ok(response) => match response { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionStreamingRequest { - request_id, - body: response, - } - .into_req() - } - Err(err) => build_event_error_request(request_id, err), - }, - Err(err) => { - error!("{:?}", err); - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {msg}") - } else { - "Lambda panicked".to_string() - }; - EventErrorRequest::new(request_id, error_type, &msg).into_req() - } - } - } - Err(err) => build_event_error_request(request_id, err), - }?; - - client.call(req).await.expect("Unable to send response to Runtime APIs"); - Ok::<(), Error>(()) - } - .instrument(request_span) - .await?; - } - Ok(()) - } -} - -pub(crate) struct EventCompletionStreamingRequest<'a, B> { - pub(crate) request_id: &'a str, - pub(crate) body: Response, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct MetadataPrelude { - #[serde(serialize_with = "http_serde::status_code::serialize")] - status_code: StatusCode, - #[serde(serialize_with = "http_serde::header_map::serialize")] - headers: HeaderMap, - cookies: Vec, -} - -impl<'a, B> IntoRequest for EventCompletionStreamingRequest<'a, B> -where - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - fn into_req(self) -> Result, Error> { - let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); - let uri = Uri::from_str(&uri)?; - - let (parts, mut body) = self.body.into_parts(); - - let mut builder = build_request().method(Method::POST).uri(uri); - let req_headers = builder.headers_mut().unwrap(); - - req_headers.insert("Transfer-Encoding", "chunked".parse()?); - req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); - req_headers.insert( - "Content-Type", - "application/vnd.awslambda.http-integration-response".parse()?, - ); - - let mut prelude_headers = parts.headers; - // default Content-Type - prelude_headers - .entry(CONTENT_TYPE) - .or_insert("application/octet-stream".parse()?); - - let cookies = prelude_headers.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); - prelude_headers.remove(SET_COOKIE); - - let metadata_prelude = serde_json::to_string(&MetadataPrelude { - status_code: parts.status, - headers: prelude_headers, - cookies, - })?; - - trace!(?metadata_prelude); - - let (mut tx, rx) = Body::channel(); - - tokio::spawn(async move { - tx.send_data(metadata_prelude.into()).await.unwrap(); - tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); - - while let Some(chunk) = body.data().await { - let chunk = chunk.unwrap(); - tx.send_data(chunk.into()).await.unwrap(); - } - }); - - let req = builder.body(rx)?; - Ok(req) - } -} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 87d6ded5..27a4a9ae 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,11 +1,14 @@ use crate::{Config, Error}; -use http::{HeaderMap, HeaderValue}; +use bytes::Bytes; +use http::{HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, convert::TryFrom, + fmt::Debug, time::{Duration, SystemTime}, }; +use tokio_stream::Stream; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -182,6 +185,90 @@ impl LambdaEvent { } } +/// Metadata prelude for a stream response. +#[derive(Debug, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataPrelude { + #[serde(with = "http_serde::status_code")] + /// The HTTP status code. + pub status_code: StatusCode, + #[serde(with = "http_serde::header_map")] + /// The HTTP headers. + pub headers: HeaderMap, + /// The HTTP cookies. + pub cookies: Vec, +} + +pub trait ToStreamErrorTrailer { + /// Convert the hyper error into a stream error trailer. + fn to_tailer(&self) -> String; +} + +impl ToStreamErrorTrailer for Error { + fn to_tailer(&self) -> String { + format!( + "Lambda-Runtime-Function-Error-Type: Runtime.StreamError\r\nLambda-Runtime-Function-Error-Body: {}\r\n", + base64::encode(self.to_string()) + ) + } +} + +/// A streaming response that contains the metadata prelude and the stream of bytes that will be +/// sent to the client. +#[derive(Debug)] +pub struct StreamResponse { + /// The metadata prelude. + pub metadata_prelude: MetadataPrelude, + /// The stream of bytes that will be sent to the client. + pub stream: S, +} + +/// An enum representing the response of a function that can return either a buffered +/// response of type `B` or a streaming response of type `S`. +pub enum FunctionResponse { + /// A buffered response containing the entire payload of the response. This is useful + /// for responses that can be processed quickly and have a relatively small payload size(<= 6MB). + BufferedResponse(B), + /// A streaming response that delivers the payload incrementally. This is useful for + /// large payloads(> 6MB) or responses that take a long time to generate. The client can start + /// processing the response as soon as the first chunk is available, without waiting + /// for the entire payload to be generated. + StreamingResponse(StreamResponse), +} + +/// a trait that can be implemented for any type that can be converted into a FunctionResponse. +/// This allows us to use the `into` method to convert a type into a FunctionResponse. +pub trait IntoFunctionResponse { + /// Convert the type into a FunctionResponse. + fn into_response(self) -> FunctionResponse; +} + +impl IntoFunctionResponse for FunctionResponse { + fn into_response(self) -> FunctionResponse { + self + } +} + +impl IntoFunctionResponse for B +where + B: Serialize, +{ + fn into_response(self) -> FunctionResponse { + FunctionResponse::BufferedResponse(self) + } +} + +impl IntoFunctionResponse<(), S> for StreamResponse +where + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + fn into_response(self) -> FunctionResponse<(), S> { + FunctionResponse::StreamingResponse(self) + } +} + #[cfg(test)] mod test { use super::*; From 722e33f2feb0f0f24f8f276b59d4ef875ccbc0e4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Sep 2023 10:19:26 -0700 Subject: [PATCH 206/394] Use compile_error if no http features are enabled (#698) - Provide help so people know that they have to enable a feature. - Put imports in place so conditional compilation doesn't try to compile unnecesary code. Signed-off-by: David Calavera --- lambda-http/src/deserializer.rs | 23 ++++++++++------------- lambda-http/src/request.rs | 31 ++++++++++++++++++++++--------- lambda-http/src/response.rs | 7 +++++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 1771ea7b..a77f68a5 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -1,8 +1,4 @@ use crate::request::LambdaRequest; -use aws_lambda_events::{ - alb::AlbTargetGroupRequest, - apigw::{ApiGatewayProxyRequest, ApiGatewayV2httpRequest, ApiGatewayWebsocketProxyRequest}, -}; use serde::{de::Error, Deserialize}; const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; @@ -17,28 +13,29 @@ impl<'de> Deserialize<'de> for LambdaRequest { Err(err) => return Err(err), }; #[cfg(feature = "apigw_rest")] - if let Ok(res) = - ApiGatewayProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) - { + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayProxyRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { return Ok(LambdaRequest::ApiGatewayV1(res)); } #[cfg(feature = "apigw_http")] - if let Ok(res) = ApiGatewayV2httpRequest::deserialize( + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayV2httpRequest::deserialize( serde::__private::de::ContentRefDeserializer::::new(&content), ) { return Ok(LambdaRequest::ApiGatewayV2(res)); } #[cfg(feature = "alb")] if let Ok(res) = - AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + aws_lambda_events::alb::AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::< + D::Error, + >::new(&content)) { return Ok(LambdaRequest::Alb(res)); } #[cfg(feature = "apigw_websockets")] - if let Ok(res) = ApiGatewayWebsocketProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::< - D::Error, - >::new(&content)) - { + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { return Ok(LambdaRequest::WebSocket(res)); } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index bdb755ed..ad86e5a5 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -8,6 +8,12 @@ //! [`RequestExt`]: crate::RequestExt #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] use crate::ext::extensions::{PathParameters, StageVariables}; +#[cfg(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" +))] use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; @@ -26,7 +32,7 @@ use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; -use std::{env, io::Read, mem}; +use std::{env, io::Read}; use url::Url; /// Internal representation of an Lambda http event from @@ -61,6 +67,13 @@ impl LambdaRequest { LambdaRequest::Alb { .. } => RequestOrigin::Alb, #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, + #[cfg(not(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" + )))] + _ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, or `apigw_websockets` must be enabled for the `lambda-http` crate."), } } } @@ -141,8 +154,8 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request { .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method); req } @@ -255,8 +268,8 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method); req } @@ -319,8 +332,8 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET)); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET)); req } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 1a2ede5c..a51d1b2d 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -114,6 +114,13 @@ impl LambdaResponse { headers: headers.clone(), multi_value_headers: headers, }), + #[cfg(not(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" + )))] + _ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, or `apigw_websockets` must be enabled for the `lambda-http` crate."), } } } From d1687e174ec2037ae786cc6407d1aca5dbe271f5 Mon Sep 17 00:00:00 2001 From: Blake Jakopovic Date: Tue, 19 Sep 2023 19:59:18 +0200 Subject: [PATCH 207/394] Update README.md (#699) Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f6f899a..cd7bbcfc 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ $ docker run --rm \ rustserverless/lambda-rust ``` -With your application build and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: +With your application built and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: ```bash # start a docker container replicating the "provided" lambda runtime From 1cbe34b7cefbddd819013830a24a012028ea91f1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 18 Oct 2023 09:29:33 -0700 Subject: [PATCH 208/394] Fix time serialization issues (#707) - Update Chrono to fix compilation issues. - Update leap second tests. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 7 +++++-- lambda-events/src/encodings/time.rs | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index e401bfff..73ab06d8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -25,12 +25,15 @@ serde_with = { version = "^3", features = ["json"], optional = true } serde_json = "^1" serde_dynamo = { version = "^4.1", optional = true } bytes = { version = "1", features = ["serde"], optional = true } -chrono = { version = "0.4.23", default-features = false, features = [ +chrono = { version = "0.4.31", default-features = false, features = [ "clock", "serde", "std", ], optional = true } -query_map = { version = "^0.7", features = ["serde", "url-query"], optional = true } +query_map = { version = "^0.7", features = [ + "serde", + "url-query", +], optional = true } flate2 = { version = "1.0.24", optional = true } [features] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 390927ca..a550b7b0 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -279,12 +279,12 @@ mod test { let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); - // Make sure milliseconds are included. + // Make sure leap seconds are included. let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), + v: Utc.ymd(1983, 7, 22).and_hms_nano(23, 59, 59, 1_999_999_999), }; let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); + assert_eq!(encoded, String::from(r#"{"v":"427766400.999"}"#)); } #[test] From bcd3f971016dcbe4fbbe515ec1506c92feda2799 Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:30:39 -0300 Subject: [PATCH 209/394] Eventbridge Event Processor (#704) * Eventbridge Event Processor * cfg feature fix * feature comment * Removed whitespace * makefile fix --------- Co-authored-by: nich.morgan Co-authored-by: erso --- Makefile | 1 + lambda-events/Cargo.toml | 2 + lambda-events/src/event/eventbridge/mod.rs | 87 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-eventbridge-event-obj.json | 13 +++ .../fixtures/example-eventbridge-event.json | 13 +++ lambda-events/src/lib.rs | 4 + 7 files changed, 124 insertions(+) create mode 100644 lambda-events/src/event/eventbridge/mod.rs create mode 100644 lambda-events/src/fixtures/example-eventbridge-event-obj.json create mode 100644 lambda-events/src/fixtures/example-eventbridge-event.json diff --git a/Makefile b/Makefile index 544d08b7..58eb2a9c 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs cargo test --package aws_lambda_events --no-default-features --features streams + cargo test --package aws_lambda_events --no-default-features --features eventbridge fmt: cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 73ab06d8..b7a00b29 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -76,6 +76,7 @@ default = [ "sns", "sqs", "streams", + "eventbridge", ] activemq = [] @@ -117,3 +118,4 @@ ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] +eventbridge = ["chrono", "serde_with"] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs new file mode 100644 index 00000000..7809f1e2 --- /dev/null +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -0,0 +1,87 @@ +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct EventBridgeEvent { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + pub detail_type: String, + pub source: String, + #[serde(default)] + pub account: Option, + #[serde(default)] + pub time: Option>, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub resources: Option>, + #[serde(default)] + pub detail: Option, +} + +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "kebab-case")] +pub struct EventBridgeEventObj { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + pub detail_type: String, + pub source: String, + #[serde(default)] + pub account: Option, + #[serde(default)] + pub time: Option>, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub resources: Option>, + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub detail: T, +} + +#[cfg(test)] +#[cfg(feature = "eventbridge")] +mod test { + use super::*; + + use serde_json; + + #[test] + fn example_eventbridge_obj_event() { + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustomStruct { + a: String, + b: String, + } + + let data = include_bytes!("../../fixtures/example-eventbridge-event-obj.json"); + let parsed: EventBridgeEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.detail.a, "123"); + assert_eq!(parsed.detail.b, "456"); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_eventbridge_event() { + let data = include_bytes!("../../fixtures/example-eventbridge-event.json"); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(parsed.detail, Some(String::from("String Message"))); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 4ce71dfc..46dc760c 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -140,3 +140,7 @@ pub mod sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] pub mod streams; + +/// AWS Lambda event definitions for EventBridge. +#[cfg(feature = "eventbridge")] +pub mod eventbridge; diff --git a/lambda-events/src/fixtures/example-eventbridge-event-obj.json b/lambda-events/src/fixtures/example-eventbridge-event-obj.json new file mode 100644 index 00000000..97c5e0ae --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-event-obj.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "111122223333", + "time": "2017-12-22T18:43:48Z", + "region": "us-west-1", + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": "{\"a\":\"123\",\"b\":\"456\"}" +} diff --git a/lambda-events/src/fixtures/example-eventbridge-event.json b/lambda-events/src/fixtures/example-eventbridge-event.json new file mode 100644 index 00000000..793ca8dc --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-event.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "111122223333", + "time": "2017-12-22T18:43:48Z", + "region": "us-west-1", + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": "String Message" +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 7402a8f4..5fe81cfc 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -164,3 +164,7 @@ pub use event::sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] pub use event::streams; + +/// AWS Lambda event definitions for EventBridge. +#[cfg(feature = "eventbridge")] +pub use event::eventbridge; From 2675fa9e305536b14756bf7290db95f21f5cc992 Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:05:51 -0300 Subject: [PATCH 210/394] DocumentDB support (#706) * insert event draft * abstract change event * Added documentdb delete event * Added support to change event drop * Added support to dropDatabase Event * - MongoDB v6.0 Change Event Fields removed - ChangeEvent enum tagged - AnyDocument common type created * replace event support * added support to invalidate event in documentdb * Adding DocumentDB Rename event. * run cargo fmt * Excluding 'to' parameter * Add DocumentDB Update event * fixed 'to' parameter and run cargo fmt * Refactoring 'Rename' event declaration as a single type not a commum type * InsertNs renamed to DatabaseCollection for code reuse * unused field removed * cfg fix * fix lines * fmt and makefile fixed * makefile reord --------- Co-authored-by: nich.morgan Co-authored-by: erso Co-authored-by: Luca Barcelos Co-authored-by: Vinicius Brisotti Co-authored-by: Pedro Rabello Sato Co-authored-by: darwish --- Makefile | 3 +- lambda-events/Cargo.toml | 2 + .../event/documentdb/events/commom_types.rs | 44 +++++++++ .../event/documentdb/events/delete_event.rs | 20 ++++ .../documentdb/events/drop_database_event.rs | 17 ++++ .../src/event/documentdb/events/drop_event.rs | 17 ++++ .../event/documentdb/events/insert_event.rs | 21 ++++ .../documentdb/events/invalidate_event.rs | 13 +++ .../src/event/documentdb/events/mod.rs | 9 ++ .../event/documentdb/events/rename_event.rs | 21 ++++ .../event/documentdb/events/replace_event.rs | 20 ++++ .../event/documentdb/events/update_event.rs | 19 ++++ lambda-events/src/event/documentdb/mod.rs | 96 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-documentdb-delete-event.json | 30 ++++++ ...xample-documentdb-drop-database-event.json | 24 +++++ .../example-documentdb-drop-event.json | 30 ++++++ .../example-documentdb-insert-event.json | 29 ++++++ .../example-documentdb-invalidate-event.json | 20 ++++ .../example-documentdb-rename-event.json | 33 +++++++ .../example-documentdb-replace-event.json | 29 ++++++ .../example-documentdb-update-event.json | 29 ++++++ lambda-events/src/lib.rs | 4 + 23 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/event/documentdb/events/commom_types.rs create mode 100644 lambda-events/src/event/documentdb/events/delete_event.rs create mode 100644 lambda-events/src/event/documentdb/events/drop_database_event.rs create mode 100644 lambda-events/src/event/documentdb/events/drop_event.rs create mode 100644 lambda-events/src/event/documentdb/events/insert_event.rs create mode 100644 lambda-events/src/event/documentdb/events/invalidate_event.rs create mode 100644 lambda-events/src/event/documentdb/events/mod.rs create mode 100644 lambda-events/src/event/documentdb/events/rename_event.rs create mode 100644 lambda-events/src/event/documentdb/events/replace_event.rs create mode 100644 lambda-events/src/event/documentdb/events/update_event.rs create mode 100644 lambda-events/src/event/documentdb/mod.rs create mode 100644 lambda-events/src/fixtures/example-documentdb-delete-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-drop-database-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-drop-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-insert-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-invalidate-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-rename-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-replace-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-update-event.json diff --git a/Makefile b/Makefile index 58eb2a9c..76e57e94 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,10 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features cognito cargo test --package aws_lambda_events --no-default-features --features config cargo test --package aws_lambda_events --no-default-features --features connect + cargo test --package aws_lambda_events --no-default-features --features documentdb cargo test --package aws_lambda_events --no-default-features --features dynamodb cargo test --package aws_lambda_events --no-default-features --features ecr_scan + cargo test --package aws_lambda_events --no-default-features --features eventbridge cargo test --package aws_lambda_events --no-default-features --features firehose cargo test --package aws_lambda_events --no-default-features --features iam cargo test --package aws_lambda_events --no-default-features --features iot @@ -101,7 +103,6 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs cargo test --package aws_lambda_events --no-default-features --features streams - cargo test --package aws_lambda_events --no-default-features --features eventbridge fmt: cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b7a00b29..c58ec475 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -76,6 +76,7 @@ default = [ "sns", "sqs", "streams", + "documentdb", "eventbridge", ] @@ -118,4 +119,5 @@ ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] +documentdb = [] eventbridge = ["chrono", "serde_with"] diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs new file mode 100644 index 00000000..5d1bdc19 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +pub type AnyDocument = HashMap; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DatabaseCollection { + db: String, + #[serde(default)] + coll: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentId { + #[serde(rename = "_data")] + pub data: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentKeyIdOid { + #[serde(rename = "$oid")] + pub oid: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentKeyId { + #[serde(rename = "_id")] + pub id: DocumentKeyIdOid, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct InnerTimestamp { + t: usize, + i: usize, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Timestamp { + #[serde(rename = "$timestamp")] + pub timestamp: InnerTimestamp, +} diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs new file mode 100644 index 00000000..7761d62f --- /dev/null +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDeleteEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs new file mode 100644 index 00000000..c51e345c --- /dev/null +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDropDatabaseEvent { + #[serde(rename = "_id")] + id: DocumentId, + cluster_time: Timestamp, + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs new file mode 100644 index 00000000..866ce143 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -0,0 +1,17 @@ +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDropEvent { + #[serde(rename = "_id")] + id: DocumentId, + cluster_time: Timestamp, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs new file mode 100644 index 00000000..09ab66b2 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] + +pub struct ChangeInsertEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + //operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs new file mode 100644 index 00000000..47469ff9 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeInvalidateEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + // operation_type: String, +} diff --git a/lambda-events/src/event/documentdb/events/mod.rs b/lambda-events/src/event/documentdb/events/mod.rs new file mode 100644 index 00000000..c1c41b98 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/mod.rs @@ -0,0 +1,9 @@ +pub mod commom_types; +pub mod delete_event; +pub mod drop_database_event; +pub mod drop_event; +pub mod insert_event; +pub mod invalidate_event; +pub mod rename_event; +pub mod replace_event; +pub mod update_event; diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs new file mode 100644 index 00000000..8bc250fb --- /dev/null +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeRenameEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + //operation_type: String, + #[serde(default)] + txn_number: Option, + to: DatabaseCollection, +} diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs new file mode 100644 index 00000000..4a0e58ad --- /dev/null +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeReplaceEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs new file mode 100644 index 00000000..8698485a --- /dev/null +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeUpdateEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs new file mode 100644 index 00000000..67f7c9ad --- /dev/null +++ b/lambda-events/src/event/documentdb/mod.rs @@ -0,0 +1,96 @@ +pub mod events; + +use self::events::{ + delete_event::ChangeDeleteEvent, drop_database_event::ChangeDropDatabaseEvent, drop_event::ChangeDropEvent, + insert_event::ChangeInsertEvent, invalidate_event::ChangeInvalidateEvent, rename_event::ChangeRenameEvent, + replace_event::ChangeReplaceEvent, update_event::ChangeUpdateEvent, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(tag = "operationType", rename_all = "camelCase")] +pub enum ChangeEvent { + Insert(ChangeInsertEvent), + Delete(ChangeDeleteEvent), + Drop(ChangeDropEvent), + DropDatabase(ChangeDropDatabaseEvent), + Invalidate(ChangeInvalidateEvent), + Replace(ChangeReplaceEvent), + Update(ChangeUpdateEvent), + Rename(ChangeRenameEvent), +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentDbInnerEvent { + pub event: ChangeEvent, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DocumentDbEvent { + #[serde(default)] + pub event_source_arn: Option, + pub events: Vec, + #[serde(default)] + pub event_source: Option, +} + +#[cfg(test)] +#[cfg(feature = "documentdb")] +mod test { + use super::*; + + pub type Event = DocumentDbEvent; + + fn test_example(data: &[u8]) { + let parsed: Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: Event = serde_json::from_slice(output.as_bytes()).unwrap(); + + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_documentdb_insert_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-insert-event.json")); + } + + #[test] + fn example_documentdb_delete_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-delete-event.json")); + } + + #[test] + fn example_documentdb_drop_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-drop-event.json")); + } + + #[test] + fn example_documentdb_replace_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-replace-event.json")); + } + + #[test] + fn example_documentdb_update_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-update-event.json")); + } + + #[test] + fn example_documentdb_rename_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-rename-event.json")); + } + + #[test] + fn example_documentdb_invalidate_event() { + test_example(include_bytes!( + "../../fixtures/example-documentdb-invalidate-event.json" + )); + } + + #[test] + fn example_documentdb_drop_database_event() { + test_example(include_bytes!( + "../../fixtures/example-documentdb-drop-database-event.json" + )); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 46dc760c..5ee57911 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -141,6 +141,10 @@ pub mod sqs; #[cfg(feature = "streams")] pub mod streams; +// AWS Lambda event definitions for DocumentDB +#[cfg(feature = "documentdb")] +pub mod documentdb; + /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] pub mod eventbridge; diff --git a/lambda-events/src/fixtures/example-documentdb-delete-event.json b/lambda-events/src/fixtures/example-documentdb-delete-event.json new file mode 100644 index 00000000..fd9259da --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-delete-event.json @@ -0,0 +1,30 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "delete" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-drop-database-event.json b/lambda-events/src/fixtures/example-documentdb-drop-database-event.json new file mode 100644 index 00000000..77a1cb93 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-drop-database-event.json @@ -0,0 +1,24 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "test_database" + }, + "operationType": "dropDatabase" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-drop-event.json b/lambda-events/src/fixtures/example-documentdb-drop-event.json new file mode 100644 index 00000000..89d8cc8f --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-drop-event.json @@ -0,0 +1,30 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "drop" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-insert-event.json b/lambda-events/src/fixtures/example-documentdb-insert-event.json new file mode 100644 index 00000000..cd03e374 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-insert-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "insert" + } + } + ], + "eventSource": "aws:docdb" +} diff --git a/lambda-events/src/fixtures/example-documentdb-invalidate-event.json b/lambda-events/src/fixtures/example-documentdb-invalidate-event.json new file mode 100644 index 00000000..59f5af65 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-invalidate-event.json @@ -0,0 +1,20 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "operationType": "invalidate" + } + } + ], + "eventSource": "aws:docdb" + } \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-rename-event.json b/lambda-events/src/fixtures/example-documentdb-rename-event.json new file mode 100644 index 00000000..65416470 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-rename-event.json @@ -0,0 +1,33 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "to": { + "db": "test_database_new", + "coll": "test_collection_new" + }, + "operationType": "rename" + } + } + ], + "eventSource": "aws:docdb" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-replace-event.json b/lambda-events/src/fixtures/example-documentdb-replace-event.json new file mode 100644 index 00000000..1c7fe559 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-replace-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "operationType": "replace", + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "engineering", + "coll": "users" + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + } + } + } + ], + "eventSource": "aws:docdb" +} diff --git a/lambda-events/src/fixtures/example-documentdb-update-event.json b/lambda-events/src/fixtures/example-documentdb-update-event.json new file mode 100644 index 00000000..dbb19159 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-update-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "update" + } + } + ], + "eventSource": "aws:docdb" + } \ No newline at end of file diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 5fe81cfc..aa0d5495 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -165,6 +165,10 @@ pub use event::sqs; #[cfg(feature = "streams")] pub use event::streams; +/// AWS Lambda event definitions for documentdb. +#[cfg(feature = "documentdb")] +pub use event::documentdb; + /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] pub use event::eventbridge; From b9d64e893122262e3abf0b2126b9d8dacfb265e6 Mon Sep 17 00:00:00 2001 From: Seb Maz Date: Mon, 23 Oct 2023 17:46:28 +0400 Subject: [PATCH 211/394] Updated all crates (#709) removed the deprecated mod "Attribute_value" added serde_dynamo "to_attribute_value" updated the code to match the new changes #708 --- examples/http-dynamodb/Cargo.toml | 17 +++++++++-------- examples/http-dynamodb/src/main.rs | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index c3f6d8be..be95f867 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -11,15 +11,16 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -simple-error = "0.2.3" -serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } +simple-error = "0.3.0" +serde_json = "1.0.107" +serde = { version = "1.0.189", features = ["derive"] } +serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } -aws-sdk-dynamodb = "0.21.0" -aws-config = "0.51.0" -tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +aws-sdk-dynamodb = "0.33.0" +aws-config = "0.56.1" +tokio = { version = "1.33.0", features = ["macros"] } +tracing = { version = "0.1.40", features = ["log"] } +tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 5a7030f9..b2e8af20 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,9 +1,10 @@ -use aws_sdk_dynamodb::model::AttributeValue; -use aws_sdk_dynamodb::{Client, Error as OtherError}; +use aws_sdk_dynamodb::{Client}; use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use serde::{Deserialize, Serialize}; +use serde_dynamo::to_attribute_value; use tracing::info; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { pub p_type: String, pub age: String, @@ -76,12 +77,12 @@ async fn main() -> Result<(), Error> { // Add an item to a table. // snippet-start:[dynamodb.rust.add-item] -pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), OtherError> { - let user_av = AttributeValue::S(item.username); - let type_av = AttributeValue::S(item.p_type); - let age_av = AttributeValue::S(item.age); - let first_av = AttributeValue::S(item.first); - let last_av = AttributeValue::S(item.last); +pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Error> { + let user_av = to_attribute_value(item.username)?; + let type_av = to_attribute_value(item.p_type)?; + let age_av = to_attribute_value(item.age)?; + let first_av = to_attribute_value(item.first)?; + let last_av = to_attribute_value(item.last)?; let request = client .put_item() From 45525e0dfe1196315dd130101b9cec64ac6b67f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Greinhofer?= Date: Mon, 23 Oct 2023 14:52:41 -0500 Subject: [PATCH 212/394] Add SQS API event structs (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds strucs to allow serializing data coming from the AWS SQS API. Fixes awslabs/aws-lambda-rust-runtime#710 Signed-off-by: Rémy Greinhofer --- lambda-events/src/event/sqs/mod.rs | 90 +++++++++++++++++++ .../fixtures/example-sqs-api-event-obj.json | 10 +++ 2 files changed, 100 insertions(+) create mode 100644 lambda-events/src/fixtures/example-sqs-api-event-obj.json diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index af4d3f21..5c10a428 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -112,6 +112,74 @@ pub struct BatchItemFailure { pub item_identifier: String, } +/// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SqsApiEventObj { + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub messages: Vec>, +} + +/// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsApiEvent { + pub messages: Vec, +} + +/// Alternative to SqsApiEvent to be used alongside SqsApiMessageObj when you need to +/// deserialize a nested object into a struct of type T within the SQS Message rather +/// than just using the raw SQS Message string +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "PascalCase")] +pub struct SqsApiMessageObj { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + /// Deserialized into a `T` from nested JSON inside the SQS body string. `T` must implement the `Deserialize` or `DeserializeOwned` trait. + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub body: T, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// An individual SQS API Message, its metadata, and Message Attributes +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SqsApiMessage { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + #[serde(default)] + pub body: Option, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + #[cfg(test)] mod test { use super::*; @@ -159,4 +227,26 @@ mod test { let reparsed: SqsBatchResponse = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_api_obj_event() { + // Example sqs api receive message response, fetched 2023-10-23, inspired from: + // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html#API_ReceiveMessage_ResponseSyntax + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustStruct { + city: String, + country: String, + } + + let data = include_bytes!("../../fixtures/example-sqs-api-event-obj.json"); + let parsed: SqsApiEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.messages[0].body.city, "provincetown"); + assert_eq!(parsed.messages[0].body.country, "usa"); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsApiEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-sqs-api-event-obj.json b/lambda-events/src/fixtures/example-sqs-api-event-obj.json new file mode 100644 index 00000000..39ab67cf --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-api-event-obj.json @@ -0,0 +1,10 @@ +{ + "Messages": [ + { + "Body": "{\"country\": \"usa\", \"city\": \"provincetown\"}", + "Md5OfBody": "2b3e4f40b57e80d67ac5b9660c56d787", + "MessageId": "f663a189-97e2-41f5-9c0e-cfb595d8322c", + "ReceiptHandle": "AQEBdObBZIl7FWJiK9c3KmqKNvusy6+eqG51SLIp5Gs6lQ6+e4SI0lJ6Glw+qcOi+2RRrnfOjlsF8uDlo13TgubmtgP+CH7s+YKDdpbg2jA931vLi6qnU0ZFXcf/H8BDZ4kcz29npMu9/N2DT9F+kI9Q9pTfLsISg/7XFMvRTqAtjSfa2wI5TVcOPZBdkGqTLUoKqAYni0L7NTLzFUTjCN/HiOcvG+16zahhsTniM1MwOTSpbOO2uTZmY25V/PCfNdF1PBXtdNA9mWW2Ym6THV28ug3cuK6dXbFQBuxIGVhOq+mRVU6gKN/eZpZediiBt75oHD6ASu8jIUpJGeUWEZm6qSWU+YTivr6QoqGLwAVvI3CXOIZQ/+Wp/RJAxMQxtRIe/MOsOITcmGlFqhWnjlGQdg==" + } + ] +} From c215812f284e8d53f915518d959af493dce3c490 Mon Sep 17 00:00:00 2001 From: Martin Bartlett Date: Fri, 27 Oct 2023 18:37:38 +0200 Subject: [PATCH 213/394] Remove cfg(test) on with_stage_variables (#713) None of the other with_ methods are constrained to test-only, although this method did have a specific comment indicating test-only, so there may have been a reason. Anyway, this commit removes that constraint. Co-authored-by: Martin Bartlett --- lambda-http/src/ext/extensions.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index e002d0ea..313090c6 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -108,10 +108,9 @@ pub trait RequestExt { /// These will always be `None` for ALB triggered requests. fn stage_variables_ref(&self) -> Option<&QueryMap>; - /// Configures instance with stage variables under `#[cfg(test)]` configurations + /// Configures instance with stage variables /// /// This is intended for use in mock testing contexts. - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into; @@ -216,7 +215,6 @@ impl RequestExt for http::Extensions { .and_then(|StageVariables(vars)| if vars.is_empty() { None } else { Some(vars) }) } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, @@ -318,7 +316,6 @@ impl RequestExt for Parts { self.extensions.stage_variables_ref() } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, @@ -420,7 +417,6 @@ impl RequestExt for http::Request { self.extensions().stage_variables_ref() } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, From 3e195f6b0c734ae694cb1375fed57cf1b6dd01fd Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 30 Oct 2023 22:58:22 +0800 Subject: [PATCH 214/394] Fixed media type suffix detection (#714) --- lambda-http/src/response.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index a51d1b2d..e77ec181 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -282,7 +282,9 @@ where } for suffix in TEXT_ENCODING_SUFFIXES { - if content_type.ends_with(suffix) { + let mut parts = content_type.trim().split(';'); + let mime_type = parts.next().unwrap_or_default(); + if mime_type.ends_with(suffix) { return convert_to_text(self, content_type); } } @@ -484,6 +486,24 @@ mod tests { ) } + #[tokio::test] + async fn charset_content_type_header_suffix() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_TYPE, "application/graphql-response+json; charset=utf-16") + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + ) + } + #[tokio::test] async fn content_headers_unset() { // Drive the implementation by using `hyper::Body` instead of From c565173e36891da95218482f10301394dc6b2a97 Mon Sep 17 00:00:00 2001 From: Kikuo Emoto Date: Sun, 5 Nov 2023 03:29:16 +0900 Subject: [PATCH 215/394] Fix: Missing userNotFound field in "create/verify auth challenge" Cognito user pool events (#719) * Add user_not_found to Create/Verify auth challenge events - Adds `user_not_found` field to: - `CognitoEventUserPoolsCreateAuthChallengeRequest` - `CognitoEventUserPoolsVerifyAuthChallengeRequest` - Adds test cases where `user_not_found` becomes `true` for: - `CognitoEventUserPoolsDefineAuthChallengeRequest` - `CognitoEventUserPoolsCreateAuthChallengeRequest` - `CognitoEventUserPoolsVerifyAuthChallengeRequest` issue awslabs/aws-lambda-rust-runtime#718 * Fix coding style with cargo fmt --- lambda-events/src/event/cognito/mod.rs | 46 +++++++++++++++++++ ...-create-auth-challenge-user-not-found.json | 41 +++++++++++++++++ ...event-userpools-create-auth-challenge.json | 3 +- ...-define-auth-challenge-user-not-found.json | 36 +++++++++++++++ ...uth-challenge-optional-answer-correct.json | 3 +- ...-verify-auth-challenge-user-not-found.json | 31 +++++++++++++ ...event-userpools-verify-auth-challenge.json | 3 +- 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 49f2eebd..c07c40a4 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -343,6 +343,8 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters @@ -389,6 +391,8 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters @@ -482,6 +486,20 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_create_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_custommessage() { @@ -518,6 +536,20 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_define_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_migrateuser() { @@ -612,4 +644,18 @@ mod test { let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json new file mode 100644 index 00000000..40ce2a2b --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json @@ -0,0 +1,41 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "CreateAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "challengeName": "CUSTOM_CHALLENGE", + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "publicChallengeParameters": { + "a": "b" + }, + "privateChallengeParameters": { + "c": "d" + }, + "challengeMetadata": "challengeMetadata" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json index 99acf0a2..2d0f2a83 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json @@ -26,7 +26,8 @@ ], "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { "publicChallengeParameters": { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json new file mode 100644 index 00000000..1ad40e2a --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json @@ -0,0 +1,36 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "DefineAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "challengeName": "challengeName", + "issueTokens": true, + "failAuthentication": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json index 70a973f4..f6f7ca09 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json @@ -22,7 +22,8 @@ "challengeAnswer": "123xxxx", "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json new file mode 100644 index 00000000..a5068eaa --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json @@ -0,0 +1,31 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "answerCorrect": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json index b1d88fee..6bff9974 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json @@ -22,7 +22,8 @@ "challengeAnswer": "123xxxx", "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { "answerCorrect": true From 2a82ba7b842b9ffbda14fbda4bfbf3aa4a27a157 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 7 Nov 2023 16:44:07 -0500 Subject: [PATCH 216/394] Add an advanced SQS multiple functions with shared data example (#720) * add multi functions example * update Readme * pr comment * pr comments * run clippy --- .../Cargo.toml | 13 ++++ .../README.md | 28 +++++++++ .../consumer/Cargo.toml | 22 +++++++ .../consumer/src/main.rs | 24 ++++++++ .../pizza_lib/Cargo.toml | 7 +++ .../pizza_lib/src/lib.rs | 7 +++ .../producer/Cargo.toml | 25 ++++++++ .../producer/src/main.rs | 61 +++++++++++++++++++ 8 files changed, 187 insertions(+) create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/README.md create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs diff --git a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml new file mode 100644 index 00000000..116ab8ef --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] + +members = [ + "producer", + "consumer", + "pizza_lib", +] + +[profile.release] +opt-level = 'z' +lto = true +codegen-units = 1 +panic = 'abort' \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/README.md b/examples/advanced-sqs-multiple-functions-shared-data/README.md new file mode 100644 index 00000000..83136b9b --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/README.md @@ -0,0 +1,28 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +4. Make sure to edit the QUEUE_URL env variable in producer/Cargo.toml +3. Deploy boths functions to AWS Lambda with + +`cargo lambda deploy consumer --iam-role YOUR_ROLE` + +`cargo lambda deploy producer --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` + +## Add the SQS trigger to the consumer function + +You can use aws-cli to create an event source mapping: + +```bash +aws lambda create-event-source-mapping \ +--function-name consumer \ +--region \ +--event-source-arn \ +--batch-size 1 +``` \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml new file mode 100644 index 00000000..8555a073 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "consumer" +version = "0.1.0" +edition = "2021" + + +[dependencies] +#tracing +tracing = "0.1.40" +tracing-subscriber = "0.3.17" + +#aws dependencies +aws-sdk-config = "0.35.0" +aws-sdk-sqs = "0.35.0" +aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } + +#lambda runtime +lambda_runtime = "0.8.1" +tokio = { version = "1", features = ["macros"] } + +#shared lib +pizza_lib = { path = "../pizza_lib" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs new file mode 100644 index 00000000..42290192 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs @@ -0,0 +1,24 @@ +use aws_lambda_events::event::sqs::SqsEventObj; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use pizza_lib::Pizza; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .with_ansi(false) + .without_time() + .init(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(event: LambdaEvent>) -> Result<(), Error> { + for record in event.payload.records.iter() { + let pizza = &record.body; + println!("Pizza name: {} with toppings: {:?}", pizza.name, pizza.toppings); + } + Ok(()) +} diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml new file mode 100644 index 00000000..76631bbd --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pizza_lib" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.191", features = ["derive"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs new file mode 100644 index 00000000..638fa762 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Pizza { + pub name: String, + pub toppings: Vec, +} diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml new file mode 100644 index 00000000..557ac6e5 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "producer" +version = "0.1.0" +edition = "2021" + +[package.metadata.lambda.deploy] +env = { "QUEUE_URL" = "https://changeMe" } + +[dependencies] +#tracing +tracing = "0.1.40" +tracing-subscriber = "0.3.17" + +#aws dependencies +aws-config = "0.57.1" +aws-sdk-config = "0.35.0" +aws-sdk-sqs = "0.35.0" + +#lambda runtime +lambda_runtime = "0.8.1" +serde_json = "1.0.108" +tokio = { version = "1", features = ["macros"] } + +#shared lib +pizza_lib = { path = "../pizza_lib" } \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs new file mode 100644 index 00000000..2cc2541b --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -0,0 +1,61 @@ +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use pizza_lib::Pizza; +use serde_json::{json, Value}; + +struct SQSManager { + client: aws_sdk_sqs::Client, + queue_url: String, +} + +impl SQSManager { + fn new(client: aws_sdk_sqs::Client, queue_url: String) -> Self { + Self { client, queue_url } + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .with_ansi(false) + .without_time() + .init(); + + // read the queue url from the environment + let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); + // build the config from environment variables (fed by AWS Lambda) + let config = aws_config::from_env().load().await; + // create our SQS Manager + let sqs_manager = SQSManager::new(aws_sdk_sqs::Client::new(&config), queue_url); + let sqs_manager_ref = &sqs_manager; + + // no need to create a SQS Client for each incoming request, let's use a shared state + let handler_func_closure = |event: LambdaEvent| async move { + process_event(event, sqs_manager_ref).await + }; + lambda_runtime::run(service_fn(handler_func_closure)).await?; + Ok(()) +} + +async fn process_event(_: LambdaEvent, sqs_manager: &SQSManager) -> Result<(), Error> { + // let's create our pizza + let message = Pizza { + name: "margherita".to_string(), + toppings: vec![ + "San Marzano Tomatoes".to_string(), + "Fresh Mozzarella".to_string(), + "Basil".to_string(), + ], + }; + // send our message to SQS + sqs_manager + .client + .send_message() + .queue_url(&sqs_manager.queue_url) + .message_body(json!(message).to_string()) + .send() + .await?; + + Ok(()) +} From 39f770ba357c6139d3be7b1eceaf426be5f5b547 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 Nov 2023 18:19:11 -0800 Subject: [PATCH 217/394] New runtime, http, and events release. (#721) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index c58ec475..bb7f115e 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.11.1" +version = "0.12.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ea4a5fba..c8caec8d 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.1" +version = "0.8.2" authors = [ "David Calavera ", "Harold Sun ", @@ -41,7 +41,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.11.0" +version = "0.12.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9202b1c1..d16eaedd 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.2" +version = "0.8.3" authors = [ "David Calavera ", "Harold Sun ", From 8e6d77c5cfbbcacbc751758a8d2f1074538797ac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 8 Nov 2023 17:45:54 -0800 Subject: [PATCH 218/394] Fix transitive dependency version of lambda_runtime. (#723) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index c8caec8d..fc93d88f 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.2" +version = "0.8.3" authors = [ "David Calavera ", "Harold Sun ", @@ -29,7 +29,7 @@ futures = "0.3" http = "0.2" http-body = "0.4" hyper = "0.14" -lambda_runtime = { path = "../lambda-runtime", version = "0.8" } +lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" From f102711500e55c0a0bc9e0944f3043604c17db0a Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 10 Nov 2023 08:45:20 -0800 Subject: [PATCH 219/394] Use base64 0.21. (#724) * Use base64 0.21. * Use a non-deprecated base64 encoding function. --- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/types.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d16eaedd..9fb8eb8b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -43,5 +43,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" http-serde = "1.1.3" -base64 = "0.20.0" +base64 = "0.21.0" http-body = "0.4" diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 27a4a9ae..2f0287ee 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,4 +1,5 @@ use crate::{Config, Error}; +use base64::prelude::*; use bytes::Bytes; use http::{HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; @@ -208,7 +209,7 @@ impl ToStreamErrorTrailer for Error { fn to_tailer(&self) -> String { format!( "Lambda-Runtime-Function-Error-Type: Runtime.StreamError\r\nLambda-Runtime-Function-Error-Body: {}\r\n", - base64::encode(self.to_string()) + BASE64_STANDARD.encode(self.to_string()) ) } } From 8cdedc09b89b17c3e505effe83cc74a11ee31347 Mon Sep 17 00:00:00 2001 From: amir-haroun <108406706+amir-haroun@users.noreply.github.com> Date: Fri, 10 Nov 2023 19:08:48 +0100 Subject: [PATCH 220/394] added Default implementation for S3EventRecord (#726) --- lambda-events/src/event/s3/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 13d514ad..128a5811 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -13,7 +13,7 @@ pub struct S3Event { } /// `S3EventRecord` which wrap record data -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3EventRecord { #[serde(default)] From 636df70ab5385b7ed741cfeb0a4e4e9d8486ea2e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Sat, 11 Nov 2023 11:29:45 -0500 Subject: [PATCH 221/394] add provided.al2023 integration tests (#727) --- README.md | 4 +- lambda-integration-tests/template.yaml | 86 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd7bbcfc..c586e657 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip $ aws lambda create-function --function-name rustTest \ --handler bootstrap \ --zip-file fileb://./target/lambda/basic/bootstrap.zip \ - --runtime provided.al2 \ # Change this to provided.al if you would like to use Amazon Linux 1. + --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 (or to provided.al for Amazon Linux 1). --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ --tracing-config Mode=Active @@ -202,7 +202,7 @@ Resources: MemorySize: 128 Architectures: ["arm64"] Handler: bootstrap - Runtime: provided.al2 + Runtime: provided.al2023 Timeout: 5 CodeUri: target/lambda/basic/ diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index d3716d7c..d148c264 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -8,6 +8,17 @@ Globals: Timeout: 5 Resources: + # Rust function using runtime_fn running on AL2023 + RuntimeFnAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-fn/ + Runtime: provided.al2023 + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using runtime_fn running on AL2 RuntimeFnAl2: Type: AWS::Serverless::Function @@ -30,6 +41,17 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using a Service implementation running on AL2023 + RuntimeTraitAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-trait/ + Runtime: provided.al2023 + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using a Service implementation running on AL2 RuntimeTraitAl2: Type: AWS::Serverless::Function @@ -52,6 +74,38 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using lambda_http::service_fn running on AL2023 + HttpFnAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-fn/ + Runtime: provided.al2023 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2/post + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using lambda_http::service_fn running on AL2 HttpFnAl2: Type: AWS::Serverless::Function @@ -84,6 +138,38 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using lambda_http with Service running on AL2023 + HttpTraitAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-trait/ + Runtime: provided.al2023 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2-trait/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2-trait/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2-trait/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2-trait/post + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using lambda_http with Service running on AL2 HttpTraitAl2: Type: AWS::Serverless::Function From 25acb4a3fd176698f8c249857d5b6bdb23afd472 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 Nov 2023 16:56:26 -0800 Subject: [PATCH 222/394] Release lambda-events 0.12.1 (#728) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index bb7f115e..ca5fa1a8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.12.0" +version = "0.12.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", From 32207eaa3b4e946144e7e7ee0f654fb88f12ed7e Mon Sep 17 00:00:00 2001 From: Siarhei Kazhura <90936653+kazhura-aws@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:37:35 -0800 Subject: [PATCH 223/394] Added LogBuffering validation. (#729) * Added LogBuffering validation. The validation will provide a meaningful error in case the LogBuffering is not configured according to https://docs.aws.amazon.com/lambda/latest/dg/telemetry-api.html\#telemetry-api-buffering * Amended the code based on the PR review: https://github.com/awslabs/aws-lambda-rust-runtime/pull/729 * fixing formatting and linting issues --- lambda-extension/src/extension.rs | 6 ++ lambda-extension/src/logs.rs | 113 +++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 52fe5c31..4747b041 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -225,6 +225,9 @@ where if let Some(mut log_processor) = self.logs_processor { trace!("Log processor found"); + + validate_buffering_configuration(self.log_buffering)?; + // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.log_port_number)); let make_service = service_fn(move |_socket: &AddrStream| { @@ -261,6 +264,9 @@ where if let Some(mut telemetry_processor) = self.telemetry_processor { trace!("Telemetry processor found"); + + validate_buffering_configuration(self.telemetry_buffering)?; + // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number)); let make_service = service_fn(move |_socket: &AddrStream| { diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index cf894100..20a095c4 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -5,6 +5,8 @@ use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; +use crate::{Error, ExtensionError}; + /// Payload received from the Lambda Logs API /// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-msg #[derive(Clone, Debug, Deserialize, PartialEq)] @@ -110,7 +112,7 @@ pub struct LogPlatformReportMetrics { /// Log buffering configuration. /// Allows Lambda to buffer logs before deliverying them to a subscriber. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Copy, Clone)] #[serde(rename_all = "camelCase")] pub struct LogBuffering { /// The maximum time (in milliseconds) to buffer a batch. @@ -124,6 +126,40 @@ pub struct LogBuffering { pub max_items: usize, } +static LOG_BUFFERING_MIN_TIMEOUT_MS: usize = 25; +static LOG_BUFFERING_MAX_TIMEOUT_MS: usize = 30_000; +static LOG_BUFFERING_MIN_BYTES: usize = 262_144; +static LOG_BUFFERING_MAX_BYTES: usize = 1_048_576; +static LOG_BUFFERING_MIN_ITEMS: usize = 1_000; +static LOG_BUFFERING_MAX_ITEMS: usize = 10_000; + +impl LogBuffering { + fn validate(&self) -> Result<(), Error> { + if self.timeout_ms < LOG_BUFFERING_MIN_TIMEOUT_MS || self.timeout_ms > LOG_BUFFERING_MAX_TIMEOUT_MS { + let error = format!( + "LogBuffering validation error: Invalid timeout_ms: {}. Allowed values: Minumun: {}. Maximum: {}", + self.timeout_ms, LOG_BUFFERING_MIN_TIMEOUT_MS, LOG_BUFFERING_MAX_TIMEOUT_MS + ); + return Err(ExtensionError::boxed(error)); + } + if self.max_bytes < LOG_BUFFERING_MIN_BYTES || self.max_bytes > LOG_BUFFERING_MAX_BYTES { + let error = format!( + "LogBuffering validation error: Invalid max_bytes: {}. Allowed values: Minumun: {}. Maximum: {}", + self.max_bytes, LOG_BUFFERING_MIN_BYTES, LOG_BUFFERING_MAX_BYTES + ); + return Err(ExtensionError::boxed(error)); + } + if self.max_items < LOG_BUFFERING_MIN_ITEMS || self.max_items > LOG_BUFFERING_MAX_ITEMS { + let error = format!( + "LogBuffering validation error: Invalid max_items: {}. Allowed values: Minumun: {}. Maximum: {}", + self.max_items, LOG_BUFFERING_MIN_ITEMS, LOG_BUFFERING_MAX_ITEMS + ); + return Err(ExtensionError::boxed(error)); + } + Ok(()) + } +} + impl Default for LogBuffering { fn default() -> Self { LogBuffering { @@ -134,6 +170,18 @@ impl Default for LogBuffering { } } +/// Validate the `LogBuffering` configuration (if present) +/// +/// # Errors +/// +/// This function will return an error if `LogBuffering` is present and configured incorrectly +pub(crate) fn validate_buffering_configuration(log_buffering: Option) -> Result<(), Error> { + match log_buffering { + Some(log_buffering) => log_buffering.validate(), + None => Ok(()), + } +} + /// Wrapper function that sends logs to the subscriber Service /// /// This takes an `hyper::Request` and transforms it into `Vec` for the @@ -303,4 +351,67 @@ mod tests { }, ), } + + macro_rules! log_buffering_configuration_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let result = validate_buffering_configuration(input); + + if let Some(expected) = expected { + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), expected.to_string()); + } else { + assert!(result.is_ok()); + } + + } + )* + } + } + + log_buffering_configuration_tests! { + log_buffer_configuration_none_success: ( + None, + None:: + ), + log_buffer_configuration_default_success: ( + Some(LogBuffering::default()), + None:: + ), + log_buffer_configuration_min_success: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MIN_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MIN_BYTES, max_items: LOG_BUFFERING_MIN_ITEMS }), + None:: + ), + log_buffer_configuration_max_success: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + None:: + ), + min_timeout_ms_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MIN_TIMEOUT_MS-1, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid timeout_ms: 24. Allowed values: Minumun: 25. Maximum: 30000")) + ), + max_timeout_ms_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS+1, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid timeout_ms: 30001. Allowed values: Minumun: 25. Maximum: 30000")) + ), + min_max_bytes_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MIN_BYTES-1, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_bytes: 262143. Allowed values: Minumun: 262144. Maximum: 1048576")) + ), + max_max_bytes_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES+1, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_bytes: 1048577. Allowed values: Minumun: 262144. Maximum: 1048576")) + ), + min_max_items_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MIN_ITEMS-1 }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_items: 999. Allowed values: Minumun: 1000. Maximum: 10000")) + ), + max_max_items_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS+1 }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_items: 10001. Allowed values: Minumun: 1000. Maximum: 10000")) + ), + } } From fed79f4497e4723e63196a5fd2c7cd62ff424817 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 14 Nov 2023 19:27:00 -0800 Subject: [PATCH 224/394] Release lambda_extension 0.8.2 (#730) Fixes incompatibility issue calling the logging api on AL2023. Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index fcae3cc7..5d9d54b4 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.8.1" +version = "0.8.2" edition = "2021" authors = [ "David Calavera ", From d3e365cfe94e51b7b3238b16575d563a5f6f0c10 Mon Sep 17 00:00:00 2001 From: Titus <8200809+nismotie@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:20:33 +0000 Subject: [PATCH 225/394] Fix typo in LogBuffering struct documentation (#731) Co-authored-by: Titus Bridgwood --- lambda-extension/src/logs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 20a095c4..c453c951 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -111,7 +111,7 @@ pub struct LogPlatformReportMetrics { } /// Log buffering configuration. -/// Allows Lambda to buffer logs before deliverying them to a subscriber. +/// Allows Lambda to buffer logs before delivering them to a subscriber. #[derive(Debug, Serialize, Copy, Clone)] #[serde(rename_all = "camelCase")] pub struct LogBuffering { From 53637e79ef499088007b74e702fb864748622bce Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 20 Nov 2023 10:42:07 -0800 Subject: [PATCH 226/394] Remove function config allocations per invocation. (#732) Every invocation clones the function config. This allocates memory in the heap for no reason. This change removes those extra allocations by wrapping the config into an Arc and sharing that between invocations. Signed-off-by: David Calavera --- Cargo.toml | 1 + lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 28 ++++++++----- lambda-runtime/src/types.rs | 81 ++++++++++++++++++++++--------------- 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48bcd5db..16f57a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "lambda-http", "lambda-integration-tests", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9fb8eb8b..335b5482 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -32,7 +32,7 @@ hyper = { version = "0.14.20", features = [ "server", ] } futures = "0.3" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" bytes = "1.0" http = "0.2" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 18b1066e..5404fb96 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -23,6 +23,7 @@ use std::{ future::Future, marker::PhantomData, panic, + sync::Arc, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; @@ -58,6 +59,8 @@ pub struct Config { pub log_group: String, } +type RefConfig = Arc; + impl Config { /// Attempts to read configuration from environment variables. pub fn from_env() -> Result { @@ -86,7 +89,7 @@ where struct Runtime = HttpConnector> { client: Client, - config: Config, + config: RefConfig, } impl Runtime @@ -127,8 +130,7 @@ where continue; } - let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(&self.config); + let ctx: Context = Context::try_from((self.config.clone(), parts.headers))?; let request_id = &ctx.request_id.clone(); let request_span = match &ctx.xray_trace_id { @@ -263,7 +265,10 @@ where trace!("Loading config from env"); let config = Config::from_env()?; let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client, config }; + let runtime = Runtime { + client, + config: Arc::new(config), + }; let client = &runtime.client; let incoming = incoming(client); @@ -294,7 +299,7 @@ mod endpoint_tests { }, simulated, types::Diagnostic, - Error, Runtime, + Config, Error, Runtime, }; use futures::future::BoxFuture; use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; @@ -302,7 +307,7 @@ mod endpoint_tests { use lambda_runtime_api_client::Client; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, marker::PhantomData}; + use std::{convert::TryFrom, env, marker::PhantomData, sync::Arc}; use tokio::{ io::{self, AsyncRead, AsyncWrite}, select, @@ -531,9 +536,12 @@ mod endpoint_tests { if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); } - let config = crate::Config::from_env().expect("Failed to read env vars"); + let config = Config::from_env().expect("Failed to read env vars"); - let runtime = Runtime { client, config }; + let runtime = Runtime { + client, + config: Arc::new(config), + }; let client = &runtime.client; let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; @@ -568,13 +576,13 @@ mod endpoint_tests { let f = crate::service_fn(func); - let config = crate::Config { + let config = Arc::new(Config { function_name: "test_fn".to_string(), memory: 128, version: "1".to_string(), log_stream: "test_stream".to_string(), log_group: "test_log".to_string(), - }; + }); let runtime = Runtime { client, config }; let client = &runtime.client; diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 2f0287ee..a252475b 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,4 +1,4 @@ -use crate::{Config, Error}; +use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; use http::{HeaderMap, HeaderValue, StatusCode}; @@ -97,7 +97,7 @@ pub struct CognitoIdentity { /// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) /// and [the headers returned by the poll request to the Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next). #[non_exhaustive] -#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Context { /// The AWS request ID generated by the Lambda service. pub request_id: String, @@ -117,12 +117,14 @@ pub struct Context { /// Lambda function configuration from the local environment variables. /// Includes information such as the function name, memory allocation, /// version, and log streams. - pub env_config: Config, + pub env_config: RefConfig, } -impl TryFrom for Context { +impl TryFrom<(RefConfig, HeaderMap)> for Context { type Error = Error; - fn try_from(headers: HeaderMap) -> Result { + fn try_from(data: (RefConfig, HeaderMap)) -> Result { + let env_config = data.0; + let headers = data.1; let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { serde_json::from_str(value.to_str()?)? } else { @@ -158,13 +160,20 @@ impl TryFrom for Context { .map(|v| String::from_utf8_lossy(v.as_bytes()).to_string()), client_context, identity, - ..Default::default() + env_config, }; Ok(ctx) } } +impl Context { + /// The execution deadline for the current invocation. + pub fn deadline(&self) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) + } +} + /// Incoming Lambda request containing the event payload and context. #[derive(Clone, Debug)] pub struct LambdaEvent { @@ -273,6 +282,8 @@ where #[cfg(test)] mod test { use super::*; + use crate::Config; + use std::sync::Arc; #[test] fn round_trip_lambda_error() { @@ -292,6 +303,8 @@ mod test { #[test] fn context_with_expected_values_and_types_resolves() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -300,16 +313,18 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); } #[test] fn context_with_certain_missing_headers_still_resolves() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); } @@ -338,7 +353,9 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_str(&client_context_str).unwrap(), ); - let tried = Context::try_from(headers); + + let config = Arc::new(Config::default()); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.client_context.is_some()); @@ -347,17 +364,20 @@ mod test { #[test] fn context_with_empty_client_context_resolves() { + let config = Arc::new(Config::default()); let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); assert!(tried.unwrap().client_context.is_some()); } #[test] fn context_with_identity_resolves() { + let config = Arc::new(Config::default()); + let cognito_identity = CognitoIdentity { identity_id: String::new(), identity_pool_id: String::new(), @@ -370,7 +390,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_str(&cognito_identity_str).unwrap(), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.identity.is_some()); @@ -379,6 +399,8 @@ mod test { #[test] fn context_with_bad_deadline_type_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert( @@ -390,12 +412,14 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_bad_client_context_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -403,22 +427,26 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_empty_identity_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_bad_identity_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -426,7 +454,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } @@ -434,6 +462,8 @@ mod test { #[should_panic] #[allow(unused_must_use)] fn context_with_missing_request_id_should_panic() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert( @@ -441,13 +471,15 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from(headers); + Context::try_from((config, headers)); } #[test] #[should_panic] #[allow(unused_must_use)] fn context_with_missing_deadline_should_panic() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert( @@ -455,21 +487,6 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from(headers); - } -} - -impl Context { - /// Add environment details to the context by setting `env_config`. - pub fn with_config(self, config: &Config) -> Self { - Self { - env_config: config.clone(), - ..self - } - } - - /// The execution deadline for the current invocation. - pub fn deadline(&self) -> SystemTime { - SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) + Context::try_from((config, headers)); } } From b7df6fcf2f3e039cdaaacaffc8aa323f9b4c225e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 28 Nov 2023 09:49:08 -0800 Subject: [PATCH 227/394] Extract the request ID without allocating extra memory. (#735) Changes the way that the Context is initialized to receive the request ID as an argument. This way we also avoid allocating additional memory for it. Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 19 ++------ lambda-runtime/src/types.rs | 93 ++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 5404fb96..ccd35ab0 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -17,7 +17,6 @@ use hyper::{ use lambda_runtime_api_client::Client; use serde::{Deserialize, Serialize}; use std::{ - convert::TryFrom, env, fmt::{self, Debug, Display}, future::Future, @@ -41,6 +40,8 @@ mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; +use types::invoke_request_id; + /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::Error; @@ -121,6 +122,7 @@ where trace!("New event arrived (run loop)"); let event = next_event_response?; let (parts, body) = event.into_parts(); + let request_id = invoke_request_id(&parts.headers)?; #[cfg(debug_assertions)] if parts.status == http::StatusCode::NO_CONTENT { @@ -130,19 +132,8 @@ where continue; } - let ctx: Context = Context::try_from((self.config.clone(), parts.headers))?; - let request_id = &ctx.request_id.clone(); - - let request_span = match &ctx.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = request_id) - } - }; + let ctx: Context = Context::new(request_id, self.config.clone(), &parts.headers)?; + let request_span = ctx.request_span(); // Group the handling in one future and instrument it with the span async { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index a252475b..82d9b21f 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,15 +1,16 @@ use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; -use http::{HeaderMap, HeaderValue, StatusCode}; +use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - convert::TryFrom, + env, fmt::Debug, time::{Duration, SystemTime}, }; use tokio_stream::Stream; +use tracing::Span; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -120,11 +121,10 @@ pub struct Context { pub env_config: RefConfig, } -impl TryFrom<(RefConfig, HeaderMap)> for Context { - type Error = Error; - fn try_from(data: (RefConfig, HeaderMap)) -> Result { - let env_config = data.0; - let headers = data.1; +impl Context { + /// Create a new [Context] struct based on the fuction configuration + /// and the incoming request data. + pub fn new(request_id: &str, env_config: RefConfig, headers: &HeaderMap) -> Result { let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { serde_json::from_str(value.to_str()?)? } else { @@ -138,11 +138,7 @@ impl TryFrom<(RefConfig, HeaderMap)> for Context { }; let ctx = Context { - request_id: headers - .get("lambda-runtime-aws-request-id") - .expect("missing lambda-runtime-aws-request-id header") - .to_str()? - .to_owned(), + request_id: request_id.to_owned(), deadline: headers .get("lambda-runtime-deadline-ms") .expect("missing lambda-runtime-deadline-ms header") @@ -165,13 +161,37 @@ impl TryFrom<(RefConfig, HeaderMap)> for Context { Ok(ctx) } -} -impl Context { /// The execution deadline for the current invocation. pub fn deadline(&self) -> SystemTime { SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) } + + /// Create a new [`tracing::Span`] for an incoming invocation. + pub(crate) fn request_span(&self) -> Span { + match &self.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!( + "Lambda runtime invoke", + requestId = &self.request_id, + xrayTraceId = trace_id + ) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = &self.request_id) + } + } + } +} + +/// Extract the invocation request id from the incoming request. +pub(crate) fn invoke_request_id(headers: &HeaderMap) -> Result<&str, ToStrError> { + headers + .get("lambda-runtime-aws-request-id") + .expect("missing lambda-runtime-aws-request-id header") + .to_str() } /// Incoming Lambda request containing the event payload and context. @@ -313,7 +333,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); } @@ -324,7 +344,7 @@ mod test { let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); } @@ -355,7 +375,7 @@ mod test { ); let config = Arc::new(Config::default()); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.client_context.is_some()); @@ -369,7 +389,7 @@ mod test { headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); assert!(tried.unwrap().client_context.is_some()); } @@ -390,7 +410,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_str(&cognito_identity_str).unwrap(), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.identity.is_some()); @@ -412,7 +432,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -427,7 +447,7 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -439,7 +459,7 @@ mod test { headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -454,14 +474,13 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } #[test] #[should_panic] - #[allow(unused_must_use)] - fn context_with_missing_request_id_should_panic() { + fn context_with_missing_deadline_should_panic() { let config = Arc::new(Config::default()); let mut headers = HeaderMap::new(); @@ -471,15 +490,26 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from((config, headers)); + let _ = Context::new("id", config, &headers); } #[test] - #[should_panic] - #[allow(unused_must_use)] - fn context_with_missing_deadline_should_panic() { - let config = Arc::new(Config::default()); + fn invoke_request_id_should_not_panic() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + + let _ = invoke_request_id(&headers); + } + #[test] + #[should_panic] + fn invoke_request_id_should_panic() { let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert( @@ -487,6 +517,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from((config, headers)); + + let _ = invoke_request_id(&headers); } } From 3c53476206482535f6b73f118e09258aeeb1d6f8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 28 Nov 2023 09:49:50 -0800 Subject: [PATCH 228/394] Remove unused types. (#736) These types are not used anywhere. Signed-off-by: David Calavera --- lambda-runtime/src/types.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 82d9b21f..8b70ce80 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -19,34 +19,6 @@ pub(crate) struct Diagnostic<'a> { pub(crate) error_message: &'a str, } -/// The request ID, which identifies the request that triggered the function invocation. This header -/// tracks the invocation within the Lambda control plane. The request ID is used to specify completion -/// of a given invocation. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct RequestId(pub String); - -/// The date that the function times out in Unix time milliseconds. For example, `1542409706888`. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct InvocationDeadline(pub u64); - -/// The ARN of the Lambda function, version, or alias that is specified in the invocation. -/// For instance, `arn:aws:lambda:us-east-2:123456789012:function:custom-runtime`. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct FunctionArn(pub String); - -/// The AWS X-Ray Tracing header. For more information, -/// please see [AWS' documentation](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader). -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct XRayTraceId(pub String); - -/// For invocations from the AWS Mobile SDK contains data about client application and device. -#[derive(Debug, Clone, Eq, PartialEq)] -struct MobileClientContext(String); - -/// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. -#[derive(Debug, Clone, Eq, PartialEq)] -struct MobileClientIdentity(String); - /// Client context sent by the AWS Mobile SDK. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ClientContext { From 1e4e20326f7df9829550b370901b18579843222b Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:21:28 -0300 Subject: [PATCH 229/394] Hotfix/documentdb (#742) * Fix field types: - ls_nd must be an AnyDocument - cluster_time must be optional, because it's new at version 4.0 * Fields bug fix --- .../documentdb/events/drop_database_event.rs | 4 +- .../src/event/documentdb/events/drop_event.rs | 3 +- .../event/documentdb/events/insert_event.rs | 2 +- .../event/documentdb/events/replace_event.rs | 2 +- .../event/documentdb/events/update_event.rs | 19 +++++- .../example-documentdb-update-event.json | 60 ++++++++++++------- 6 files changed, 62 insertions(+), 28 deletions(-) diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index c51e345c..273c897c 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -7,7 +7,9 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp pub struct ChangeDropDatabaseEvent { #[serde(rename = "_id")] id: DocumentId, - cluster_time: Timestamp, + #[serde(default)] + cluster_time: Option, + #[serde(default)] #[serde(rename = "lsid")] ls_id: Option, ns: DatabaseCollection, diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index 866ce143..a6f92934 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; pub struct ChangeDropEvent { #[serde(rename = "_id")] id: DocumentId, - cluster_time: Timestamp, + #[serde(default)] + cluster_time: Option, #[serde(default)] #[serde(rename = "lsid")] ls_id: Option, diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index 09ab66b2..de9b5843 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -13,7 +13,7 @@ pub struct ChangeInsertEvent { document_key: DocumentKeyId, #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, //operation_type: String, #[serde(default)] diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index 4a0e58ad..c253e272 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -12,7 +12,7 @@ pub struct ChangeReplaceEvent { document_key: DocumentKeyId, #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, // operation_type: String, #[serde(default)] diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 8698485a..04369cf0 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -2,6 +2,21 @@ use serde::{Deserialize, Serialize}; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateTruncate { + field: String, + new_size: usize, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateDescription { + removed_fields: Vec, + truncated_arrays: Vec, + updated_fields: AnyDocument, +} + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeUpdateEvent { @@ -10,10 +25,12 @@ pub struct ChangeUpdateEvent { #[serde(default)] cluster_time: Option, document_key: DocumentKeyId, + #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, // operation_type: String, + update_description: UpdateDescription, #[serde(default)] txn_number: Option, } diff --git a/lambda-events/src/fixtures/example-documentdb-update-event.json b/lambda-events/src/fixtures/example-documentdb-update-event.json index dbb19159..38f3e659 100644 --- a/lambda-events/src/fixtures/example-documentdb-update-event.json +++ b/lambda-events/src/fixtures/example-documentdb-update-event.json @@ -1,29 +1,43 @@ { - "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", - "events": [ - { - "event": { + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "operationType": "update", + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "documentKey": { "_id": { - "_data": "0163eeb6e7000000090100000009000041e1" + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "updateDescription": { + "updatedFields": { + "email": "alice@10gen.com" }, - "clusterTime": { - "$timestamp": { - "t": 1676588775, - "i": 9 + "removedFields": [ + "phoneNumber" + ], + "truncatedArrays": [ + { + "field": "vacation_time", + "newSize": 36 } - }, - "documentKey": { - "_id": { - "$oid": "63eeb6e7d418cd98afb1c1d7" - } - }, - "ns": { - "db": "test_database", - "coll": "test_collection" - }, - "operationType": "update" + ] } } - ], - "eventSource": "aws:docdb" - } \ No newline at end of file + } + ], + "eventSource": "aws:docdb" +} \ No newline at end of file From 6e953eb6a476f23fad495025bfe2b93467ad7a2c Mon Sep 17 00:00:00 2001 From: David Ramos Date: Sun, 3 Dec 2023 10:22:49 -0800 Subject: [PATCH 230/394] Support internal Lambda extensions (#744) * Support internal Lambda extensions Internal Lambda extensions must be registered during the Lamba lifecycle Init phase, which ends when the runtime calls Next to await the first event. Since the `Extension::run` function both registers and executes the extension, there was previously no way for the runtime to determine that all internal extensions have been registered and that it's safe to proceed to the Invoke lifecycle phase. This change introduces an `Extension::register` method that registers the extension and begins any logs/telemetry handlers. It then returns a new `RegisteredExtension` abstraction that can be used to invoke the extension's run loop concurrently with the runtime's run loop. This change maintains backward compatibility by having the existing `Extension::run` method perform both steps. External Lambda extensions can use either API, and internal extensions should use the new API. Resolves #743. * Add example * Set extension name in example * Remove unnecessary Arc/Mutex --- examples/extension-internal-flush/Cargo.toml | 12 ++ examples/extension-internal-flush/README.md | 30 +++++ examples/extension-internal-flush/src/main.rs | 112 ++++++++++++++++++ lambda-extension/src/extension.rs | 53 ++++++++- 4 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 examples/extension-internal-flush/Cargo.toml create mode 100644 examples/extension-internal-flush/README.md create mode 100644 examples/extension-internal-flush/src/main.rs diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml new file mode 100644 index 00000000..daadd0eb --- /dev/null +++ b/examples/extension-internal-flush/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "extension-internal-flush" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +aws_lambda_events = { path = "../../lambda-events" } +lambda-extension = { path = "../../lambda-extension" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/README.md b/examples/extension-internal-flush/README.md new file mode 100644 index 00000000..553f7a3d --- /dev/null +++ b/examples/extension-internal-flush/README.md @@ -0,0 +1,30 @@ +# AWS Lambda runtime + internal extension example + +This example demonstrates how to build an AWS Lambda function that includes a +[Lambda internal extension](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html). +Unlike external extensions that run as separate processes, an internal extension runs within the +main runtime process. + +One use case for internal extensions is to flush logs or telemetry data after the Lambda runtime +handler has finished processing an event but before the execution environment is frozen awaiting the +arrival of the next event. Without an explicit flush, telemetry data may never be sent since the +execution environment will remain frozen and eventually be terminated if no additional events arrive. + +Note that for +[synchronous](https://docs.aws.amazon.com/lambda/latest/dg/invocation-sync.html) Lambda invocations +(e.g., via +[Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-integrations.html)), +the Lambda service returns the response to the caller immediately. Extensions may continue to run +without introducing an observable delay. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +The last command will give you an ARN for the extension layer that you can use in your functions. + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --arm64` diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs new file mode 100644 index 00000000..3706809d --- /dev/null +++ b/examples/extension-internal-flush/src/main.rs @@ -0,0 +1,112 @@ +use anyhow::anyhow; +use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; +use lambda_extension::{service_fn, Error, Extension, NextEvent}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; +use tokio::sync::Mutex; + +use std::sync::Arc; + +/// Implements an internal Lambda extension to flush logs/telemetry after each request. +struct FlushExtension { + request_done_receiver: Mutex>, +} + +impl FlushExtension { + pub fn new(request_done_receiver: UnboundedReceiver<()>) -> Self { + Self { + request_done_receiver: Mutex::new(request_done_receiver), + } + } + + pub async fn invoke(&self, event: lambda_extension::LambdaEvent) -> Result<(), Error> { + match event.next { + // NB: Internal extensions only support the INVOKE event. + NextEvent::Shutdown(shutdown) => { + return Err(anyhow!("extension received unexpected SHUTDOWN event: {:?}", shutdown).into()); + } + NextEvent::Invoke(_e) => {} + } + + eprintln!("[extension] waiting for event to be processed"); + + // Wait for runtime to finish processing event. + self.request_done_receiver + .lock() + .await + .recv() + .await + .ok_or_else(|| anyhow!("channel is closed"))?; + + eprintln!("[extension] flushing logs and telemetry"); + + // + + Ok(()) + } +} + +/// Object that you send to SQS and plan to process with the function. +#[derive(Debug, Deserialize, Serialize)] +struct Data { + a: String, + b: i64, +} + +/// Implements the main event handler for processing events from an SQS queue. +struct EventHandler { + request_done_sender: UnboundedSender<()>, +} + +impl EventHandler { + pub fn new(request_done_sender: UnboundedSender<()>) -> Self { + Self { request_done_sender } + } + + pub async fn invoke( + &self, + event: lambda_runtime::LambdaEvent>, + ) -> Result { + let data = &event.payload.records[0].body; + eprintln!("[runtime] received event {data:?}"); + + // + + // Notify the extension to flush traces. + self.request_done_sender.send(()).map_err(Box::new)?; + + Ok(SqsBatchResponse::default()) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let (request_done_sender, request_done_receiver) = unbounded_channel::<()>(); + + let flush_extension = Arc::new(FlushExtension::new(request_done_receiver)); + let extension = Extension::new() + // Internal extensions only support INVOKE events. + .with_events(&["INVOKE"]) + .with_events_processor(service_fn(|event| { + let flush_extension = flush_extension.clone(); + async move { flush_extension.invoke(event).await } + })) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("internal-flush") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await?; + + let handler = Arc::new(EventHandler::new(request_done_sender)); + + tokio::try_join!( + lambda_runtime::run(service_fn(|event| { + let handler = handler.clone(); + async move { handler.invoke(event).await } + })), + extension.run(), + )?; + + Ok(()) +} diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 4747b041..d653e0dc 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -215,14 +215,21 @@ where } } - /// Execute the given extension - pub async fn run(self) -> Result<(), Error> { + /// Register the extension. + /// + /// Performs the + /// [init phase](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-ib) + /// Lambda lifecycle operations to register the extension. When implementing an internal Lambda + /// extension, it is safe to call `lambda_runtime::run` once the future returned by this + /// function resolves. + pub async fn register(self) -> Result, Error> { let client = &Client::builder().build()?; let extension_id = register(client, self.extension_name, self.events).await?; let extension_id = extension_id.to_str()?; - let mut ep = self.events_processor; + // Logs API subscriptions must be requested during the Lambda init phase (see + // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-subscribing). if let Some(mut log_processor) = self.logs_processor { trace!("Log processor found"); @@ -262,6 +269,8 @@ where trace!("Registered extension with Logs API"); } + // Telemetry API subscriptions must be requested during the Lambda init phase (see + // https://docs.aws.amazon.com/lambda/latest/dg/telemetry-api.html#telemetry-api-registration if let Some(mut telemetry_processor) = self.telemetry_processor { trace!("Telemetry processor found"); @@ -301,6 +310,42 @@ where trace!("Registered extension with Telemetry API"); } + Ok(RegisteredExtension { + extension_id: extension_id.to_string(), + events_processor: self.events_processor, + }) + } + + /// Execute the given extension. + pub async fn run(self) -> Result<(), Error> { + self.register().await?.run().await + } +} + +/// An extension registered by calling [`Extension::register`]. +pub struct RegisteredExtension { + extension_id: String, + events_processor: E, +} + +impl RegisteredExtension +where + E: Service, + E::Future: Future>, + E::Error: Into> + fmt::Display + fmt::Debug, +{ + /// Execute the extension's run loop. + /// + /// Performs the + /// [invoke](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-invoke) + /// and, for external Lambda extensions registered to receive the `SHUTDOWN` event, the + /// [shutdown](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-shutdown) + /// Lambda lifecycle phases. + pub async fn run(self) -> Result<(), Error> { + let client = &Client::builder().build()?; + let mut ep = self.events_processor; + let extension_id = &self.extension_id; + let incoming = async_stream::stream! { loop { trace!("Waiting for next event (incoming loop)"); @@ -351,6 +396,8 @@ where return Err(err.into()); } } + + // Unreachable. Ok(()) } } From 3231e9f144d73b56759d1acbd8e243d283657ca0 Mon Sep 17 00:00:00 2001 From: KUrushi <36495149+KUrushi@users.noreply.github.com> Date: Tue, 5 Dec 2023 01:24:07 +0900 Subject: [PATCH 231/394] add lambda event agent for amazon bedrock (#746) * add lambda event agent for amazon bedrock * add comment for properties * add comment for AgentEvent * add check-event-features for bedrock * remove duplicated module * fix invalid naming * apply formatting --- Makefile | 1 + lambda-events/Cargo.toml | 2 + .../src/event/bedrock_agent_runtime/mod.rs | 94 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-bedrock-agent-runtime-event.json | 40 ++++++++ 5 files changed, 141 insertions(+) create mode 100644 lambda-events/src/event/bedrock_agent_runtime/mod.rs create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json diff --git a/Makefile b/Makefile index 76e57e94..049be8f8 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features apigw cargo test --package aws_lambda_events --no-default-features --features appsync cargo test --package aws_lambda_events --no-default-features --features autoscaling + cargo test --package aws_lambda_events --no-default-features --features bedrock_agent_runtime cargo test --package aws_lambda_events --no-default-features --features chime_bot cargo test --package aws_lambda_events --no-default-features --features clientvpn cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index ca5fa1a8..b35809a2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -43,6 +43,7 @@ default = [ "apigw", "appsync", "autoscaling", + "bedrock_agent_runtime", "chime_bot", "clientvpn", "cloudformation", @@ -85,6 +86,7 @@ alb = ["bytes", "http", "http-body", "http-serde", "query_map"] apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] appsync = [] autoscaling = ["chrono"] +bedrock_agent_runtime = [] chime_bot = ["chrono"] clientvpn = [] cloudformation = [] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs new file mode 100644 index 00000000..65ab4a9d --- /dev/null +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// The Event sent to Lambda from Agents for Amazon Bedrock. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentEvent { + ///The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. + pub message_version: String, + ///Contains information about the name, ID, alias, and version of the agent that the action group belongs to. + pub agent: Agent, + ///The user input for the conversation turn. + pub input_text: String, + /// The unique identifier of the agent session. + pub session_id: String, + /// The name of the action group. + pub action_group: String, + /// The path to the API operation, as defined in the OpenAPI schema. + pub api_path: String, + /// The method of the API operation, as defined in the OpenAPI schema. + pub http_method: String, + /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema. + pub parameters: Vec, + /// Contains the request body and its properties, as defined in the OpenAPI schema. + pub request_body: RequestBody, + /// Contains session attributes and their values. + pub session_attributes: HashMap, + /// Contains prompt attributes and their values. + pub prompt_session_attributes: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestBody { + /// Contains the request body and its properties + pub content: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Content { + /// The content of the request body + pub properties: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Property { + /// The name of the parameter + pub name: String, + /// The type of the parameter + pub r#type: String, + /// The value of the parameter + pub value: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Parameter { + /// The name of the parameter + pub name: String, + /// The type of the parameter + pub r#type: String, + /// The value of the parameter + pub value: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Agent { + /// The name of the agent. + pub name: String, + /// The unique identifier of the agent. + pub id: String, + /// The alias of the agent. + pub alias: String, + /// The version of the agent. + pub version: String, +} + +#[cfg(test)] +mod tests { + use serde_json; + + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent__runtime_event() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 5ee57911..28a0c82b 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -17,6 +17,10 @@ pub mod appsync; #[cfg(feature = "autoscaling")] pub mod autoscaling; +/// AWS Lambda event definitions for agent for amazon bedrock +#[cfg(feature = "bedrock_agent_runtime")] +pub mod bedrock_agent_runtime; + /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] pub mod chime_bot; diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json new file mode 100644 index 00000000..b5244515 --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json @@ -0,0 +1,40 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "parameters": [ + { + "name": "param1", + "type": "string", + "value": "value1" + } + ], + "requestBody": { + "content": { + "application/json": { + "properties": [ + { + "name": "prop1", + "type": "string", + "value": "value1" + } + ] + } + } + }, + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file From d513b13b4c48122602c0690f55147607f3bcc0da Mon Sep 17 00:00:00 2001 From: KUrushi <36495149+KUrushi@users.noreply.github.com> Date: Thu, 7 Dec 2023 01:43:17 +0900 Subject: [PATCH 232/394] Made some properties of Bedrock's Event optional. (#748) * change some properties to optional value * Update lambda-events/src/event/bedrock_agent_runtime/mod.rs add default value to parameters Co-authored-by: David Calavera * Update lambda-events/src/event/bedrock_agent_runtime/mod.rs add default to request_body Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- .../src/event/bedrock_agent_runtime/mod.rs | 24 ++++++++++++-- ...gent-runtime-event-without-parameters.json | 33 +++++++++++++++++++ ...nt-runtime-event-without-request-body.json | 27 +++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index 65ab4a9d..836d2f95 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -20,9 +20,11 @@ pub struct AgentEvent { /// The method of the API operation, as defined in the OpenAPI schema. pub http_method: String, /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema. - pub parameters: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parameters: Option>, /// Contains the request body and its properties, as defined in the OpenAPI schema. - pub request_body: RequestBody, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub request_body: Option, /// Contains session attributes and their values. pub session_attributes: HashMap, /// Contains prompt attributes and their values. @@ -91,4 +93,22 @@ mod tests { let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent_runtime_event_without_parameters() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent_runtime_event_without_request_body() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json new file mode 100644 index 00000000..d41d9ee1 --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json @@ -0,0 +1,33 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "requestBody": { + "content": { + "application/json": { + "properties": [ + { + "name": "prop1", + "type": "string", + "value": "value1" + } + ] + } + } + }, + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json new file mode 100644 index 00000000..2ca5aa9a --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json @@ -0,0 +1,27 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "parameters": [ + { + "name": "param1", + "type": "string", + "value": "value1" + } + ], + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file From 9b21cd01445675d9e66ada94204adfbeaca3702a Mon Sep 17 00:00:00 2001 From: David Ramos Date: Wed, 13 Dec 2023 18:56:07 -0800 Subject: [PATCH 233/394] Remove #[serde(deny_unknown_fields)] (#753) Adding new fields to request payloads is typically not considered to be a breaking change, but using #[serde(deny_unknown_fields)] would cause all Lambda functions to start failing immediately in production if a new feature were deployed that added a new request field. Fixes #750. --- lambda-events/src/event/alb/mod.rs | 1 - lambda-events/src/event/apigw/mod.rs | 3 --- lambda-events/src/event/cloudformation/mod.rs | 4 ---- lambda-http/src/deserializer.rs | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 7bb1eb7f..259dce23 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct AlbTargetGroupRequest { #[serde(with = "http_method")] pub http_method: Method, diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 9119bdc7..fcc5ed0c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayProxyRequest where T1: DeserializeOwned, @@ -120,7 +119,6 @@ where /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayV2httpRequest { #[serde(default, rename = "type")] pub kind: Option, @@ -335,7 +333,6 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayWebsocketProxyRequest where T1: DeserializeOwned, diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index e2d51745..0d3f816c 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -18,7 +18,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct CreateRequest where P2: DeserializeOwned + Serialize, @@ -37,7 +36,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct UpdateRequest where P1: DeserializeOwned + Serialize, @@ -60,7 +58,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct DeleteRequest where P2: DeserializeOwned + Serialize, @@ -87,7 +84,6 @@ mod test { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] - #[serde(deny_unknown_fields)] struct TestProperties { key_1: String, key_2: Vec, diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index a77f68a5..7756629c 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -107,7 +107,7 @@ mod tests { #[test] fn test_deserialize_error() { - let err = serde_json::from_str::("{\"command\": \"hi\"}").unwrap_err(); + let err = serde_json::from_str::("{\"body\": {}}").unwrap_err(); assert_eq!(ERROR_CONTEXT, err.to_string()); } From 2a2ae8bad6913d1af12e25e8fa60af8da21ff443 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Fri, 15 Dec 2023 10:55:41 +0900 Subject: [PATCH 234/394] Add session_issuer field to SessionContext (#751) * Add session_issuer field to SessionContext * Update lambda-events/src/event/s3/object_lambda.rs Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- lambda-events/src/event/s3/object_lambda.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 1eec2c0d..69e9907d 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -102,6 +102,8 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct SessionContext { pub attributes: HashMap, + #[serde(default)] + pub session_issuer: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] From fb86ebc1fdb680107165f081a36863574de40917 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 15 Dec 2023 17:26:50 -0800 Subject: [PATCH 235/394] Config::from_env never returns an error. (#754) We don't need to return Result because the function either panics or returns the Ok value. Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index ccd35ab0..49add76c 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -64,8 +64,8 @@ type RefConfig = Arc; impl Config { /// Attempts to read configuration from environment variables. - pub fn from_env() -> Result { - let conf = Config { + pub fn from_env() -> Self { + Config { function_name: env::var("AWS_LAMBDA_FUNCTION_NAME").expect("Missing AWS_LAMBDA_FUNCTION_NAME env var"), memory: env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") .expect("Missing AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var") @@ -74,8 +74,7 @@ impl Config { version: env::var("AWS_LAMBDA_FUNCTION_VERSION").expect("Missing AWS_LAMBDA_FUNCTION_VERSION env var"), log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME").unwrap_or_default(), log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME").unwrap_or_default(), - }; - Ok(conf) + } } } @@ -254,7 +253,7 @@ where E: Into + Send + Debug, { trace!("Loading config from env"); - let config = Config::from_env()?; + let config = Config::from_env(); let client = Client::builder().build().expect("Unable to create a runtime client"); let runtime = Runtime { client, @@ -527,7 +526,7 @@ mod endpoint_tests { if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); } - let config = Config::from_env().expect("Failed to read env vars"); + let config = Config::from_env(); let runtime = Runtime { client, From 07430f1e174ab81477df40a42325f0925036ee7b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Dec 2023 08:30:50 -0800 Subject: [PATCH 236/394] Hyper 1 upgrade (#749) * chore: update hyper/http * chore: update hyper with legacy * Create new workspace dependencies. This makes tracking versions across packages easier. Signed-off-by: David Calavera * Bring helpers to work with Hyper 1. Copy some of the utils that Axum created to support Hyper 1. Signed-off-by: David Calavera * Upgrade lambda_runtime to Hyper 1. Signed-off-by: David Calavera * Upgrade lambda_http to Hyper 1. Signed-off-by: David Calavera * Update Axum examples to version 0.7. Signed-off-by: David Calavera * Make extensions compile. Switch to use hyper::service::service_fn definitions. Implement new Hyper's polling loop for http connections. Signed-off-by: David Calavera * Update tower-http to make cors example work. Signed-off-by: David Calavera * Update tower-http to make tower-trace example work. Signed-off-by: David Calavera * Make less copies of extension services. Signed-off-by: David Calavera * Bring Body::channel implementation from Hyper Given the lack of a public implementation anymore, we don't have other choice but copying Hyper's private implementation. Signed-off-by: David Calavera * Organize more dependencies. Signed-off-by: David Calavera * Cleanup commented code. Signed-off-by: David Calavera * Cleanup unused dependencies. Signed-off-by: David Calavera * Introduce a module to group all streaming functionality. This makes the streaming functionallity more concise. It aliases other functionality to keep backwards compatibility. Signed-off-by: David Calavera * Remove unnecesary loop. Signed-off-by: David Calavera * Don't re-export `lambda_runtime_api_client::body`. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera Co-authored-by: Spencer C. Imbleau --- Cargo.toml | 16 ++ examples/basic-streaming-response/Cargo.toml | 5 - examples/basic-streaming-response/src/main.rs | 16 +- examples/http-axum-diesel-ssl/Cargo.toml | 2 +- examples/http-axum-diesel/Cargo.toml | 2 +- examples/http-axum/Cargo.toml | 5 +- examples/http-cors/Cargo.toml | 2 +- examples/http-tower-trace/Cargo.toml | 2 +- lambda-events/Cargo.toml | 20 +-- lambda-events/src/encodings/http.rs | 31 ++-- .../src/event/bedrock_agent_runtime/mod.rs | 2 - lambda-extension/Cargo.toml | 16 +- lambda-extension/src/error.rs | 2 +- lambda-extension/src/extension.rs | 95 +++++++----- lambda-extension/src/logs.rs | 23 +-- lambda-extension/src/requests.rs | 3 +- lambda-extension/src/telemetry.rs | 18 ++- lambda-http/Cargo.toml | 22 +-- lambda-http/src/ext/extensions.rs | 4 + lambda-http/src/response.rs | 16 +- lambda-http/src/streaming.rs | 24 ++- lambda-runtime-api-client/Cargo.toml | 20 ++- lambda-runtime-api-client/src/body/channel.rs | 110 ++++++++++++++ lambda-runtime-api-client/src/body/mod.rs | 143 ++++++++++++++++++ lambda-runtime-api-client/src/body/sender.rs | 135 +++++++++++++++++ lambda-runtime-api-client/src/body/watch.rs | 69 +++++++++ lambda-runtime-api-client/src/error.rs | 33 ++++ lambda-runtime-api-client/src/lib.rs | 36 ++--- lambda-runtime/Cargo.toml | 51 ++++--- lambda-runtime/src/lib.rs | 97 ++++++------ lambda-runtime/src/requests.rs | 32 ++-- lambda-runtime/src/simulated.rs | 79 +++++++--- lambda-runtime/src/streaming.rs | 35 +++++ lambda-runtime/src/types.rs | 19 ++- 34 files changed, 921 insertions(+), 264 deletions(-) create mode 100644 lambda-runtime-api-client/src/body/channel.rs create mode 100644 lambda-runtime-api-client/src/body/mod.rs create mode 100644 lambda-runtime-api-client/src/body/sender.rs create mode 100644 lambda-runtime-api-client/src/body/watch.rs create mode 100644 lambda-runtime-api-client/src/error.rs create mode 100644 lambda-runtime/src/streaming.rs diff --git a/Cargo.toml b/Cargo.toml index 16f57a7b..51d57ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,19 @@ members = [ ] exclude = ["examples"] + +[workspace.dependencies] +base64 = "0.21" +bytes = "1" +futures = "0.3" +futures-channel = "0.3" +futures-util = "0.3" +http = "1.0" +http-body = "1.0" +http-body-util = "0.1" +http-serde = "2.0" +hyper = "1.0" +hyper-util = "0.1.1" +pin-project-lite = "0.2" +tower = "0.4" +tower-service = "0.3" diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index 4bbe66f4..e9b7499c 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -6,11 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -hyper = { version = "0.14", features = [ - "http1", - "client", - "stream", -] } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 9d505206..c8932554 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,12 +1,15 @@ -use hyper::body::Body; -use lambda_runtime::{service_fn, Error, LambdaEvent, StreamResponse}; +use lambda_runtime::{ + service_fn, + streaming::{channel, Body, Response}, + Error, LambdaEvent, +}; use serde_json::Value; use std::{thread, time::Duration}; -async fn func(_event: LambdaEvent) -> Result, Error> { +async fn func(_event: LambdaEvent) -> Result, Error> { let messages = vec!["Hello", "world", "from", "Lambda!"]; - let (mut tx, rx) = Body::channel(); + let (mut tx, rx) = channel(); tokio::spawn(async move { for message in messages.iter() { @@ -15,10 +18,7 @@ async fn func(_event: LambdaEvent) -> Result, Error> } }); - Ok(StreamResponse { - metadata_prelude: Default::default(), - stream: rx, - }) + Ok(Response::from(rx)) } #[tokio::main] diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index cdcdd4ef..006a82ce 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -axum = "0.6.4" +axum = "0.7" bb8 = "0.8.0" diesel = "2.0.3" diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 5a97cfab..0366f32d 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -axum = "0.6.4" +axum = "0.7" bb8 = "0.8.0" diesel = "2.0.3" diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 50db3ebf..88df4140 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -11,11 +11,10 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] +axum = "0.7" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - -axum = "0.6.4" -serde_json = "1.0" diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 9fd7f25b..059a3f63 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.3.3", features = ["cors"] } +tower-http = { version = "0.5", features = ["cors"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 2b8f7a60..0b0c46a9 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -14,6 +14,6 @@ edition = "2021" lambda_http = { path = "../../lambda-http" } lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.3.4", features = ["trace"] } +tower-http = { version = "0.5", features = ["trace"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b35809a2..29d4e191 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -16,25 +16,25 @@ categories = ["api-bindings", "encoding", "web-programming"] edition = "2021" [dependencies] -base64 = "0.21" -http = { version = "0.2", optional = true } -http-body = { version = "0.4", optional = true } -http-serde = { version = "^1", optional = true } -serde = { version = "^1", features = ["derive"] } -serde_with = { version = "^3", features = ["json"], optional = true } -serde_json = "^1" -serde_dynamo = { version = "^4.1", optional = true } -bytes = { version = "1", features = ["serde"], optional = true } +base64 = { workspace = true } +bytes = { workspace = true, features = ["serde"], optional = true } chrono = { version = "0.4.31", default-features = false, features = [ "clock", "serde", "std", ], optional = true } +flate2 = { version = "1.0.24", optional = true } +http = { workspace = true, optional = true } +http-body = { workspace = true, optional = true } +http-serde = { workspace = true, optional = true } query_map = { version = "^0.7", features = [ "serde", "url-query", ], optional = true } -flate2 = { version = "1.0.24", optional = true } +serde = { version = "^1", features = ["derive"] } +serde_with = { version = "^3", features = ["json"], optional = true } +serde_json = "^1" +serde_dynamo = { version = "^4.1", optional = true } [features] default = [ diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index effb48f4..1cb10c81 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -218,25 +218,6 @@ impl HttpBody for Body { type Data = Bytes; type Error = super::Error; - fn poll_data( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll>> { - let body = take(self.get_mut()); - Poll::Ready(match body { - Body::Empty => None, - Body::Text(s) => Some(Ok(s.into())), - Body::Binary(b) => Some(Ok(b.into())), - }) - } - - fn poll_trailers( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll, Self::Error>> { - Poll::Ready(Ok(None)) - } - fn is_end_stream(&self) -> bool { matches!(self, Body::Empty) } @@ -248,6 +229,18 @@ impl HttpBody for Body { Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), } } + + fn poll_frame( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>>> { + let body = take(self.get_mut()); + Poll::Ready(match body { + Body::Empty => None, + Body::Text(s) => Some(Ok(http_body::Frame::data(s.into()))), + Body::Binary(b) => Some(Ok(http_body::Frame::data(b.into()))), + }) + } } #[cfg(test)] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index 836d2f95..cf84d4d3 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -82,8 +82,6 @@ pub struct Agent { #[cfg(test)] mod tests { - use serde_json; - #[test] #[cfg(feature = "bedrock-agent-runtime")] fn example_bedrock_agent__runtime_event() { diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 5d9d54b4..667b866f 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -15,15 +15,21 @@ readme = "README.md" [dependencies] async-stream = "0.3" -bytes = "1.0" +bytes = { workspace = true } chrono = { version = "0.4", features = ["serde"] } -http = "0.2" -hyper = { version = "0.14.20", features = ["http1", "client", "server", "stream", "runtime"] } +http = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true, features = ["http1", "client", "server"] } +hyper-util = { workspace = true } lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } -tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +tokio = { version = "1.0", features = [ + "macros", + "io-util", + "sync", + "rt-multi-thread", +] } tokio-stream = "0.1.2" tower = { version = "0.4", features = ["make", "util"] } - diff --git a/lambda-extension/src/error.rs b/lambda-extension/src/error.rs index 2c3e23b3..4f6a9909 100644 --- a/lambda-extension/src/error.rs +++ b/lambda-extension/src/error.rs @@ -1,5 +1,5 @@ /// Error type that extensions may result in -pub type Error = lambda_runtime_api_client::Error; +pub type Error = lambda_runtime_api_client::BoxError; /// Simple error that encapsulates human readable descriptions #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index d653e0dc..cac1c7ec 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,13 +1,18 @@ +use http::Request; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; + +use hyper_util::rt::tokio::TokioIo; +use lambda_runtime_api_client::Client; use std::{ convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, }; - -use hyper::{server::conn::AddrStream, Server}; -use lambda_runtime_api_client::Client; -use tokio::sync::Mutex; +use tokio::{net::TcpListener, sync::Mutex}; use tokio_stream::StreamExt; -use tower::{service_fn, MakeService, Service, ServiceExt}; -use tracing::{error, trace}; +use tower::{MakeService, Service, ServiceExt}; +use tracing::trace; use crate::{ logs::*, @@ -64,22 +69,22 @@ impl<'a, E, L, T> Extension<'a, E, L, T> where E: Service, E::Future: Future>, - E::Error: Into> + fmt::Display + fmt::Debug, + E::Error: Into + fmt::Display + fmt::Debug, // Fixme: 'static bound might be too restrictive L: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, L::Service: Service, Response = ()> + Send + Sync, >>::Future: Send + 'a, - L::Error: Into> + fmt::Debug, - L::MakeError: Into> + fmt::Debug, + L::Error: Into + fmt::Debug, + L::MakeError: Into + fmt::Debug, L::Future: Send, // Fixme: 'static bound might be too restrictive T: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, T::Service: Service, Response = ()> + Send + Sync, >>::Future: Send + 'a, - T::Error: Into> + fmt::Debug, - T::MakeError: Into> + fmt::Debug, + T::Error: Into + fmt::Debug, + T::MakeError: Into + fmt::Debug, T::Future: Send, { /// Create a new [`Extension`] with a given extension name @@ -104,7 +109,7 @@ where where N: Service, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { events_processor: ep, @@ -126,7 +131,7 @@ where where N: Service<()>, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { logs_processor: Some(lp), @@ -173,7 +178,7 @@ where where N: Service<()>, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { telemetry_processor: Some(lp), @@ -235,22 +240,27 @@ where validate_buffering_configuration(self.log_buffering)?; - // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.log_port_number)); - let make_service = service_fn(move |_socket: &AddrStream| { - trace!("Creating new log processor Service"); - let service = log_processor.make_service(()); - async move { - let service = Arc::new(Mutex::new(service.await?)); - Ok::<_, L::MakeError>(service_fn(move |req| log_wrapper(service.clone(), req))) - } - }); - let server = Server::bind(&addr).serve(make_service); - tokio::spawn(async move { - if let Err(e) = server.await { - error!("Error while running log processor: {}", e); + let service = log_processor.make_service(()); + let service = Arc::new(Mutex::new(service.await.unwrap())); + tokio::task::spawn(async move { + trace!("Creating new logs processor Service"); + + loop { + let service: Arc> = service.clone(); + let make_service = service_fn(move |req: Request| log_wrapper(service.clone(), req)); + + let listener = TcpListener::bind(addr).await.unwrap(); + let (tcp, _) = listener.accept().await.unwrap(); + let io = TokioIo::new(tcp); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { + println!("Error serving connection: {:?}", err); + } + }); } }); + trace!("Log processor started"); // Call Logs API to start receiving events @@ -276,22 +286,27 @@ where validate_buffering_configuration(self.telemetry_buffering)?; - // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number)); - let make_service = service_fn(move |_socket: &AddrStream| { + let service = telemetry_processor.make_service(()); + let service = Arc::new(Mutex::new(service.await.unwrap())); + tokio::task::spawn(async move { trace!("Creating new telemetry processor Service"); - let service = telemetry_processor.make_service(()); - async move { - let service = Arc::new(Mutex::new(service.await?)); - Ok::<_, T::MakeError>(service_fn(move |req| telemetry_wrapper(service.clone(), req))) - } - }); - let server = Server::bind(&addr).serve(make_service); - tokio::spawn(async move { - if let Err(e) = server.await { - error!("Error while running telemetry processor: {}", e); + + loop { + let service = service.clone(); + let make_service = service_fn(move |req| telemetry_wrapper(service.clone(), req)); + + let listener = TcpListener::bind(addr).await.unwrap(); + let (tcp, _) = listener.accept().await.unwrap(); + let io = TokioIo::new(tcp); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { + println!("Error serving connection: {:?}", err); + } + }); } }); + trace!("Telemetry processor started"); // Call Telemetry API to start receiving events @@ -361,7 +376,7 @@ where let event = event?; let (_parts, body) = event.into_parts(); - let body = hyper::body::to_bytes(body).await?; + let body = body.collect().await?.to_bytes(); trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose let event: NextEvent = serde_json::from_slice(&body)?; let is_invoke = event.is_invoke(); diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index c453c951..4d1948a0 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -1,6 +1,10 @@ use chrono::{DateTime, Utc}; +use http::{Request, Response}; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; -use std::{boxed::Box, fmt, sync::Arc}; +use std::{fmt, sync::Arc}; use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; @@ -186,34 +190,31 @@ pub(crate) fn validate_buffering_configuration(log_buffering: Option` for the /// underlying `Service` to process. -pub(crate) async fn log_wrapper( - service: Arc>, - req: hyper::Request, -) -> Result, Box> +pub(crate) async fn log_wrapper(service: Arc>, req: Request) -> Result, Error> where S: Service, Response = ()>, - S::Error: Into> + fmt::Debug, + S::Error: Into + fmt::Debug, S::Future: Send, { trace!("Received logs request"); // Parse the request body as a Vec - let body = match hyper::body::to_bytes(req.into_body()).await { + let body = match req.into_body().collect().await { Ok(body) => body, Err(e) => { error!("Error reading logs request body: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; - let logs: Vec = match serde_json::from_slice(&body) { + let logs: Vec = match serde_json::from_slice(&body.to_bytes()) { Ok(logs) => logs, Err(e) => { error!("Error parsing logs: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; @@ -226,7 +227,7 @@ where } } - Ok(hyper::Response::new(hyper::Body::empty())) + Ok(hyper::Response::new(Body::empty())) } #[cfg(test)] diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 75c24a0f..4d5f1527 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -1,7 +1,6 @@ use crate::{Error, LogBuffering}; use http::{Method, Request}; -use hyper::Body; -use lambda_runtime_api_client::build_request; +use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; const EXTENSION_NAME_HEADER: &str = "Lambda-Extension-Name"; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index b3131338..1e83ee8e 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -1,4 +1,8 @@ use chrono::{DateTime, Utc}; +use http::{Request, Response}; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use lambda_runtime_api_client::body::Body; use serde::Deserialize; use std::{boxed::Box, fmt, sync::Arc}; use tokio::sync::Mutex; @@ -256,8 +260,8 @@ pub struct RuntimeDoneMetrics { /// underlying `Service` to process. pub(crate) async fn telemetry_wrapper( service: Arc>, - req: hyper::Request, -) -> Result, Box> + req: Request, +) -> Result, Box> where S: Service, Response = ()>, S::Error: Into> + fmt::Debug, @@ -265,24 +269,24 @@ where { trace!("Received telemetry request"); // Parse the request body as a Vec - let body = match hyper::body::to_bytes(req.into_body()).await { + let body = match req.into_body().collect().await { Ok(body) => body, Err(e) => { error!("Error reading telemetry request body: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; - let telemetry: Vec = match serde_json::from_slice(&body) { + let telemetry: Vec = match serde_json::from_slice(&body.to_bytes()) { Ok(telemetry) => telemetry, Err(e) => { error!("Error parsing telemetry: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; @@ -295,7 +299,7 @@ where } } - Ok(hyper::Response::new(hyper::Body::empty())) + Ok(hyper::Response::new(Body::empty())) } #[cfg(test)] diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index fc93d88f..057134a6 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -23,21 +23,24 @@ apigw_websockets = [] alb = [] [dependencies] -base64 = "0.21" -bytes = "1.4" -futures = "0.3" -http = "0.2" -http-body = "0.4" -hyper = "0.14" +base64 = { workspace = true } +bytes = { workspace = true } +encoding_rs = "0.8" +futures = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } +mime = "0.3" +percent-encoding = "2.2" +pin-project-lite = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" tokio-stream = "0.1.2" -mime = "0.3" -encoding_rs = "0.8" url = "2.2" -percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" @@ -46,6 +49,7 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index 313090c6..cfbdaec2 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -7,20 +7,24 @@ use lambda_runtime::Context; use crate::request::RequestContext; /// ALB/API gateway pre-parsed http query string parameters +#[derive(Clone)] pub(crate) struct QueryStringParameters(pub(crate) QueryMap); /// API gateway pre-extracted url path parameters /// /// These will always be empty for ALB requests +#[derive(Clone)] pub(crate) struct PathParameters(pub(crate) QueryMap); /// API gateway configured /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// /// These will always be empty for ALB requests +#[derive(Clone)] pub(crate) struct StageVariables(pub(crate) QueryMap); /// ALB/API gateway raw http path without any stage information +#[derive(Clone)] pub(crate) struct RawHttpPath(pub(crate) String); /// Extensions for [`lambda_http::Request`], `http::request::Parts`, and `http::Extensions` structs diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e77ec181..d26ef838 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -13,7 +13,7 @@ use http::header::CONTENT_ENCODING; use http::HeaderMap; use http::{header::CONTENT_TYPE, Response, StatusCode}; use http_body::Body as HttpBody; -use hyper::body::to_bytes; +use http_body_util::BodyExt; use mime::{Mime, CHARSET}; use serde::Serialize; use std::borrow::Cow; @@ -305,7 +305,15 @@ where B::Data: Send, B::Error: fmt::Debug, { - Box::pin(async move { Body::from(to_bytes(body).await.expect("unable to read bytes from body").to_vec()) }) + Box::pin(async move { + Body::from( + body.collect() + .await + .expect("unable to read bytes from body") + .to_bytes() + .to_vec(), + ) + }) } fn convert_to_text(body: B, content_type: &str) -> BodyFuture @@ -326,7 +334,7 @@ where // assumes utf-8 Box::pin(async move { - let bytes = to_bytes(body).await.expect("unable to read bytes from body"); + let bytes = body.collect().await.expect("unable to read bytes from body").to_bytes(); let (content, _, _) = encoding.decode(&bytes); match content { @@ -345,7 +353,7 @@ mod tests { header::{CONTENT_ENCODING, CONTENT_TYPE}, Response, StatusCode, }; - use hyper::Body as HyperBody; + use lambda_runtime_api_client::body::Body as HyperBody; use serde_json::{self, json}; const SVG_LOGO: &str = include_str!("../tests/data/svg_logo.svg"); diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index a59cf700..601e699b 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -63,19 +63,11 @@ where lambda_runtime::run(svc).await } +pin_project_lite::pin_project! { pub struct BodyStream { + #[pin] pub(crate) body: B, } - -impl BodyStream -where - B: Body + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - fn project(self: Pin<&mut Self>) -> Pin<&mut B> { - unsafe { self.map_unchecked_mut(|s| &mut s.body) } - } } impl Stream for BodyStream @@ -86,8 +78,14 @@ where { type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let body = self.project(); - body.poll_data(cx) + #[inline] + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match futures_util::ready!(self.as_mut().project().body.poll_frame(cx)?) { + Some(frame) => match frame.into_data() { + Ok(data) => Poll::Ready(Some(Ok(data))), + Err(_frame) => Poll::Ready(None), + }, + None => Poll::Ready(None), + } } } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index ae3e0f70..8868b145 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.8.0" edition = "2021" authors = [ "David Calavera ", - "Harold Sun " + "Harold Sun ", ] description = "AWS Lambda Runtime interaction API" license = "Apache-2.0" @@ -14,7 +14,19 @@ keywords = ["AWS", "Lambda", "API"] readme = "README.md" [dependencies] -http = "0.2" -hyper = { version = "0.14.20", features = ["http1", "client", "stream", "tcp"] } -tower-service = "0.3" +bytes = { workspace = true } +futures-channel = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true, features = ["http1", "client"] } +hyper-util = { workspace = true, features = [ + "client", + "client-legacy", + "http1", + "tokio", +] } +tower = { workspace = true, features = ["util"] } +tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs new file mode 100644 index 00000000..815de5f2 --- /dev/null +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -0,0 +1,110 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; + +use crate::body::{sender, watch}; +use bytes::Bytes; +use futures_channel::mpsc; +use futures_channel::oneshot; +use futures_util::{stream::FusedStream, Future, Stream}; +use http::HeaderMap; +use http_body::Body; +use http_body::Frame; +use http_body::SizeHint; +pub use sender::Sender; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) struct DecodedLength(u64); + +impl DecodedLength { + pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX); + pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1); + pub(crate) const ZERO: DecodedLength = DecodedLength(0); + + pub(crate) fn sub_if(&mut self, amt: u64) { + match *self { + DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (), + DecodedLength(ref mut known) => { + *known -= amt; + } + } + } + + /// Converts to an Option representing a Known or Unknown length. + pub(crate) fn into_opt(self) -> Option { + match self { + DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None, + DecodedLength(known) => Some(known), + } + } +} + +pub struct ChannelBody { + content_length: DecodedLength, + want_tx: watch::Sender, + data_rx: mpsc::Receiver>, + trailers_rx: oneshot::Receiver, +} + +pub fn channel() -> (Sender, ChannelBody) { + let (data_tx, data_rx) = mpsc::channel(0); + let (trailers_tx, trailers_rx) = oneshot::channel(); + + let (want_tx, want_rx) = watch::channel(sender::WANT_READY); + + let tx = Sender { + want_rx, + data_tx, + trailers_tx: Some(trailers_tx), + }; + let rx = ChannelBody { + content_length: DecodedLength::CHUNKED, + want_tx, + data_rx, + trailers_rx, + }; + + (tx, rx) +} + +impl Body for ChannelBody { + type Data = Bytes; + type Error = crate::Error; + + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + self.want_tx.send(sender::WANT_READY); + + if !self.data_rx.is_terminated() { + if let Some(chunk) = ready!(Pin::new(&mut self.data_rx).poll_next(cx)?) { + self.content_length.sub_if(chunk.len() as u64); + return Poll::Ready(Some(Ok(Frame::data(chunk)))); + } + } + + // check trailers after data is terminated + match ready!(Pin::new(&mut self.trailers_rx).poll(cx)) { + Ok(t) => Poll::Ready(Some(Ok(Frame::trailers(t)))), + Err(_) => Poll::Ready(None), + } + } + + fn is_end_stream(&self) -> bool { + self.content_length == DecodedLength::ZERO + } + + fn size_hint(&self) -> SizeHint { + let mut hint = SizeHint::default(); + + if let Some(content_length) = self.content_length.into_opt() { + hint.set_exact(content_length); + } + + hint + } +} diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs new file mode 100644 index 00000000..7e2d597c --- /dev/null +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -0,0 +1,143 @@ +//! HTTP body utilities. Extracted from Axum under MIT license. +//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE + +use crate::{BoxError, Error}; +use bytes::Bytes; +use futures_util::stream::Stream; +use http_body::{Body as _, Frame}; +use http_body_util::{BodyExt, Collected}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use self::channel::Sender; + +macro_rules! ready { + ($e:expr) => { + match $e { + std::task::Poll::Ready(v) => v, + std::task::Poll::Pending => return std::task::Poll::Pending, + } + }; +} + +mod channel; +pub mod sender; +mod watch; + +type BoxBody = http_body_util::combinators::UnsyncBoxBody; + +fn boxed(body: B) -> BoxBody +where + B: http_body::Body + Send + 'static, + B::Error: Into, +{ + try_downcast(body).unwrap_or_else(|body| body.map_err(Error::new).boxed_unsync()) +} + +pub(crate) fn try_downcast(k: K) -> Result +where + T: 'static, + K: Send + 'static, +{ + let mut k = Some(k); + if let Some(k) = ::downcast_mut::>(&mut k) { + Ok(k.take().unwrap()) + } else { + Err(k.unwrap()) + } +} + +/// The body type used in axum requests and responses. +#[derive(Debug)] +pub struct Body(BoxBody); + +impl Body { + /// Create a new `Body` that wraps another [`http_body::Body`]. + pub fn new(body: B) -> Self + where + B: http_body::Body + Send + 'static, + B::Error: Into, + { + try_downcast(body).unwrap_or_else(|body| Self(boxed(body))) + } + + /// Create an empty body. + pub fn empty() -> Self { + Self::new(http_body_util::Empty::new()) + } + + /// Create a new `Body` stream with associated Sender half. + pub fn channel() -> (Sender, Body) { + let (sender, body) = channel::channel(); + (sender, Body::new(body)) + } + + /// Collect the body into `Bytes` + pub async fn collect(self) -> Result, Error> { + self.0.collect().await + } +} + +impl Default for Body { + fn default() -> Self { + Self::empty() + } +} + +macro_rules! body_from_impl { + ($ty:ty) => { + impl From<$ty> for Body { + fn from(buf: $ty) -> Self { + Self::new(http_body_util::Full::from(buf)) + } + } + }; +} + +body_from_impl!(&'static [u8]); +body_from_impl!(std::borrow::Cow<'static, [u8]>); +body_from_impl!(Vec); + +body_from_impl!(&'static str); +body_from_impl!(std::borrow::Cow<'static, str>); +body_from_impl!(String); + +body_from_impl!(Bytes); + +impl http_body::Body for Body { + type Data = Bytes; + type Error = Error; + + #[inline] + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Pin::new(&mut self.0).poll_frame(cx) + } + + #[inline] + fn size_hint(&self) -> http_body::SizeHint { + self.0.size_hint() + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } +} + +impl Stream for Body { + type Item = Result; + + #[inline] + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match futures_util::ready!(Pin::new(&mut self).poll_frame(cx)?) { + Some(frame) => match frame.into_data() { + Ok(data) => Poll::Ready(Some(Ok(data))), + Err(_frame) => Poll::Ready(None), + }, + None => Poll::Ready(None), + } + } +} diff --git a/lambda-runtime-api-client/src/body/sender.rs b/lambda-runtime-api-client/src/body/sender.rs new file mode 100644 index 00000000..0e008454 --- /dev/null +++ b/lambda-runtime-api-client/src/body/sender.rs @@ -0,0 +1,135 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +use crate::Error; +use std::task::{Context, Poll}; + +use bytes::Bytes; +use futures_channel::{mpsc, oneshot}; +use http::HeaderMap; + +use super::watch; + +type BodySender = mpsc::Sender>; +type TrailersSender = oneshot::Sender; + +pub(crate) const WANT_PENDING: usize = 1; +pub(crate) const WANT_READY: usize = 2; + +/// A sender half created through [`Body::channel()`]. +/// +/// Useful when wanting to stream chunks from another thread. +/// +/// ## Body Closing +/// +/// Note that the request body will always be closed normally when the sender is dropped (meaning +/// that the empty terminating chunk will be sent to the remote). If you desire to close the +/// connection with an incomplete response (e.g. in the case of an error during asynchronous +/// processing), call the [`Sender::abort()`] method to abort the body in an abnormal fashion. +/// +/// [`Body::channel()`]: struct.Body.html#method.channel +/// [`Sender::abort()`]: struct.Sender.html#method.abort +#[must_use = "Sender does nothing unless sent on"] +pub struct Sender { + pub(crate) want_rx: watch::Receiver, + pub(crate) data_tx: BodySender, + pub(crate) trailers_tx: Option, +} + +impl Sender { + /// Check to see if this `Sender` can send more data. + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + // Check if the receiver end has tried polling for the body yet + ready!(self.poll_want(cx)?); + self.data_tx + .poll_ready(cx) + .map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + fn poll_want(&mut self, cx: &mut Context<'_>) -> Poll> { + match self.want_rx.load(cx) { + WANT_READY => Poll::Ready(Ok(())), + WANT_PENDING => Poll::Pending, + watch::CLOSED => Poll::Ready(Err(Error::new(SenderError::ChannelClosed))), + unexpected => unreachable!("want_rx value: {}", unexpected), + } + } + + async fn ready(&mut self) -> Result<(), Error> { + futures_util::future::poll_fn(|cx| self.poll_ready(cx)).await + } + + /// Send data on data channel when it is ready. + #[allow(unused)] + pub async fn send_data(&mut self, chunk: Bytes) -> Result<(), Error> { + self.ready().await?; + self.data_tx + .try_send(Ok(chunk)) + .map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + /// Send trailers on trailers channel. + #[allow(unused)] + pub async fn send_trailers(&mut self, trailers: HeaderMap) -> Result<(), Error> { + let tx = match self.trailers_tx.take() { + Some(tx) => tx, + None => return Err(Error::new(SenderError::ChannelClosed)), + }; + tx.send(trailers).map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + /// Try to send data on this channel. + /// + /// # Errors + /// + /// Returns `Err(Bytes)` if the channel could not (currently) accept + /// another `Bytes`. + /// + /// # Note + /// + /// This is mostly useful for when trying to send from some other thread + /// that doesn't have an async context. If in an async context, prefer + /// `send_data()` instead. + pub fn try_send_data(&mut self, chunk: Bytes) -> Result<(), Bytes> { + self.data_tx + .try_send(Ok(chunk)) + .map_err(|err| err.into_inner().expect("just sent Ok")) + } + + /// Send a `SenderError::BodyWriteAborted` error and terminate the stream. + #[allow(unused)] + pub fn abort(mut self) { + self.send_error(Error::new(SenderError::BodyWriteAborted)); + } + + /// Terminate the stream with an error. + pub fn send_error(&mut self, err: Error) { + let _ = self + .data_tx + // clone so the send works even if buffer is full + .clone() + .try_send(Err(err)); + } +} + +#[derive(Debug)] +enum SenderError { + ChannelClosed, + BodyWriteAborted, +} + +impl SenderError { + fn description(&self) -> &str { + match self { + SenderError::BodyWriteAborted => "user body write aborted", + SenderError::ChannelClosed => "channel closed", + } + } +} + +impl std::fmt::Display for SenderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.description()) + } +} +impl std::error::Error for SenderError {} diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs new file mode 100644 index 00000000..ac0bd4ee --- /dev/null +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -0,0 +1,69 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +//! An SPSC broadcast channel. +//! +//! - The value can only be a `usize`. +//! - The consumer is only notified if the value is different. +//! - The value `0` is reserved for closed. + +use futures_util::task::AtomicWaker; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; +use std::task; + +type Value = usize; + +pub(crate) const CLOSED: usize = 0; + +pub(crate) fn channel(initial: Value) -> (Sender, Receiver) { + debug_assert!(initial != CLOSED, "watch::channel initial state of 0 is reserved"); + + let shared = Arc::new(Shared { + value: AtomicUsize::new(initial), + waker: AtomicWaker::new(), + }); + + (Sender { shared: shared.clone() }, Receiver { shared }) +} + +pub(crate) struct Sender { + shared: Arc, +} + +pub(crate) struct Receiver { + shared: Arc, +} + +struct Shared { + value: AtomicUsize, + waker: AtomicWaker, +} + +impl Sender { + pub(crate) fn send(&mut self, value: Value) { + if self.shared.value.swap(value, Ordering::SeqCst) != value { + self.shared.waker.wake(); + } + } +} + +impl Drop for Sender { + fn drop(&mut self) { + self.send(CLOSED); + } +} + +impl Receiver { + pub(crate) fn load(&mut self, cx: &mut task::Context<'_>) -> Value { + self.shared.waker.register(cx.waker()); + self.shared.value.load(Ordering::SeqCst) + } + + #[allow(unused)] + pub(crate) fn peek(&self) -> Value { + self.shared.value.load(Ordering::Relaxed) + } +} diff --git a/lambda-runtime-api-client/src/error.rs b/lambda-runtime-api-client/src/error.rs new file mode 100644 index 00000000..dbb87b64 --- /dev/null +++ b/lambda-runtime-api-client/src/error.rs @@ -0,0 +1,33 @@ +//! Extracted from Axum under MIT license. +//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +use std::{error::Error as StdError, fmt}; +pub use tower::BoxError; +/// Errors that can happen when using axum. +#[derive(Debug)] +pub struct Error { + inner: BoxError, +} + +impl Error { + /// Create a new `Error` from a boxable error. + pub fn new(error: impl Into) -> Self { + Self { inner: error.into() } + } + + /// Convert an `Error` back into the underlying boxed trait object. + pub fn into_inner(self) -> BoxError { + self.inner + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&*self.inner) + } +} diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 4b082aba..15185f81 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -5,20 +5,18 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; -use hyper::{ - client::{connect::Connection, HttpConnector}, - Body, -}; +use hyper::body::Incoming; +use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; use std::{convert::TryInto, fmt::Debug}; -use tokio::io::{AsyncRead, AsyncWrite}; use tower_service::Service; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); const CUSTOM_USER_AGENT: Option<&str> = option_env!("LAMBDA_RUNTIME_USER_AGENT"); -/// Error type that lambdas may result in -pub type Error = Box; +mod error; +pub use error::*; +pub mod body; /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] @@ -26,7 +24,7 @@ pub struct Client { /// The runtime API URI pub base: Uri, /// The client that manages the API connections - pub client: hyper::Client, + pub client: hyper_util::client::legacy::Client, } impl Client { @@ -41,25 +39,24 @@ impl Client { impl Client where - C: hyper::client::connect::Connect + Sync + Send + Clone + 'static, + C: Connect + Sync + Send + Clone + 'static, { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. - pub async fn call(&self, req: Request) -> Result, Error> { + pub async fn call(&self, req: Request) -> Result, BoxError> { let req = self.set_origin(req)?; - let response = self.client.request(req).await?; - Ok(response) + self.client.request(req).await.map_err(Into::into) } /// Create a new client with a given base URI and HTTP connector. pub fn with(base: Uri, connector: C) -> Self { - let client = hyper::Client::builder() + let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .http1_max_buf_size(1024 * 1024) .build(connector); Self { base, client } } - fn set_origin(&self, req: Request) -> Result, Error> { + fn set_origin(&self, req: Request) -> Result, BoxError> { let (mut parts, body) = req.into_parts(); let (scheme, authority, base_path) = { let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP); @@ -83,7 +80,7 @@ where } /// Builder implementation to construct any Runtime API clients. -pub struct ClientBuilder = hyper::client::HttpConnector> { +pub struct ClientBuilder = HttpConnector> { connector: C, uri: Option, } @@ -93,7 +90,7 @@ where C: Service + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { /// Create a new builder with a given HTTP connector. pub fn with_connector(self, connector: C2) -> ClientBuilder @@ -101,7 +98,7 @@ where C2: Service + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { ClientBuilder { connector, @@ -116,7 +113,10 @@ where } /// Create the new client to interact with the Runtime API. - pub fn build(self) -> Result, Error> { + pub fn build(self) -> Result, Error> + where + C: Connect + Sync + Send + Clone + 'static, + { let uri = match self.uri { Some(uri) => uri, None => { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 335b5482..8a579d99 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -18,30 +18,45 @@ default = ["simulated"] simulated = [] [dependencies] +async-stream = "0.3" +base64 = { workspace = true } +bytes = { workspace = true } +futures = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +http-serde = { workspace = true } +hyper = { workspace = true, features = [ + "http1", + "client", +] } +hyper-util = { workspace = true, features = [ + "client", + "client-legacy", + "http1", + "tokio", +] } +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +serde = { version = "1", features = ["derive", "rc"] } +serde_json = "^1" +serde_path_to_error = "0.1.11" tokio = { version = "1.0", features = [ "macros", "io-util", "sync", "rt-multi-thread", ] } -# Hyper requires the `server` feature to work on nightly -hyper = { version = "0.14.20", features = [ - "http1", +tokio-stream = "0.1.2" +tower = { workspace = true, features = ["util"] } +tracing = { version = "0.1", features = ["log"] } + +[dev-dependencies] +hyper-util = { workspace = true, features = [ "client", - "stream", + "client-legacy", + "http1", "server", + "server-auto", + "tokio", ] } -futures = "0.3" -serde = { version = "1", features = ["derive", "rc"] } -serde_json = "^1" -bytes = "1.0" -http = "0.2" -async-stream = "0.3" -tracing = { version = "0.1.37", features = ["log"] } -tower = { version = "0.4", features = ["util"] } -tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } -serde_path_to_error = "0.1.11" -http-serde = "1.1.3" -base64 = "0.21.0" -http-body = "0.4" +pin-project-lite = { workspace = true } \ No newline at end of file diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 49add76c..10dfce92 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -9,22 +9,18 @@ //! and runs the Lambda runtime. use bytes::Bytes; use futures::FutureExt; -use hyper::{ - client::{connect::Connection, HttpConnector}, - http::Request, - Body, -}; -use lambda_runtime_api_client::Client; +use http_body_util::BodyExt; +use hyper::{body::Incoming, http::Request}; +use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; +use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ env, fmt::{self, Debug, Display}, future::Future, - marker::PhantomData, panic, sync::Arc, }; -use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; @@ -34,6 +30,8 @@ mod deserializer; mod requests; #[cfg(test)] mod simulated; +/// Utilities for Lambda Streaming functions. +pub mod streaming; /// Types available to a Lambda function. mod types; @@ -43,7 +41,7 @@ pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, Me use types::invoke_request_id; /// Error type that lambdas may result in -pub type Error = lambda_runtime_api_client::Error; +pub type Error = lambda_runtime_api_client::BoxError; /// Configuration derived from environment variables. #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -94,16 +92,16 @@ struct Runtime = HttpConnector> { impl Runtime where - C: Service + Clone + Send + Sync + Unpin + 'static, + C: Service + Connect + Clone + Send + Sync + Unpin + 'static, C::Future: Unpin + Send, C::Error: Into>, - C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + C::Response: Connection + Unpin + Send + 'static, { async fn run( &self, - incoming: impl Stream, Error>> + Send, + incoming: impl Stream, Error>> + Send, mut handler: F, - ) -> Result<(), Error> + ) -> Result<(), BoxError> where F: Service>, F::Future: Future>, @@ -136,7 +134,7 @@ where // Group the handling in one future and instrument it with the span async { - let body = hyper::body::to_bytes(body).await?; + let body = body.collect().await?.to_bytes(); trace!("response body - {}", std::str::from_utf8(&body)?); #[cfg(debug_assertions)] @@ -169,13 +167,7 @@ where Ok(response) => match response { Ok(response) => { trace!("Ok response from handler (run loop)"); - EventCompletionRequest { - request_id, - body: response, - _unused_b: PhantomData, - _unused_s: PhantomData, - } - .into_req() + EventCompletionRequest::new(request_id, response).into_req() } Err(err) => build_event_error_request(request_id, err), }, @@ -204,12 +196,12 @@ where } } -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ +fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ where - C: Service + Clone + Send + Sync + Unpin + 'static, + C: Service + Connect + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { async_stream::stream! { loop { @@ -293,20 +285,23 @@ mod endpoint_tests { }; use futures::future::BoxFuture; use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; - use hyper::{server::conn::Http, service::service_fn, Body}; - use lambda_runtime_api_client::Client; + use hyper::body::Incoming; + use hyper::rt::{Read, Write}; + use hyper::service::service_fn; + + use hyper_util::server::conn::auto::Builder; + use lambda_runtime_api_client::{body::Body, Client}; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, marker::PhantomData, sync::Arc}; + use std::{convert::TryFrom, env, sync::Arc}; use tokio::{ - io::{self, AsyncRead, AsyncWrite}, - select, + io, select, sync::{self, oneshot}, }; use tokio_stream::StreamExt; #[cfg(test)] - async fn next_event(req: &Request) -> Result, Error> { + async fn next_event(req: &Request) -> Result, Error> { let path = "/2018-06-01/runtime/invocation/next"; assert_eq!(req.method(), Method::GET); assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); @@ -323,7 +318,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn complete_event(req: &Request, id: &str) -> Result, Error> { + async fn complete_event(req: &Request, id: &str) -> Result, Error> { assert_eq!(Method::POST, req.method()); let rsp = Response::builder() .status(StatusCode::ACCEPTED) @@ -337,7 +332,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn event_err(req: &Request, id: &str) -> Result, Error> { + async fn event_err(req: &Request, id: &str) -> Result, Error> { let expected = format!("/2018-06-01/runtime/invocation/{id}/error"); assert_eq!(expected, req.uri().path()); @@ -351,7 +346,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn handle_incoming(req: Request) -> Result, Error> { + async fn handle_incoming(req: Request) -> Result, Error> { let path: Vec<&str> = req .uri() .path_and_query() @@ -369,11 +364,14 @@ mod endpoint_tests { } #[cfg(test)] - async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> + async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), Error> where - I: AsyncRead + AsyncWrite + Unpin + 'static, + I: Read + Write + Unpin + 'static, { - let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); + use hyper_util::rt::TokioExecutor; + + let builder = Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection(io, service_fn(handle_incoming)); select! { _ = rx => { Ok(()) @@ -396,7 +394,9 @@ mod endpoint_tests { let (tx, rx) = sync::oneshot::channel(); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -425,18 +425,15 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let client = Client::with(base, conn); - let req = EventCompletionRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - body: "done", - _unused_b: PhantomData::<&str>, - _unused_s: PhantomData::, - }; + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "done"); let req = req.into_req()?; let rsp = client.call(req).await?; @@ -458,7 +455,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -491,7 +490,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -554,7 +555,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 8e72fc2d..c9274cf4 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -3,8 +3,7 @@ use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; use http::{Method, Request, Response, Uri}; -use hyper::Body; -use lambda_runtime_api_client::build_request; +use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; use std::fmt::Debug; use std::marker::PhantomData; @@ -28,7 +27,7 @@ impl IntoRequest for NextEventRequest { let req = build_request() .method(Method::GET) .uri(Uri::from_static("/2018-06-01/runtime/invocation/next")) - .body(Body::empty())?; + .body(Default::default())?; Ok(req) } } @@ -49,6 +48,7 @@ pub struct NextEventResponse<'a> { impl<'a> IntoResponse for NextEventResponse<'a> { fn into_rsp(self) -> Result, Error> { + // let body: BoxyBody< = BoxBody::new(); let rsp = Response::builder() .header("lambda-runtime-aws-request-id", self.request_id) .header("lambda-runtime-deadline-ms", self.deadline) @@ -85,6 +85,25 @@ where pub(crate) _unused_s: PhantomData, } +impl<'a, R, B, D, E, S> EventCompletionRequest<'a, R, B, S, D, E> +where + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + /// Initialize a new EventCompletionRequest + pub(crate) fn new(request_id: &'a str, body: R) -> EventCompletionRequest<'a, R, B, S, D, E> { + EventCompletionRequest { + request_id, + body, + _unused_b: PhantomData::, + _unused_s: PhantomData::, + } + } +} + impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> where R: IntoFunctionResponse, @@ -157,12 +176,7 @@ where #[test] fn test_event_completion_request() { - let req = EventCompletionRequest { - request_id: "id", - body: "hello, world!", - _unused_b: PhantomData::<&str>, - _unused_s: PhantomData::, - }; + let req = EventCompletionRequest::new("id", "hello, world!"); let req = req.into_req().unwrap(); let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/response"); assert_eq!(req.method(), Method::POST); diff --git a/lambda-runtime/src/simulated.rs b/lambda-runtime/src/simulated.rs index f6a06bca..018664fe 100644 --- a/lambda-runtime/src/simulated.rs +++ b/lambda-runtime/src/simulated.rs @@ -1,14 +1,15 @@ use http::Uri; -use hyper::client::connect::Connection; +use hyper::rt::{Read, Write}; +use hyper_util::client::legacy::connect::{Connected, Connection}; +use pin_project_lite::pin_project; use std::{ collections::HashMap, future::Future, - io::Result as IoResult, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, }; -use tokio::io::{AsyncRead, AsyncWrite, DuplexStream, ReadBuf}; +use tokio::io::DuplexStream; use crate::Error; @@ -17,11 +18,16 @@ pub struct Connector { inner: Arc>>, } -pub struct DuplexStreamWrapper(DuplexStream); +pin_project! { +pub struct DuplexStreamWrapper { + #[pin] + inner: DuplexStream, +} +} impl DuplexStreamWrapper { - pub(crate) fn new(stream: DuplexStream) -> DuplexStreamWrapper { - DuplexStreamWrapper(stream) + pub(crate) fn new(inner: DuplexStream) -> DuplexStreamWrapper { + DuplexStreamWrapper { inner } } } @@ -53,16 +59,12 @@ impl Connector { } } -impl hyper::service::Service for Connector { +impl tower::Service for Connector { type Response = DuplexStreamWrapper; type Error = crate::Error; #[allow(clippy::type_complexity)] type Future = Pin> + Send>>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, uri: Uri) -> Self::Future { let res = match self.inner.lock() { Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), @@ -71,30 +73,61 @@ impl hyper::service::Service for Connector { }; Box::pin(async move { res }) } + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } } impl Connection for DuplexStreamWrapper { - fn connected(&self) -> hyper::client::connect::Connected { - hyper::client::connect::Connected::new() + fn connected(&self) -> Connected { + Connected::new() } } -impl AsyncRead for DuplexStreamWrapper { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - Pin::new(&mut self.0).poll_read(cx, buf) +impl Read for DuplexStreamWrapper { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + mut buf: hyper::rt::ReadBufCursor<'_>, + ) -> Poll> { + let n = unsafe { + let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut()); + match tokio::io::AsyncRead::poll_read(self.project().inner, cx, &mut tbuf) { + Poll::Ready(Ok(())) => tbuf.filled().len(), + other => return other, + } + }; + + unsafe { + buf.advance(n); + } + Poll::Ready(Ok(())) } } -impl AsyncWrite for DuplexStreamWrapper { - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - Pin::new(&mut self.0).poll_write(cx, buf) +impl Write for DuplexStreamWrapper { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + tokio::io::AsyncWrite::poll_write(self.project().inner, cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_flush(self.project().inner, cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_flush(cx) + fn is_write_vectored(&self) -> bool { + tokio::io::AsyncWrite::is_write_vectored(&self.inner) } - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_shutdown(cx) + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + tokio::io::AsyncWrite::poll_write_vectored(self.project().inner, cx, bufs) } } diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs new file mode 100644 index 00000000..4f0c8083 --- /dev/null +++ b/lambda-runtime/src/streaming.rs @@ -0,0 +1,35 @@ +pub use lambda_runtime_api_client::body::{sender::Sender, Body}; + +pub use crate::types::StreamResponse as Response; + +/// Create a new `Body` stream with associated Sender half. +/// +/// Examples +/// +/// ``` +/// use lambda_runtime::{ +/// streaming::{channel, Body, Response}, +/// Error, LambdaEvent, +/// }; +/// use std::{thread, time::Duration}; +/// +/// async fn func(_event: LambdaEvent) -> Result, Error> { +/// let messages = vec!["Hello", "world", "from", "Lambda!"]; +/// +/// let (mut tx, rx) = channel(); +/// +/// tokio::spawn(async move { +/// for message in messages.iter() { +/// tx.send_data((message.to_string() + "\n").into()).await.unwrap(); +/// thread::sleep(Duration::from_millis(500)); +/// } +/// }); +/// +/// Ok(Response::from(rx)) +/// } +/// ``` +#[allow(unused)] +#[inline] +pub fn channel() -> (Sender, Body) { + Body::channel() +} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 8b70ce80..f2a36073 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -2,6 +2,7 @@ use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; +use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -251,11 +252,11 @@ impl IntoFunctionResponse for FunctionResponse { } } -impl IntoFunctionResponse for B +impl IntoFunctionResponse for B where B: Serialize, { - fn into_response(self) -> FunctionResponse { + fn into_response(self) -> FunctionResponse { FunctionResponse::BufferedResponse(self) } } @@ -271,6 +272,20 @@ where } } +impl From for StreamResponse +where + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + fn from(value: S) -> Self { + StreamResponse { + metadata_prelude: Default::default(), + stream: value, + } + } +} + #[cfg(test)] mod test { use super::*; From 953b2d2dae2e2e16c3143b992bc6640e35e6625d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Dec 2023 11:37:24 -0800 Subject: [PATCH 237/394] Fix EventBridge event structures. (#755) According to the docs, the detail is always a JSON object: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html Signed-off-by: David Calavera --- lambda-events/src/event/eventbridge/mod.rs | 59 +++++-------------- .../example-eventbridge-event-obj.json | 18 +++--- .../fixtures/example-eventbridge-event.json | 13 ---- 3 files changed, 24 insertions(+), 66 deletions(-) delete mode 100644 lambda-events/src/fixtures/example-eventbridge-event.json diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 7809f1e2..ed9bf447 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,34 +1,15 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct EventBridgeEvent { - #[serde(default)] - pub version: Option, - #[serde(default)] - pub id: Option, - pub detail_type: String, - pub source: String, - #[serde(default)] - pub account: Option, - #[serde(default)] - pub time: Option>, - #[serde(default)] - pub region: Option, - #[serde(default)] - pub resources: Option>, - #[serde(default)] - pub detail: Option, -} - -#[serde_with::serde_as] +/// Parse EventBridge events. +/// Deserialize the event detail into a structure that's `DeserializeOwned`. +/// +/// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] -pub struct EventBridgeEventObj { +pub struct EventBridgeEvent { #[serde(default)] pub version: Option, #[serde(default)] @@ -43,7 +24,6 @@ pub struct EventBridgeEventObj { pub region: Option, #[serde(default)] pub resources: Option>, - #[serde_as(as = "serde_with::json::JsonString")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub detail: T, } @@ -53,35 +33,24 @@ pub struct EventBridgeEventObj { mod test { use super::*; - use serde_json; - #[test] fn example_eventbridge_obj_event() { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - struct CustomStruct { - a: String, - b: String, + #[serde(rename_all = "kebab-case")] + struct Ec2StateChange { + instance_id: String, + state: String, } + // Example from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instance-state-changes.html let data = include_bytes!("../../fixtures/example-eventbridge-event-obj.json"); - let parsed: EventBridgeEventObj = serde_json::from_slice(data).unwrap(); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); - assert_eq!(parsed.detail.a, "123"); - assert_eq!(parsed.detail.b, "456"); - - let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: EventBridgeEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); - assert_eq!(parsed, reparsed); - } - - #[test] - fn example_eventbridge_event() { - let data = include_bytes!("../../fixtures/example-eventbridge-event.json"); - let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); - assert_eq!(parsed.detail, Some(String::from("String Message"))); + assert_eq!("i-abcd1111", parsed.detail.instance_id); + assert_eq!("pending", parsed.detail.state); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } diff --git a/lambda-events/src/fixtures/example-eventbridge-event-obj.json b/lambda-events/src/fixtures/example-eventbridge-event-obj.json index 97c5e0ae..e9b26968 100644 --- a/lambda-events/src/fixtures/example-eventbridge-event-obj.json +++ b/lambda-events/src/fixtures/example-eventbridge-event-obj.json @@ -1,13 +1,15 @@ { - "version": "0", - "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "id": "7bf73129-1428-4cd3-a780-95db273d1602", "detail-type": "EC2 Instance State-change Notification", "source": "aws.ec2", - "account": "111122223333", - "time": "2017-12-22T18:43:48Z", - "region": "us-west-1", + "account": "123456789012", + "time": "2021-11-11T21:29:54Z", + "region": "us-east-1", "resources": [ - "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" ], - "detail": "{\"a\":\"123\",\"b\":\"456\"}" -} + "detail": { + "instance-id": "i-abcd1111", + "state": "pending" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-eventbridge-event.json b/lambda-events/src/fixtures/example-eventbridge-event.json deleted file mode 100644 index 793ca8dc..00000000 --- a/lambda-events/src/fixtures/example-eventbridge-event.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "0", - "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", - "detail-type": "EC2 Instance State-change Notification", - "source": "aws.ec2", - "account": "111122223333", - "time": "2017-12-22T18:43:48Z", - "region": "us-west-1", - "resources": [ - "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" - ], - "detail": "String Message" -} From ead7f3754258b00d929444762dc44db7dba3de92 Mon Sep 17 00:00:00 2001 From: Titus <8200809+nismotie@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:02:23 +0000 Subject: [PATCH 238/394] Implement Serialize for lambda telemetry (#759) * Add serialize testing mod and macro * Use and derive serialize * Skip serialize if None on Options * Add unit tests for serialization --- lambda-extension/src/telemetry.rs | 231 ++++++++++++++++++++++++++++-- 1 file changed, 216 insertions(+), 15 deletions(-) diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index 1e83ee8e..cfb4dde2 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -3,14 +3,14 @@ use http::{Request, Response}; use http_body_util::BodyExt; use hyper::body::Incoming; use lambda_runtime_api_client::body::Body; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{boxed::Box, fmt, sync::Arc}; use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; /// Payload received from the Telemetry API -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct LambdaTelemetry { /// Time when the telemetry was generated pub time: DateTime, @@ -20,7 +20,7 @@ pub struct LambdaTelemetry { } /// Record in a LambdaTelemetry entry -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(tag = "type", content = "record", rename_all = "lowercase")] pub enum LambdaTelemetryRecord { /// Function log records @@ -37,8 +37,10 @@ pub enum LambdaTelemetryRecord { /// Phase of initialisation phase: InitPhase, /// Lambda runtime version + #[serde(skip_serializing_if = "Option::is_none")] runtime_version: Option, /// Lambda runtime version ARN + #[serde(skip_serializing_if = "Option::is_none")] runtime_version_arn: Option, }, /// Platform init runtime done record @@ -47,10 +49,12 @@ pub enum LambdaTelemetryRecord { /// Type of initialization initialization_type: InitType, /// Phase of initialisation + #[serde(skip_serializing_if = "Option::is_none")] phase: Option, /// Status of initalization status: Status, /// When the status = failure, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Spans #[serde(default)] @@ -75,8 +79,10 @@ pub enum LambdaTelemetryRecord { /// Request identifier request_id: String, /// Version of the Lambda function + #[serde(skip_serializing_if = "Option::is_none")] version: Option, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, /// Record marking the completion of an invocation @@ -87,13 +93,16 @@ pub enum LambdaTelemetryRecord { /// Status of the invocation status: Status, /// When unsuccessful, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Metrics corresponding to the runtime + #[serde(skip_serializing_if = "Option::is_none")] metrics: Option, /// Spans #[serde(default)] spans: Vec, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, /// Platfor report record @@ -104,6 +113,7 @@ pub enum LambdaTelemetryRecord { /// Status of the invocation status: Status, /// When unsuccessful, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Metrics metrics: ReportMetrics, @@ -111,6 +121,7 @@ pub enum LambdaTelemetryRecord { #[serde(default)] spans: Vec, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, @@ -147,7 +158,7 @@ pub enum LambdaTelemetryRecord { } /// Type of Initialization -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum InitType { /// Initialised on demand @@ -159,7 +170,7 @@ pub enum InitType { } /// Phase in which initialization occurs -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum InitPhase { /// Initialization phase @@ -169,7 +180,7 @@ pub enum InitPhase { } /// Status of invocation/initialization -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Status { /// Success @@ -183,7 +194,7 @@ pub enum Status { } /// Span -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Span { /// Duration of the span @@ -195,7 +206,7 @@ pub struct Span { } /// Tracing Context -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct TraceContext { /// Span ID @@ -207,7 +218,7 @@ pub struct TraceContext { } /// Type of tracing -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub enum TracingType { /// Amazon trace type #[serde(rename = "X-Amzn-Trace-Id")] @@ -215,7 +226,7 @@ pub enum TracingType { } ///Init report metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct InitReportMetrics { /// Duration of initialization @@ -223,7 +234,7 @@ pub struct InitReportMetrics { } /// Report metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ReportMetrics { /// Duration in milliseconds @@ -237,15 +248,15 @@ pub struct ReportMetrics { #[serde(rename = "maxMemoryUsedMB")] pub max_memory_used_mb: u64, /// Init duration in case of a cold start - #[serde(default = "Option::default")] + #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")] pub init_duration_ms: Option, /// Restore duration in milliseconds - #[serde(default = "Option::default")] + #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")] pub restore_duration_ms: Option, } /// Runtime done metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct RuntimeDoneMetrics { /// Duration in milliseconds @@ -303,7 +314,7 @@ where } #[cfg(test)] -mod tests { +mod deserialization_tests { use super::*; use chrono::{Duration, TimeZone}; @@ -459,3 +470,193 @@ mod tests { ), } } + +#[cfg(test)] +mod serialization_tests { + use chrono::{Duration, TimeZone}; + + use super::*; + macro_rules! serialize_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let actual = serde_json::to_string(&input).expect("unable to serialize"); + println!("Input: {:?}\n", input); + println!("Expected:\n {:?}\n", expected); + println!("Actual:\n {:?}\n", actual); + + assert!(actual == expected); + } + )* + } + } + + serialize_tests! { + // function + function: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::Function("hello world".to_string()), + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"function","record":"hello world"}"#, + ), + // extension + extension: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::Extension("hello world".to_string()), + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"extension","record":"hello world"}"#, + ), + //platform.Start + platform_start: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformStart { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + version: Some("$LATEST".to_string()), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + } + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.start","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","version":"$LATEST","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.initStart + platform_init_start: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitStart { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + runtime_version: None, + runtime_version_arn: None, + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#, + ), + // platform.runtimeDone + platform_runtime_done: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformRuntimeDone { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: Some(RuntimeDoneMetrics { + duration_ms: 2599.0, + produced_bytes: Some(8), + }), + spans: vec!( + Span { + name:"responseLatency".to_string(), + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) + .unwrap() + .checked_add_signed(Duration::milliseconds(165)) + .unwrap(), + duration_ms: 2598.0 + }, + Span { + name:"responseDuration".to_string(), + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) + .unwrap() + .checked_add_signed(Duration::milliseconds(763)) + .unwrap(), + duration_ms: 0.0 + }, + ), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.runtimeDone","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.0,"producedBytes":8},"spans":[{"durationMs":2598.0,"name":"responseLatency","start":"2022-10-21T14:05:03.165Z"},{"durationMs":0.0,"name":"responseDuration","start":"2022-10-21T14:05:05.763Z"}],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.report + platform_report: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformReport { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: ReportMetrics { + duration_ms: 2599.4, + billed_duration_ms: 2600, + memory_size_mb:128, + max_memory_used_mb:94, + init_duration_ms: Some(549.04), + restore_duration_ms: None, + }, + spans: Vec::new(), + tracing: Some(TraceContext { + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.report","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.4,"billedDurationMs":2600,"memorySizeMB":128,"maxMemoryUsedMB":94,"initDurationMs":549.04},"spans":[],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.telemetrySubscription + platform_telemetry_subscription: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformTelemetrySubscription { + name: "my-extension".to_string(), + state: "Subscribed".to_string(), + types: vec!("platform".to_string(), "function".to_string()), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#, + ), + // platform.initRuntimeDone + platform_init_runtime_done: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitRuntimeDone { + initialization_type: InitType::OnDemand, + status: Status::Success, + phase: None, + error_type: None, + spans: Vec::new(), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success","spans":[]}}"#, + ), + // platform.extension + platform_extension: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformExtension { + name: "my-extension".to_string(), + state: "Ready".to_string(), + events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#, + ), + // platform.initReport + platform_init_report: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitReport { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + metrics: InitReportMetrics { duration_ms: 500.0 }, + spans: Vec::new(), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initReport","record":{"initializationType":"on-demand","phase":"init","metrics":{"durationMs":500.0},"spans":[]}}"#, + ), + + } +} From ab3b809d63fad05369596d17ad4fc449f7035e5e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 20 Dec 2023 11:09:55 -0800 Subject: [PATCH 239/394] Release new versions compatible with Hyper 1 (#756) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 8 ++++---- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 29d4e191..244738e9 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.12.1" +version = "0.13.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 667b866f..ba0898a3 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.8.2" +version = "0.9.0" edition = "2021" authors = [ "David Calavera ", @@ -21,7 +21,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 057134a6..c3ec425e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.3" +version = "0.9.0" authors = [ "David Calavera ", "Harold Sun ", @@ -32,7 +32,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } +lambda_runtime = { version = "0.9", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -44,12 +44,12 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.12.0" +version = "0.13" default-features = false features = ["alb", "apigw"] [dev-dependencies] -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 8868b145..12b26043 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 8a579d99..f3c8e053 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.3" +version = "0.9.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" From a8f860ff6704227e21ad28dc800112a7aa3803f5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 26 Dec 2023 14:16:18 -0800 Subject: [PATCH 240/394] Cloudwatch alarms (#760) * Add CloudWatch Alarm events CloudWatch has added the ability to trigger Lambda functions base on alarms. See annoucement here: https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-cloudwatch-alarms-lambda-change-action/ Signed-off-by: David Calavera * Remove unnecessary import from tests. Signed-off-by: David Calavera * Update events section in the readme. Signed-off-by: David Calavera * Remove empty lines. Signed-off-by: David Calavera * Implement custom serde interfaces with known reason data. Signed-off-by: David Calavera * Add more docs Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- Makefile | 1 + README.md | 2 +- lambda-events/Cargo.toml | 2 + lambda-events/src/custom_serde/mod.rs | 1 - lambda-events/src/encodings/http.rs | 1 - lambda-events/src/encodings/time.rs | 1 - lambda-events/src/event/activemq/mod.rs | 2 - lambda-events/src/event/alb/mod.rs | 2 - lambda-events/src/event/apigw/mod.rs | 2 - lambda-events/src/event/appsync/mod.rs | 2 - lambda-events/src/event/autoscaling/mod.rs | 2 - lambda-events/src/event/clientvpn/mod.rs | 2 - .../src/event/cloudwatch_alarms/mod.rs | 301 ++++++++++++++++++ lambda-events/src/event/code_commit/mod.rs | 2 - lambda-events/src/event/codebuild/mod.rs | 2 - lambda-events/src/event/codedeploy/mod.rs | 2 - .../src/event/codepipeline_cloudwatch/mod.rs | 2 - .../src/event/codepipeline_job/mod.rs | 2 - lambda-events/src/event/cognito/mod.rs | 2 - lambda-events/src/event/config/mod.rs | 2 - lambda-events/src/event/connect/mod.rs | 2 - lambda-events/src/event/dynamodb/mod.rs | 2 - lambda-events/src/event/ecr_scan/mod.rs | 2 - lambda-events/src/event/firehose/mod.rs | 2 - lambda-events/src/event/iot/mod.rs | 2 - lambda-events/src/event/iot_1_click/mod.rs | 1 - lambda-events/src/event/iot_button/mod.rs | 2 - lambda-events/src/event/kafka/mod.rs | 2 - lambda-events/src/event/kinesis/event.rs | 2 - lambda-events/src/event/lex/mod.rs | 2 - lambda-events/src/event/mod.rs | 6 +- lambda-events/src/event/rabbitmq/mod.rs | 2 - lambda-events/src/event/s3/event.rs | 2 - lambda-events/src/event/s3/object_lambda.rs | 2 - lambda-events/src/event/ses/mod.rs | 2 - lambda-events/src/event/sns/mod.rs | 2 - lambda-events/src/event/sqs/mod.rs | 2 - .../example-cloudwatch-alarm-composite.json | 30 ++ .../example-cloudwatch-alarm-metric.json | 42 +++ lambda-events/src/lib.rs | 6 +- lambda-events/src/time_window.rs | 2 - 41 files changed, 387 insertions(+), 65 deletions(-) create mode 100644 lambda-events/src/event/cloudwatch_alarms/mod.rs create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json diff --git a/Makefile b/Makefile index 049be8f8..f66ee1fd 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features bedrock_agent_runtime cargo test --package aws_lambda_events --no-default-features --features chime_bot cargo test --package aws_lambda_events --no-default-features --features clientvpn + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_alarms cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events cargo test --package aws_lambda_events --no-default-features --features cloudwatch_logs cargo test --package aws_lambda_events --no-default-features --features code_commit diff --git a/README.md b/README.md index c586e657..ef43ba74 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt ## AWS event objects -This project does not currently include Lambda event struct definitions. Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. +This project includes Lambda event struct definitions, [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events). This crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. ### Custom event objects diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 244738e9..46ab7c11 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -47,6 +47,7 @@ default = [ "chime_bot", "clientvpn", "cloudformation", + "cloudwatch_alarms", "cloudwatch_events", "cloudwatch_logs", "code_commit", @@ -90,6 +91,7 @@ bedrock_agent_runtime = [] chime_bot = ["chrono"] clientvpn = [] cloudformation = [] +cloudwatch_alarms = ["chrono"] cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] code_commit = ["chrono"] diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 65f0f89a..82723c3f 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -96,7 +96,6 @@ where mod test { use super::*; use serde::{Deserialize, Serialize}; - use serde_json; #[test] fn test_deserialize_base64() { diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 1cb10c81..e013a698 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -246,7 +246,6 @@ impl HttpBody for Body { #[cfg(test)] mod tests { use super::*; - use serde_json; use std::collections::HashMap; #[test] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index a550b7b0..203aff75 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -215,7 +215,6 @@ where mod test { use super::*; use chrono::TimeZone; - use serde_json; #[test] fn test_deserialize_milliseconds() { diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 9469ece4..89cfda1c 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -54,8 +54,6 @@ pub struct ActiveMqDestination { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "activemq")] fn example_activemq_event() { diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 259dce23..a3f96d88 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -69,8 +69,6 @@ pub struct AlbTargetGroupResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "alb")] fn example_alb_lambda_target_request_headers_only() { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index fcc5ed0c..36512f9c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -769,8 +769,6 @@ fn default_http_method() -> Method { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "apigw")] fn example_apigw_custom_auth_request_type_request() { diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 120fc9e3..4035f181 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -122,8 +122,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "appsync")] fn example_appsync_identity_cognito() { diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index cc003daf..707b828a 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -48,8 +48,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "autoscaling")] fn example_autoscaling_event_launch_successful() { diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 0e188704..163abe72 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -49,8 +49,6 @@ pub struct ClientVpnConnectionHandlerResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "clientvpn")] fn example_clientvpn_connectionhandler_request() { diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs new file mode 100644 index 00000000..0f2eb03d --- /dev/null +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -0,0 +1,301 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{ + de::{DeserializeOwned, Visitor}, + ser::Error as SerError, + Deserialize, Serialize, +}; +use serde_json::Value; + +/// `CloudWatchAlarm` is the generic outer structure of an event triggered by a CloudWatch Alarm. +/// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. +/// For examples of events that come via CloudWatch Alarms, +/// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#Lambda-action-payload +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarm +where + C: DeserializeOwned, + C: Serialize, + R: DeserializeOwned, + R: Serialize, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub alarm_arn: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + pub region: Option, + pub time: DateTime, + + #[serde(default, bound = "")] + pub alarm_data: CloudWatchAlarmData, +} + +/// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. +#[allow(unused)] +type CloudWatchMetricAlarm = CloudWatchAlarm; + +/// `CloudWatchCompositeAlarm` is the structure of an event triggered by CloudWatch composite alarms. +#[allow(unused)] +type CloudWatchCompositeAlarm = + CloudWatchAlarm; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmData +where + C: DeserializeOwned, + C: Serialize, + R: DeserializeOwned, + R: Serialize, +{ + pub alarm_name: String, + #[serde(default, bound = "")] + pub state: Option>, + #[serde(default, bound = "")] + pub previous_state: Option>, + #[serde(bound = "")] + pub configuration: C, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmState +where + R: DeserializeOwned, + R: Serialize, +{ + pub value: String, + pub reason: String, + #[serde(default, bound = "")] + pub reason_data: Option, + pub timestamp: DateTime, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricAlarmConfiguration { + #[serde(default)] + pub description: Option, + #[serde(default)] + pub metrics: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricDefinition { + pub id: String, + #[serde(default)] + pub return_data: bool, + pub metric_stat: CloudWatchMetricStatDefinition, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricStatDefinition { + #[serde(default)] + pub unit: Option, + #[serde(default)] + pub stat: Option, + pub period: u16, + pub metric: CloudWatchMetricStatMetricDefinition, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricStatMetricDefinition { + #[serde(default)] + pub namespace: Option, + pub name: String, + pub dimensions: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchCompositeAlarmConfiguration { + pub alarm_rule: String, + pub actions_suppressor: String, + pub actions_suppressor_wait_period: u16, + pub actions_suppressor_extension_period: u16, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CloudWatchAlarmStateValue { + #[default] + Ok, + Alarm, + InsuficientData, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CloudWatchAlarmStateReasonData { + Metric(CloudWatchAlarmStateReasonDataMetric), + Composite(ClodWatchAlarmStateReasonDataComposite), + Generic(Value), +} + +impl Default for CloudWatchAlarmStateReasonData { + fn default() -> Self { + Self::Generic(Value::String(String::new())) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateReasonDataMetric { + pub version: String, + #[serde(default)] + pub query_date: Option>, + #[serde(default)] + pub start_date: Option>, + #[serde(default)] + pub unit: Option, + #[serde(default)] + pub statistic: Option, + pub period: u16, + #[serde(default)] + pub recent_datapoints: Vec, + #[serde(default)] + pub recent_lower_thresholds: Vec, + #[serde(default)] + pub recent_upper_thresholds: Vec, + pub threshold: f64, + #[serde(default)] + pub evaluated_datapoints: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateEvaluatedDatapoint { + pub timestamp: DateTime, + #[serde(default)] + pub sample_count: Option, + #[serde(default)] + pub value: Option, + #[serde(default)] + pub threshold: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClodWatchAlarmStateReasonDataComposite { + #[serde(default)] + pub triggering_alarms: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateTriggeringAlarm { + pub arn: String, + pub state: CloudWatchAlarmStateTriggeringAlarmState, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateTriggeringAlarmState { + pub timestamp: DateTime, + #[serde(default)] + pub value: CloudWatchAlarmStateValue, +} + +impl<'de> Deserialize<'de> for CloudWatchAlarmStateReasonData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(ReasonDataVisitor) + } +} + +impl Serialize for CloudWatchAlarmStateReasonData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let r = match self { + Self::Metric(m) => serde_json::to_string(m), + Self::Composite(m) => serde_json::to_string(m), + Self::Generic(m) => serde_json::to_string(m), + }; + let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {}", e)))?; + + serializer.serialize_str(&s) + } +} + +struct ReasonDataVisitor; + +impl<'de> Visitor<'de> for ReasonDataVisitor { + type Value = CloudWatchAlarmStateReasonData; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a string with the alarm state reason data") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + if let Ok(metric) = serde_json::from_str::(v) { + return Ok(CloudWatchAlarmStateReasonData::Metric(metric)); + } + if let Ok(aggregate) = serde_json::from_str::(v) { + return Ok(CloudWatchAlarmStateReasonData::Composite(aggregate)); + } + Ok(CloudWatchAlarmStateReasonData::Generic(Value::String(v.to_owned()))) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_metric() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-metric.json"); + let parsed: CloudWatchMetricAlarm = serde_json::from_slice(data).unwrap(); + let state = parsed.alarm_data.previous_state.clone().unwrap(); + let data = state.reason_data.unwrap(); + match &data { + CloudWatchAlarmStateReasonData::Metric(d) => { + assert_eq!("1.0", d.version); + assert_eq!(5, d.evaluated_datapoints.len()); + } + _ => panic!("unexpected reason data {data:?}"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchMetricAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_composite() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-composite.json"); + let parsed: CloudWatchCompositeAlarm = serde_json::from_slice(data).unwrap(); + + let state = parsed.alarm_data.state.clone().unwrap(); + let data = state.reason_data.unwrap(); + match &data { + CloudWatchAlarmStateReasonData::Composite(d) => { + assert_eq!(1, d.triggering_alarms.len()); + assert_eq!( + CloudWatchAlarmStateValue::Alarm, + d.triggering_alarms.first().unwrap().state.value + ); + } + _ => panic!("unexpected reason data {data:?}"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 87687cfd..7d5edfaa 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -69,8 +69,6 @@ pub struct CodeCommitReference { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "code_commit")] fn example_code_commit_event() { diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index a3839f92..fdabcb6f 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -213,8 +213,6 @@ pub type CodeBuildTime = DateTime; mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codebuild")] fn example_codebuild_phase_change() { diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 2ab37a82..896f509f 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -69,8 +69,6 @@ pub struct CodeDeployEventDetail { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codedeploy")] fn example_codedeploy_deployment_event() { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index f26aa54f..22db26b1 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -81,8 +81,6 @@ pub struct CodePipelineEventDetailType { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codepipeline_cloudwatch")] fn example_codepipeline_action_execution_stage_change_event() { diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 6c5d75f6..888e77b7 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -117,8 +117,6 @@ pub struct CodePipelineArtifactCredentials { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codepipeline_job")] fn example_codepipeline_job_event() { diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index c07c40a4..614f5278 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -464,8 +464,6 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "cognito")] fn example_cognito_event() { diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 0b03ecc5..7c06e13b 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -40,8 +40,6 @@ pub struct ConfigEvent { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "config")] fn example_config_event() { diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index bc640930..04f26eb5 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -94,8 +94,6 @@ pub type ConnectResponse = HashMap; mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "connect")] fn example_connect_event() { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 398f2dd5..33b977bd 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -252,8 +252,6 @@ mod test { use super::*; use chrono::TimeZone; - use serde_json; - #[test] #[cfg(feature = "dynamodb")] fn example_dynamodb_event() { diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 1ed91896..99c08f6d 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -61,8 +61,6 @@ pub struct EcrScanEventFindingSeverityCounts { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "ecr_scan")] fn example_ecr_image_scan_event() { diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 352342ec..6a0a13fd 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -76,8 +76,6 @@ pub struct KinesisFirehoseRecordMetadata { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "firehose")] fn example_firehose_event() { diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 31220b17..cf0ca246 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -76,8 +76,6 @@ pub struct IoTCoreCustomAuthorizerResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "iot")] fn example_iot_custom_auth_request() { diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 4ec47d71..bf010b50 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -59,7 +59,6 @@ pub struct IoTOneClickPlacementInfo { #[cfg(test)] mod test { use super::*; - use serde_json; #[test] #[cfg(feature = "iot_1_click")] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index ac79e34b..2d2e4627 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -15,8 +15,6 @@ pub struct IoTButtonEvent { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "iot_button")] fn example_iot_button_event() { diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 8cd92bdf..27a1e921 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -35,8 +35,6 @@ pub struct KafkaRecord { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "kafka")] fn example_kafka_event() { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 0c43ae10..2b14cbed 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -75,8 +75,6 @@ pub struct KinesisRecord { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "kinesis")] fn example_kinesis_event() { diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index a058a249..d8f9403c 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -108,8 +108,6 @@ pub struct Attachment { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "lex")] fn example_lex_event() { diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 28a0c82b..ea0ca7df 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -33,7 +33,11 @@ pub mod clientvpn; #[cfg(feature = "cloudformation")] pub mod cloudformation; -/// CloudWatch Events payload +/// AWS Lambda event definitions for CloudWatch alarms. +#[cfg(feature = "cloudwatch_alarms")] +pub mod cloudwatch_alarms; + +/// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] pub mod cloudwatch_events; diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 14a379a5..47f3c004 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -63,8 +63,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "rabbitmq")] fn example_rabbitmq_event() { diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 128a5811..e062c7d2 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -92,8 +92,6 @@ pub struct S3Object { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "s3")] fn example_s3_event() { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 69e9907d..fe52b8a6 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -120,8 +120,6 @@ pub struct SessionIssuer { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "s3")] fn example_object_lambda_event_get_object_assumed_role() { diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index a4a97039..2a60957a 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -122,8 +122,6 @@ pub struct SimpleEmailDisposition { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "ses")] fn example_ses_lambda_event() { diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 3c859d3e..e9809630 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -179,8 +179,6 @@ pub struct MessageAttribute { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "sns")] fn my_example_sns_event() { diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 5c10a428..21359f3a 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -184,8 +184,6 @@ pub struct SqsApiMessage { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "sqs")] fn example_sqs_event() { diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json new file mode 100644 index 00000000..f74b8f08 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json @@ -0,0 +1,30 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:SuppressionDemo.Main", + "accountId": "111122223333", + "time": "2023-08-04T12: 56: 46.138+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "CompositeDemo.Main", + "state": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12: 54: 46 UTC", + "reasonData": "{\"triggeringAlarms\": [{\"arn\": \"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\": {\"value\": \"ALARM\",\"timestamp\": \"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12: 56: 46.138+0000" + }, + "previousState": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August,2023 12: 54: 46 UTC", + "reasonData": "{\"triggeringAlarms\": [{\"arn\": \"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\": {\"value\": \"ALARM\",\"timestamp\": \"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12: 54: 46.138+0000", + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod" + }, + "configuration": { + "alarmRule": "ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", + "actionsSuppressor": "CompositeDemo.ActionsSuppressor", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json new file mode 100644 index 00000000..04a17649 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json @@ -0,0 +1,42 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1: 444455556666:alarm:lambda-demo-metric-alarm", + "accountId": "444455556666", + "time": "2023-08-04T12: 36: 15.490+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "lambda-demo-metric-alarm", + "state": { + "value": "ALARM", + "reason": "test", + "timestamp": "2023-08-04T12: 36: 15.490+0000" + }, + "previousState": { + "value": "INSUFFICIENT_DATA", + "reason": "Insufficient Data: 5 datapoints were unknown.", + "reasonData": "{\"version\": \"1.0\", \"queryDate\": \"2023-08-04T12: 31: 29.591+0000\", \"statistic\": \"Average\", \"period\": 60, \"recentDatapoints\": [], \"threshold\": 5.0, \"evaluatedDatapoints\": [{\"timestamp\": \"2023-08-04T12: 30: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 29: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 28: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 27: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 26: 00.000+0000\"}]}", + "timestamp": "2023-08-04T12: 31: 29.595+0000" + }, + "configuration": { + "description": "Metric Alarm to test Lambda actions", + "metrics": [ + { + "id": "1234e046-06f0-a3da-9534-EXAMPLEe4c", + "metricStat": { + "metric": { + "namespace": "AWS/Logs", + "name": "CallCount", + "dimensions": { + "InstanceId": "i-12345678" + } + }, + "period": 60, + "stat": "Average", + "unit": "Percent" + }, + "returnData": true + } + ] + } + } +} \ No newline at end of file diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index aa0d5495..4fd294e6 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -45,7 +45,11 @@ pub use event::clientvpn; #[cfg(feature = "cloudformation")] pub use event::cloudformation; -/// CloudWatch Events payload +/// AWS Lambda event definitions for CloudWatch alarms. +#[cfg(feature = "cloudwatch_alarms")] +pub use event::cloudwatch_alarms; + +/// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] pub use event::cloudwatch_events; diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 9f035995..edc9beb5 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -66,8 +66,6 @@ pub struct TimeWindowEventResponseProperties { mod test { use super::*; - use serde_json; - #[test] fn test_window_deserializer() { let v = serde_json::json!({ From f00aa0416d83828db5c509aec883e8ed5356aeb8 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Fri, 29 Dec 2023 02:41:21 +0900 Subject: [PATCH 241/394] Fix parsing failure in CloudWatchAlarmStateValue. (#761) --- lambda-events/src/event/cloudwatch_alarms/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 0f2eb03d..aa8f6dd1 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -69,7 +69,8 @@ where R: DeserializeOwned, R: Serialize, { - pub value: String, + #[serde(default)] + pub value: CloudWatchAlarmStateValue, pub reason: String, #[serde(default, bound = "")] pub reason_data: Option, @@ -129,7 +130,7 @@ pub enum CloudWatchAlarmStateValue { #[default] Ok, Alarm, - InsuficientData, + InsufficientData, } #[derive(Clone, Debug, PartialEq)] From ee7dcd7c0340f097e573e4941ebd687fad551d39 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 28 Dec 2023 10:48:06 -0800 Subject: [PATCH 242/394] Remove unused imports. (#762) Signed-off-by: David Calavera --- lambda-http/src/streaming.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 601e699b..217c4564 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -2,14 +2,10 @@ use crate::http::header::SET_COOKIE; use crate::tower::ServiceBuilder; use crate::Request; use crate::{request::LambdaRequest, RequestExt}; -pub use aws_lambda_events::encodings::Body as LambdaEventBody; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; -pub use lambda_runtime::{ - self, service_fn, tower, tower::ServiceExt, Error, FunctionResponse, LambdaEvent, MetadataPrelude, Service, - StreamResponse, -}; +pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; use std::fmt::{Debug, Display}; use std::pin::Pin; use std::task::{Context, Poll}; From 3b32d090d12b8c790d499224a2eee10f0d460b22 Mon Sep 17 00:00:00 2001 From: Benjamen Pyle Date: Fri, 29 Dec 2023 10:42:52 -0600 Subject: [PATCH 243/394] Add Event Definition for CognitoEventUserPoolsPreTokenGenV2 (#764) The added structs allow for the processing of Version 2 Cognito PreToken generation in a Lambda The V2 payloads allow for customization of the Access Token in addition to the ID Token which was already supported --- lambda-events/src/event/cognito/mod.rs | 79 +++++++++++++++++++ ...ent-userpools-pretokengen-v2-incoming.json | 33 ++++++++ ...ognito-event-userpools-pretokengen-v2.json | 58 ++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 614f5278..decc31a5 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -213,6 +213,65 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { pub claims_override_details: Option, } +/// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve +/// credentials, allowing a Lambda to perform insert, suppress or override claims. This is the Version 2 Payload +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenV2 { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreTokenGenRequestV2, + pub response: CognitoEventUserPoolsPreTokenGenResponseV2, +} + +/// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + pub group_configuration: GroupConfiguration, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, + pub scopes: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { + pub claims_and_scope_override_details: Option, +} + +/// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClaimsAndScopeOverrideDetailsV2 { + pub group_override_details: GroupConfiguration, + pub id_token_generation: Option, + pub access_token_generation: Option, +} + +/// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoIdTokenGenerationV2 { + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, +} + +/// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoAccessTokenGenerationV2 { + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, + pub scopes_to_add: Vec, + pub scopes_to_suppress: Vec, +} + /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -608,6 +667,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen_v2_incoming() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json"); + let parsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_pretokengen() { @@ -618,6 +687,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_v2_pretokengen() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-v2.json"); + let parsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_verify_auth_challenge() { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json new file mode 100644 index 00000000..3376d6e0 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json @@ -0,0 +1,33 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsOverrideDetails": null + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json new file mode 100644 index 00000000..f7ccfe2f --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json @@ -0,0 +1,58 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsAndScopeOverrideDetails": { + "idTokenGeneration": { + "claimsToAddOrOverride": { + "string": "string" + }, + "claimsToSuppress": ["string", "string"] + }, + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" + }, + "claimsToSuppress": ["email", "phone"], + "scopesToAdd": ["scope-B", "scope-B"], + "scopesToSuppress": ["scope-C", "scope-D"] + }, + "groupOverrideDetails": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } + } + } +} From 3566f424552eb01af6fef3674c9cc5558b3518df Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 16 Jan 2024 02:39:49 +0800 Subject: [PATCH 244/394] Add events pass-through feature in lambda-http crate (#775) * Add pass-through support for non-http triggers * Fix linting warming * Remove deserialize_error test This test won't fail when `pass-through` feature is enabled. --- lambda-http/Cargo.toml | 3 ++- lambda-http/src/deserializer.rs | 48 ++++++++++++++++----------------- lambda-http/src/request.rs | 32 ++++++++++++++++++++++ lambda-http/src/response.rs | 11 ++++++++ 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index c3ec425e..2e1bcdfa 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -21,6 +21,7 @@ apigw_rest = [] apigw_http = [] apigw_websockets = [] alb = [] +pass_through = [] [dependencies] base64 = { workspace = true } @@ -37,7 +38,7 @@ mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = { version = "1.0", features = ["raw_value"] } serde_urlencoded = "0.7" tokio-stream = "0.1.2" url = "2.2" diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 7756629c..2584c0ad 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -1,43 +1,48 @@ use crate::request::LambdaRequest; +#[cfg(feature = "alb")] +use aws_lambda_events::alb::AlbTargetGroupRequest; +#[cfg(feature = "apigw_rest")] +use aws_lambda_events::apigw::ApiGatewayProxyRequest; +#[cfg(feature = "apigw_http")] +use aws_lambda_events::apigw::ApiGatewayV2httpRequest; +#[cfg(feature = "apigw_websockets")] +use aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest; use serde::{de::Error, Deserialize}; +use serde_json::value::RawValue; const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; +#[cfg(feature = "pass_through")] +const PASS_THROUGH_ENABLED: bool = true; + impl<'de> Deserialize<'de> for LambdaRequest { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let content = match serde::__private::de::Content::deserialize(deserializer) { - Ok(content) => content, - Err(err) => return Err(err), - }; + let raw_value: Box = Box::deserialize(deserializer)?; + let data = raw_value.get(); + #[cfg(feature = "apigw_rest")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayProxyRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::ApiGatewayV1(res)); } #[cfg(feature = "apigw_http")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayV2httpRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::ApiGatewayV2(res)); } #[cfg(feature = "alb")] - if let Ok(res) = - aws_lambda_events::alb::AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::< - D::Error, - >::new(&content)) - { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::Alb(res)); } #[cfg(feature = "apigw_websockets")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::WebSocket(res)); } + #[cfg(feature = "pass_through")] + if PASS_THROUGH_ENABLED { + return Ok(LambdaRequest::PassThrough(data.to_string())); + } Err(Error::custom(ERROR_CONTEXT)) } @@ -104,11 +109,4 @@ mod tests { other => panic!("unexpected request variant: {:?}", other), } } - - #[test] - fn test_deserialize_error() { - let err = serde_json::from_str::("{\"body\": {}}").unwrap_err(); - - assert_eq!(ERROR_CONTEXT, err.to_string()); - } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ad86e5a5..c98f5c17 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -51,6 +51,8 @@ pub enum LambdaRequest { Alb(AlbTargetGroupRequest), #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequest), + #[cfg(feature = "pass_through")] + PassThrough(String), } impl LambdaRequest { @@ -67,6 +69,8 @@ impl LambdaRequest { LambdaRequest::Alb { .. } => RequestOrigin::Alb, #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, + #[cfg(feature = "pass_through")] + LambdaRequest::PassThrough { .. } => RequestOrigin::PassThrough, #[cfg(not(any( feature = "apigw_rest", feature = "apigw_http", @@ -97,6 +101,9 @@ pub enum RequestOrigin { /// API Gateway WebSocket #[cfg(feature = "apigw_websockets")] WebSocket, + /// PassThrough request origin + #[cfg(feature = "pass_through")] + PassThrough, } #[cfg(feature = "apigw_http")] @@ -338,6 +345,26 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< req } +#[cfg(feature = "pass_through")] +fn into_pass_through_request(data: String) -> http::Request { + let mut builder = http::Request::builder(); + + let headers = builder.headers_mut().unwrap(); + headers.insert("Content-Type", "application/json".parse().unwrap()); + + update_xray_trace_id_header(headers); + + let raw_path = "/events"; + + builder + .method(http::Method::POST) + .uri(raw_path) + .extension(RawHttpPath(raw_path.to_string())) + .extension(RequestContext::PassThrough) + .body(Body::from(data)) + .expect("failed to build request") +} + #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() { @@ -375,6 +402,9 @@ pub enum RequestContext { /// WebSocket request context #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequestContext), + /// Custom request context + #[cfg(feature = "pass_through")] + PassThrough, } /// Converts LambdaRequest types into `http::Request` types @@ -389,6 +419,8 @@ impl From for http::Request { LambdaRequest::Alb(alb) => into_alb_request(alb), #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket(ag) => into_websocket_request(ag), + #[cfg(feature = "pass_through")] + LambdaRequest::PassThrough(data) => into_pass_through_request(data), } } } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index d26ef838..cc721d46 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -46,6 +46,8 @@ pub enum LambdaResponse { ApiGatewayV2(ApiGatewayV2httpResponse), #[cfg(feature = "alb")] Alb(AlbTargetGroupResponse), + #[cfg(feature = "pass_through")] + PassThrough(serde_json::Value), } /// Transformation from http type to internal type @@ -114,6 +116,15 @@ impl LambdaResponse { headers: headers.clone(), multi_value_headers: headers, }), + #[cfg(feature = "pass_through")] + RequestOrigin::PassThrough => { + match body { + // text body must be a valid json string + Some(Body::Text(body)) => {LambdaResponse::PassThrough(serde_json::from_str(&body).unwrap_or_default())}, + // binary body and other cases return Value::Null + _ => LambdaResponse::PassThrough(serde_json::Value::Null), + } + } #[cfg(not(any( feature = "apigw_rest", feature = "apigw_http", From 4cf514a88e331f4ab92146efe0ed7ed5fd236265 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Tue, 16 Jan 2024 03:40:00 +0900 Subject: [PATCH 245/394] Add missing fields for suppressor alarm (#774) * Add missing fields for suppressor alarm * Add test for suppresor alarm * Fix format error --- .../src/event/cloudwatch_alarms/mod.rs | 19 ++++++++++ ...alarm-composite-with-suppressor-alarm.json | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index aa8f6dd1..5c4b704d 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -75,6 +75,8 @@ where #[serde(default, bound = "")] pub reason_data: Option, pub timestamp: DateTime, + pub actions_suppressed_by: Option, + pub actions_suppressed_reason: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -299,4 +301,21 @@ mod test { let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_composite_with_suppressor_alarm() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json"); + let parsed: CloudWatchCompositeAlarm = serde_json::from_slice(data).unwrap(); + let state = parsed.alarm_data.state.clone().unwrap(); + assert_eq!("WaitPeriod", state.actions_suppressed_by.unwrap()); + assert_eq!( + "Actions suppressed by WaitPeriod", + state.actions_suppressed_reason.unwrap() + ); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json new file mode 100644 index 00000000..26ce1c9b --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json @@ -0,0 +1,35 @@ +{ + "version": "0", + "id": "d3dfc86d-384d-24c8-0345-9f7986db0b80", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-22T15:57:45Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "alarmData": { + "alarmName": "ServiceAggregatedAlarm", + "state": { + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod", + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", + "timestamp": "2022-07-22T15:57:45.394+0000" + }, + "previousState": { + "value": "OK", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}", + "timestamp": "2022-07-22T15:56:14.552+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file From b4914f884c3d40290022a3558a3195bd04bd9897 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 16 Jan 2024 08:54:55 +0800 Subject: [PATCH 246/394] Release lambda-http 0.9.1 (#777) --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 2e1bcdfa..4c84de21 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.0" +version = "0.9.1" authors = [ "David Calavera ", "Harold Sun ", From 6e03ba7b5d7d91611e6d57ddf7499a6f1d54e260 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 15 Jan 2024 17:00:15 -0800 Subject: [PATCH 247/394] Add a default implementation for Context. (#778) --- lambda-runtime/src/types.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index f2a36073..85f6af78 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -94,6 +94,20 @@ pub struct Context { pub env_config: RefConfig, } +impl Default for Context { + fn default() -> Context { + Context { + request_id: "".to_owned(), + deadline: 0, + invoked_function_arn: "".to_owned(), + xray_trace_id: None, + client_context: None, + identity: None, + env_config: std::sync::Arc::new(crate::Config::default()), + } + } +} + impl Context { /// Create a new [Context] struct based on the fuction configuration /// and the incoming request data. From 6e1c1540daefb37177d2004c837af7e52c74b707 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 15 Jan 2024 18:56:38 -0800 Subject: [PATCH 248/394] Release lambda_runtime 0.9.1 (#779) --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index f3c8e053..ccbb3b2e 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.0" +version = "0.9.1" authors = [ "David Calavera ", "Harold Sun ", From 99eb0319a2c7ee229f7c88d67de02ae696729eeb Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 16 Jan 2024 01:16:09 -0800 Subject: [PATCH 249/394] Test runtime with httpmock. (#780) * Test runtime with httpmock. - Remove generic runtime over the client. - Make tests use httpmock for more concise assertions. Signed-off-by: David Calavera * Bump MSRV to 1.65. Fixes compilation issues with the regex crate. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime-api-client/src/lib.rs | 41 +-- lambda-runtime/Cargo.toml | 3 +- lambda-runtime/src/lib.rs | 345 +++++++++----------------- lambda-runtime/src/simulated.rs | 133 ---------- 8 files changed, 134 insertions(+), 396 deletions(-) delete mode 100644 lambda-runtime/src/simulated.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 4e5fb34d..caee1131 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 7365bc64..7165a281 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 9657a840..026c20e0 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index ef43ba74..eeeaf2fa 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.64, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.65, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 15185f81..ec7418ba 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -6,9 +6,8 @@ //! the AWS Lambda Runtime API. use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; use hyper::body::Incoming; -use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; +use hyper_util::client::legacy::connect::HttpConnector; use std::{convert::TryInto, fmt::Debug}; -use tower_service::Service; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); @@ -20,16 +19,16 @@ pub mod body; /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] -pub struct Client { +pub struct Client { /// The runtime API URI pub base: Uri, /// The client that manages the API connections - pub client: hyper_util::client::legacy::Client, + pub client: hyper_util::client::legacy::Client, } impl Client { /// Create a builder struct to configure the client. - pub fn builder() -> ClientBuilder { + pub fn builder() -> ClientBuilder { ClientBuilder { connector: HttpConnector::new(), uri: None, @@ -37,10 +36,7 @@ impl Client { } } -impl Client -where - C: Connect + Sync + Send + Clone + 'static, -{ +impl Client { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. pub async fn call(&self, req: Request) -> Result, BoxError> { @@ -49,7 +45,7 @@ where } /// Create a new client with a given base URI and HTTP connector. - pub fn with(base: Uri, connector: C) -> Self { + fn with(base: Uri, connector: HttpConnector) -> Self { let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .http1_max_buf_size(1024 * 1024) .build(connector); @@ -80,26 +76,14 @@ where } /// Builder implementation to construct any Runtime API clients. -pub struct ClientBuilder = HttpConnector> { - connector: C, +pub struct ClientBuilder { + connector: HttpConnector, uri: Option, } -impl ClientBuilder -where - C: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, -{ +impl ClientBuilder { /// Create a new builder with a given HTTP connector. - pub fn with_connector(self, connector: C2) -> ClientBuilder - where - C2: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, - { + pub fn with_connector(self, connector: HttpConnector) -> ClientBuilder { ClientBuilder { connector, uri: self.uri, @@ -113,10 +97,7 @@ where } /// Create the new client to interact with the Runtime API. - pub fn build(self) -> Result, Error> - where - C: Connect + Sync + Send + Clone + 'static, - { + pub fn build(self) -> Result { let uri = match self.uri { Some(uri) => uri, None => { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index ccbb3b2e..221aa6f0 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -51,6 +51,7 @@ tower = { workspace = true, features = ["util"] } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] +httpmock = "0.7.0" hyper-util = { workspace = true, features = [ "client", "client-legacy", @@ -59,4 +60,4 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } -pin-project-lite = { workspace = true } \ No newline at end of file +pin-project-lite = { workspace = true } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 10dfce92..3a91d943 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -11,7 +11,6 @@ use bytes::Bytes; use futures::FutureExt; use http_body_util::BodyExt; use hyper::{body::Incoming, http::Request}; -use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ @@ -28,8 +27,6 @@ use tracing::{error, trace, Instrument}; mod deserializer; mod requests; -#[cfg(test)] -mod simulated; /// Utilities for Lambda Streaming functions. pub mod streaming; /// Types available to a Lambda function. @@ -85,18 +82,12 @@ where service_fn(move |req: LambdaEvent| f(req.payload, req.context)) } -struct Runtime = HttpConnector> { - client: Client, +struct Runtime { + client: Client, config: RefConfig, } -impl Runtime -where - C: Service + Connect + Clone + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send, - C::Error: Into>, - C::Response: Connection + Unpin + Send + 'static, -{ +impl Runtime { async fn run( &self, incoming: impl Stream, Error>> + Send, @@ -196,13 +187,7 @@ where } } -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ -where - C: Service + Connect + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, -{ +fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ { async_stream::stream! { loop { trace!("Waiting for next event (incoming loop)"); @@ -276,231 +261,135 @@ where mod endpoint_tests { use crate::{ incoming, - requests::{ - EventCompletionRequest, EventErrorRequest, IntoRequest, IntoResponse, NextEventRequest, NextEventResponse, - }, - simulated, + requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, types::Diagnostic, Config, Error, Runtime, }; use futures::future::BoxFuture; - use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; - use hyper::body::Incoming; - use hyper::rt::{Read, Write}; - use hyper::service::service_fn; - - use hyper_util::server::conn::auto::Builder; - use lambda_runtime_api_client::{body::Body, Client}; - use serde_json::json; - use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, sync::Arc}; - use tokio::{ - io, select, - sync::{self, oneshot}, - }; - use tokio_stream::StreamExt; + use http::{HeaderValue, StatusCode}; + use http_body_util::BodyExt; + use httpmock::prelude::*; - #[cfg(test)] - async fn next_event(req: &Request) -> Result, Error> { - let path = "/2018-06-01/runtime/invocation/next"; - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); - let body = json!({"message": "hello"}); - - let rsp = NextEventResponse { - request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", - deadline: 1_542_409_706_888, - arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", - trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", - body: serde_json::to_vec(&body)?, - }; - rsp.into_rsp() - } - - #[cfg(test)] - async fn complete_event(req: &Request, id: &str) -> Result, Error> { - assert_eq!(Method::POST, req.method()); - let rsp = Response::builder() - .status(StatusCode::ACCEPTED) - .body(Body::empty()) - .expect("Unable to construct response"); - - let expected = format!("/2018-06-01/runtime/invocation/{id}/response"); - assert_eq!(expected, req.uri().path()); - - Ok(rsp) - } - - #[cfg(test)] - async fn event_err(req: &Request, id: &str) -> Result, Error> { - let expected = format!("/2018-06-01/runtime/invocation/{id}/error"); - assert_eq!(expected, req.uri().path()); - - assert_eq!(req.method(), Method::POST); - let header = "lambda-runtime-function-error-type"; - let expected = "unhandled"; - assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); - - let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; - Ok(rsp) - } - - #[cfg(test)] - async fn handle_incoming(req: Request) -> Result, Error> { - let path: Vec<&str> = req - .uri() - .path_and_query() - .expect("PathAndQuery not found") - .as_str() - .split('/') - .collect::>(); - match path[1..] { - ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, - ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, - ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, - ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), - _ => unimplemented!(), - } - } - - #[cfg(test)] - async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), Error> - where - I: Read + Write + Unpin + 'static, - { - use hyper_util::rt::TokioExecutor; - - let builder = Builder::new(TokioExecutor::new()); - let conn = builder.serve_connection(io, service_fn(handle_incoming)); - select! { - _ = rx => { - Ok(()) - } - res = conn => { - match res { - Ok(()) => Ok(()), - Err(e) => { - Err(e) - } - } - } - } - } + use lambda_runtime_api_client::Client; + use std::{env, sync::Arc}; + use tokio_stream::StreamExt; #[tokio::test] async fn test_next_event() -> Result<(), Error> { - let base = Uri::from_static("http://localhost:9001"); - let (client, server) = io::duplex(64); - - let (tx, rx) = sync::oneshot::channel(); - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let mock = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let req = NextEventRequest.into_req()?; let rsp = client.call(req).await.expect("Unable to send request"); + mock.assert_async().await; assert_eq!(rsp.status(), StatusCode::OK); - let header = "lambda-runtime-deadline-ms"; - assert_eq!(rsp.headers()[header], &HeaderValue::try_from("1542409706888")?); - - // shutdown server... - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + assert_eq!( + rsp.headers()["lambda-runtime-aws-request-id"], + &HeaderValue::from_static(request_id) + ); + assert_eq!( + rsp.headers()["lambda-runtime-deadline-ms"], + &HeaderValue::from_static(deadline) + ); + + let body = rsp.into_body().collect().await?.to_bytes(); + assert_eq!("{}", std::str::from_utf8(&body)?); + Ok(()) } #[tokio::test] async fn test_ok_response() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") + .body("\"{}\""); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; - let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "done"); + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); let req = req.into_req()?; let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) } #[tokio::test] async fn test_error_response() -> Result<(), Error> { - let (client, server) = io::duplex(200); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let diagnostic = Diagnostic { + error_type: "InvalidEventDataError", + error_message: "Error parsing event data", + }; + let body = serde_json::to_string(&diagnostic)?; + + let server = MockServer::start(); + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") + .header("lambda-runtime-function-error-type", "unhandled") + .body(body); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let req = EventErrorRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - diagnostic: Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", - }, + diagnostic, }; let req = req.into_req()?; let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) } #[tokio::test] async fn successful_end_to_end_run() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .body("{}"); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::builder() - .with_endpoint(base) - .with_connector(conn) - .build() - .expect("Unable to build client"); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; async fn func(event: crate::LambdaEvent) -> Result { let (event, _) = event.into_parts(); @@ -510,7 +399,7 @@ mod endpoint_tests { // set env vars needed to init Config if they are not already set in the environment if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { - env::set_var("AWS_LAMBDA_RUNTIME_API", "http://localhost:9001"); + env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); } if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); @@ -537,35 +426,37 @@ mod endpoint_tests { let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) } async fn run_panicking_handler(func: F) -> Result<(), Error> where F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result>, { - let (client, server) = io::duplex(64); - let (_tx, rx) = oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .header("lambda-runtime-function-error-type", "unhandled"); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::builder() - .with_endpoint(base) - .with_connector(conn) - .build() - .expect("Unable to build client"); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let f = crate::service_fn(func); @@ -582,11 +473,9 @@ mod endpoint_tests { let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) } #[tokio::test] diff --git a/lambda-runtime/src/simulated.rs b/lambda-runtime/src/simulated.rs deleted file mode 100644 index 018664fe..00000000 --- a/lambda-runtime/src/simulated.rs +++ /dev/null @@ -1,133 +0,0 @@ -use http::Uri; -use hyper::rt::{Read, Write}; -use hyper_util::client::legacy::connect::{Connected, Connection}; -use pin_project_lite::pin_project; -use std::{ - collections::HashMap, - future::Future, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll}, -}; -use tokio::io::DuplexStream; - -use crate::Error; - -#[derive(Clone)] -pub struct Connector { - inner: Arc>>, -} - -pin_project! { -pub struct DuplexStreamWrapper { - #[pin] - inner: DuplexStream, -} -} - -impl DuplexStreamWrapper { - pub(crate) fn new(inner: DuplexStream) -> DuplexStreamWrapper { - DuplexStreamWrapper { inner } - } -} - -impl Connector { - pub fn new() -> Self { - #[allow(clippy::mutable_key_type)] - let map = HashMap::new(); - Connector { - inner: Arc::new(Mutex::new(map)), - } - } - - pub fn insert(&self, uri: Uri, stream: DuplexStreamWrapper) -> Result<(), Error> { - match self.inner.lock() { - Ok(mut map) => { - map.insert(uri, stream); - Ok(()) - } - Err(_) => Err("mutex was poisoned".into()), - } - } - - pub fn with(uri: Uri, stream: DuplexStreamWrapper) -> Result { - let connector = Connector::new(); - match connector.insert(uri, stream) { - Ok(_) => Ok(connector), - Err(e) => Err(e), - } - } -} - -impl tower::Service for Connector { - type Response = DuplexStreamWrapper; - type Error = crate::Error; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; - - fn call(&mut self, uri: Uri) -> Self::Future { - let res = match self.inner.lock() { - Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), - Ok(_) => Err(format!("Uri {uri} is not in map").into()), - Err(_) => Err("mutex was poisoned".into()), - }; - Box::pin(async move { res }) - } - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} - -impl Connection for DuplexStreamWrapper { - fn connected(&self) -> Connected { - Connected::new() - } -} - -impl Read for DuplexStreamWrapper { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - mut buf: hyper::rt::ReadBufCursor<'_>, - ) -> Poll> { - let n = unsafe { - let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut()); - match tokio::io::AsyncRead::poll_read(self.project().inner, cx, &mut tbuf) { - Poll::Ready(Ok(())) => tbuf.filled().len(), - other => return other, - } - }; - - unsafe { - buf.advance(n); - } - Poll::Ready(Ok(())) - } -} - -impl Write for DuplexStreamWrapper { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - tokio::io::AsyncWrite::poll_write(self.project().inner, cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_flush(self.project().inner, cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx) - } - - fn is_write_vectored(&self) -> bool { - tokio::io::AsyncWrite::is_write_vectored(&self.inner) - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll> { - tokio::io::AsyncWrite::poll_write_vectored(self.project().inner, cx, bufs) - } -} From ea4a8f607fbde03ff9d3068ebedd94f2ddccf88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Sumis=C5=82awski?= Date: Thu, 18 Jan 2024 22:55:27 +0100 Subject: [PATCH 250/394] Make EcrScanEvent severities optional (#783) * Make EcrScanEvent severities optional * Add the ecr example json file needed by test --- lambda-events/src/event/ecr_scan/mod.rs | 28 +++++++++++++++---- ...ge-scan-event-with-missing-severities.json | 22 +++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 99c08f6d..5502e81a 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -43,18 +43,24 @@ pub struct EcrScanEventDetailType { #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { + #[serde(default)] #[serde(rename = "CRITICAL")] - pub critical: i64, + pub critical: Option, + #[serde(default)] #[serde(rename = "HIGH")] - pub high: i64, + pub high: Option, + #[serde(default)] #[serde(rename = "MEDIUM")] - pub medium: i64, + pub medium: Option, + #[serde(default)] #[serde(rename = "LOW")] - pub low: i64, + pub low: Option, + #[serde(default)] #[serde(rename = "INFORMATIONAL")] - pub informational: i64, + pub informational: Option, + #[serde(default)] #[serde(rename = "UNDEFINED")] - pub undefined: i64, + pub undefined: Option, } #[cfg(test)] @@ -70,4 +76,14 @@ mod test { let reparsed: EcrScanEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "ecr_scan")] + fn example_ecr_image_scan_event_with_missing_severities() { + let data = include_bytes!("../../fixtures/example-ecr-image-scan-event-with-missing-severities.json"); + let parsed: EcrScanEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EcrScanEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json b/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json new file mode 100644 index 00000000..883eb2d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json @@ -0,0 +1,22 @@ +{ + "version": "0", + "id": "85fc3613-e913-7fc4-a80c-a3753e4aa9ae", + "detail-type": "ECR Image Scan", + "source": "aws.ecr", + "account": "123456789012", + "time": "2019-10-29T02:36:48Z", + "region": "us-east-1", + "resources": [ + "arn:aws:ecr:us-east-1:123456789012:repository/my-repository-name" + ], + "detail": { + "scan-status": "COMPLETE", + "repository-name": "my-repository-name", + "finding-severity-counts": { + "CRITICAL": 10, + "MEDIUM": 9 + }, + "image-digest": "sha256:7f5b2640fe6fb4f46592dfd3410c4a79dac4f89e4782432e0378abcd1234", + "image-tags": [] + } +} \ No newline at end of file From 355627ea09b295146e64f488a9d1f517293996a2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 18 Jan 2024 20:03:12 -0800 Subject: [PATCH 251/394] Document APIGW stage behavior. (#784) Indicate how to disable the stage in the path. Signed-off-by: David Calavera --- lambda-http/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lambda-http/README.md b/lambda-http/README.md index 464be88b..4866d071 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -175,7 +175,7 @@ pub fn custom_authorizer_response(effect: &str, principal: &str, method_arn: &st } ``` -## Passing the Lambda execution context initialization to the handler +### Passing the Lambda execution context initialization to the handler One of the [best practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. @@ -229,3 +229,9 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Ok(response) } ``` + +## Integration with API Gateway stages + +When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. + +If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is prevent, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. From 2aa9da4a126efd27efcdb423ebebe26cecba1683 Mon Sep 17 00:00:00 2001 From: Willem <1571851+wduminy@users.noreply.github.com> Date: Sat, 20 Jan 2024 21:22:26 +0200 Subject: [PATCH 252/394] ADD: axum middleware example (#785) --- examples/http-axum-middleware/Cargo.toml | 18 ++++++++++ examples/http-axum-middleware/README.md | 11 ++++++ examples/http-axum-middleware/src/main.rs | 43 +++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 examples/http-axum-middleware/Cargo.toml create mode 100644 examples/http-axum-middleware/README.md create mode 100644 examples/http-axum-middleware/src/main.rs diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml new file mode 100644 index 00000000..ea437052 --- /dev/null +++ b/examples/http-axum-middleware/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "http-axum-middleware" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_rest", +] } +lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "fmt", +] } + diff --git a/examples/http-axum-middleware/README.md b/examples/http-axum-middleware/README.md new file mode 100644 index 00000000..498f8a50 --- /dev/null +++ b/examples/http-axum-middleware/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs new file mode 100644 index 00000000..41c5bf4b --- /dev/null +++ b/examples/http-axum-middleware/src/main.rs @@ -0,0 +1,43 @@ +//! This example demonstrates how [axum middleware](https://docs.rs/axum/latest/axum/middleware/index.html) +//! can be implemented. +//! +//! To test this: +//! ```sh +//! # start the local server +//! cargo lambda watch +//! # Then send through an example request +//! cargo lambda invoke --data-example apigw-request +//! ``` + +use axum::{response::Json, routing::post, Router}; +use lambda_http::request::RequestContext::ApiGatewayV1; +use lambda_http::{run, Error}; +use serde_json::{json, Value}; + +// Sample middleware that logs the request id +async fn mw_sample(req: axum::extract::Request, next: axum::middleware::Next) -> impl axum::response::IntoResponse { + let context = req.extensions().get::(); + if let Some(ApiGatewayV1(ctx)) = context { + tracing::info!("RequestId = {:?}", ctx.request_id); + } + next.run(req).await +} + +async fn handler_sample(body: Json) -> Json { + Json(json!({ "echo": *body })) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .without_time() + .init(); + + let app = Router::new() + .route("/testStage/hello/world", post(handler_sample)) + .route_layer(axum::middleware::from_fn(mw_sample)); + + run(app).await +} From b8880d23babacd32c5877b64e178eff0ed47781d Mon Sep 17 00:00:00 2001 From: Willem <1571851+wduminy@users.noreply.github.com> Date: Tue, 23 Jan 2024 05:40:09 +0200 Subject: [PATCH 253/394] Minor fixes to README.md (#787) --- lambda-http/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/README.md b/lambda-http/README.md index 4866d071..79c3410d 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -17,7 +17,7 @@ We are able to handle requests from: Thanks to the `Request` type we can seamlessly handle proxy integrations without the worry to specify the specific service type. -There is also an extension for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. +There is also an extension for `lambda_http::Request` structs that provides access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. For example some handy extensions: @@ -40,7 +40,7 @@ Here you will find a few examples to handle basic scenarios: ### Reading a JSON from a body and deserialize into a structure -The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload. +The code below creates a simple API Gateway proxy (HTTP, REST) that accepts in input a JSON payload. ```rust use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, IntoResponse, Request, RequestPayloadExt}; @@ -234,4 +234,4 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. -If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is prevent, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. +If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is present, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. From fde1d37ff678d520e85e3e1d035316e0266df394 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 23 Jan 2024 13:06:30 +0800 Subject: [PATCH 254/394] Tighten lambda-http event deserializer (#788) * Tighten lambda-http event deserializer * Add 'deny_unknown_fields' for alb request * Removed 'deny_unknown_fields' and make request_context mandatory for 'ApiGatewayProxyRequest' and 'ApiGatewayWebsocketProxyRequest' --- lambda-events/src/event/apigw/mod.rs | 9 ++--- .../src/fixtures/example-sqs-event.json | 3 +- lambda-http/src/deserializer.rs | 37 +++++++++++++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 36512f9c..f2cb097a 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequest where - T1: DeserializeOwned, + T1: DeserializeOwned + Default, T1: Serialize, { /// The resource path defined in API Gateway @@ -43,7 +43,6 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, - #[serde(default)] #[serde(bound = "")] pub request_context: ApiGatewayProxyRequestContext, #[serde(default)] @@ -335,9 +334,9 @@ pub struct ApiGatewayRequestIdentity { #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequest where - T1: DeserializeOwned, + T1: DeserializeOwned + Default, T1: Serialize, - T2: DeserializeOwned, + T2: DeserializeOwned + Default, T2: Serialize, { /// The resource path defined in API Gateway @@ -367,7 +366,7 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, - #[serde(bound = "", default)] + #[serde(bound = "")] pub request_context: ApiGatewayWebsocketProxyRequestContext, #[serde(default)] pub body: Option, diff --git a/lambda-events/src/fixtures/example-sqs-event.json b/lambda-events/src/fixtures/example-sqs-event.json index 732b65c1..eea77f84 100644 --- a/lambda-events/src/fixtures/example-sqs-event.json +++ b/lambda-events/src/fixtures/example-sqs-event.json @@ -37,5 +37,4 @@ } } ] -} - +} \ No newline at end of file diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 2584c0ad..1b2008c3 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -56,7 +56,7 @@ mod tests { fn test_deserialize_apigw_rest() { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-request.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw rest data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw rest data"); match req { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("12345678912", req.request_context.account_id.unwrap()); @@ -69,7 +69,7 @@ mod tests { fn test_deserialize_apigw_http() { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-v2-request-iam.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw http data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw http data"); match req { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); @@ -84,7 +84,7 @@ mod tests { "../../lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json" ); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze alb rest data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize alb rest data"); match req { LambdaRequest::Alb(req) => { assert_eq!( @@ -101,7 +101,7 @@ mod tests { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw websocket data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw websocket data"); match req { LambdaRequest::WebSocket(req) => { assert_eq!("CONNECT", req.request_context.event_type.unwrap()); @@ -109,4 +109,33 @@ mod tests { other => panic!("unexpected request variant: {:?}", other), } } + + #[test] + #[cfg(feature = "pass_through")] + fn test_deserialize_bedrock_agent() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json"); + + let req: LambdaRequest = + serde_json::from_slice(data).expect("failed to deserialize bedrock agent request data"); + match req { + LambdaRequest::PassThrough(req) => { + assert_eq!(String::from_utf8_lossy(data), req); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + #[cfg(feature = "pass_through")] + fn test_deserialize_sqs() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-sqs-event.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize sqs event data"); + match req { + LambdaRequest::PassThrough(req) => { + assert_eq!(String::from_utf8_lossy(data), req); + } + other => panic!("unexpected request variant: {:?}", other), + } + } } From b615b162900b8b01fb46568a40c9aa5c103d630d Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 24 Jan 2024 00:24:09 +0800 Subject: [PATCH 255/394] Release lambda-events 0.13.1 (#789) --- lambda-events/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 46ab7c11..894b236f 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.13.0" +version = "0.13.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", From baeaaec58aad8efd426119612ae69cffcef38b7b Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 24 Jan 2024 07:27:29 +0800 Subject: [PATCH 256/394] Release lambda-http 0.9.2 (#790) * Release lambda-http 0.9.2 * Update lambda-events version --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4c84de21..ad48dff0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.1" +version = "0.9.2" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.13" +version = "0.13.1" default-features = false features = ["alb", "apigw"] From 0384c75cbf52772eb02fa83f29a440bb88554f89 Mon Sep 17 00:00:00 2001 From: Tom Keller <1083460+kellertk@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:15:06 -0800 Subject: [PATCH 257/394] chore: a more polite closed issue message (#794) --- .github/workflows/closed-issue-message.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/closed-issue-message.yml b/.github/workflows/closed-issue-message.yml index f295d310..2a73fe92 100644 --- a/.github/workflows/closed-issue-message.yml +++ b/.github/workflows/closed-issue-message.yml @@ -11,7 +11,5 @@ jobs: # These inputs are both required repo-token: "${{ secrets.GITHUB_TOKEN }}" message: | - ### ⚠️COMMENT VISIBILITY WARNING⚠️ - Comments on closed issues are hard for the maintainers of this repository to see. - If you need more assistance, please open a new issue that references this one. - If you wish to keep having a conversation with other community members under this issue feel free to do so. + This issue is now closed. Comments on closed issues are hard for our team to see. + If you need more assistance, please either tag a team member or open a new issue that references this one. From 9f8f2500c99b45f50a4ea866851c484485bce5a2 Mon Sep 17 00:00:00 2001 From: Bryson M <43580701+Bryson14@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:27:00 -0500 Subject: [PATCH 258/394] Update axum documentation to include env var of stage name (#796) This would have been very helpful as I want making a lambda function that recieved paths from api gateway! --- examples/http-axum/src/main.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index c2805be1..cc3600a2 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,7 +6,8 @@ //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. - +use std::env::set_var; +use axum::http::StatusCode; use lambda_http::{ run, Error, @@ -35,8 +36,22 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } +/// Example on how to return status codes and data from a Axum function +async fn health_check() -> (StatusCode, &str) { + let healthy = false; + match health { + true => {(StatusCode::OK, "Healthy!")}, + false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} + } +} + #[tokio::main] async fn main() -> Result<(), Error> { + // AWS Runtime can ignore Stage Name passed from json event + // Remove if you want the first section of the url to be the stage name of the API Gateway + // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` + set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) @@ -49,7 +64,8 @@ async fn main() -> Result<(), Error> { let app = Router::new() .route("/", get(root)) .route("/foo", get(get_foo).post(post_foo)) - .route("/foo/:name", post(post_foo_name)); + .route("/foo/:name", post(post_foo_name)) + .route("/health/", get(health_check)); run(app).await } From 42e4dfc1fe10e5f9e9310450296862914222b020 Mon Sep 17 00:00:00 2001 From: sn99 <42946112+sn99@users.noreply.github.com> Date: Sun, 28 Jan 2024 21:59:20 +0530 Subject: [PATCH 259/394] fix http-axum example code (#799) --- examples/http-axum/src/main.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index cc3600a2..784d4bf1 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -1,24 +1,21 @@ -//! This is an example function that leverages the Lambda Rust runtime's HTTP support +//! This is an example function that leverages the Lambda Rust runtime HTTP support //! and the [axum](https://docs.rs/axum/latest/axum/index.html) web framework. The //! runtime HTTP support is backed by the [tower::Service](https://docs.rs/tower-service/0.3.2/tower_service/trait.Service.html) -//! trait. Axum applications are also backed by the `tower::Service` trait. That means +//! trait. Axum's applications are also backed by the `tower::Service` trait. That means //! that it is fairly easy to build an Axum application and pass the resulting `Service` //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. -use std::env::set_var; use axum::http::StatusCode; -use lambda_http::{ - run, - Error, -}; use axum::{ extract::Path, response::Json, - Router, routing::{get, post}, + Router, }; -use serde_json::{Value, json}; +use lambda_http::{run, Error}; +use serde_json::{json, Value}; +use std::env::set_var; async fn root() -> Json { Json(json!({ "msg": "I am GET /" })) @@ -36,12 +33,15 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } -/// Example on how to return status codes and data from a Axum function -async fn health_check() -> (StatusCode, &str) { - let healthy = false; +/// Example on how to return status codes and data from an Axum function +async fn health_check() -> (StatusCode, String) { + let health = true; match health { - true => {(StatusCode::OK, "Healthy!")}, - false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} + true => (StatusCode::OK, "Healthy!".to_string()), + false => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Not healthy!".to_string(), + ), } } @@ -51,7 +51,7 @@ async fn main() -> Result<(), Error> { // Remove if you want the first section of the url to be the stage name of the API Gateway // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); - + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) From 2652b451401237964c623b75890898a2983b9881 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 29 Jan 2024 09:16:55 +0800 Subject: [PATCH 260/394] Support serialize UTF-8 String in HTTP Header Values (#800) * Support serialize UTF-8 String in HTTP Header Values * Fix issues in http-axum example * Revert "Fix issues in http-axum example" This reverts commit 77ad45a39fa8194bd3deec820ec795fa9a1f928a. * Add a unit test * Add a unit test to cover both 'serialize_headers' and 'serialize_multi_value_headers' --- lambda-events/src/custom_serde/headers.rs | 44 +++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 9cb89e40..2ef3050e 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -13,7 +13,7 @@ where for key in headers.keys() { let mut map_values = Vec::new(); for value in headers.get_all(key) { - map_values.push(value.to_str().map_err(S::Error::custom)?) + map_values.push(String::from_utf8(value.as_bytes().to_vec()).map_err(S::Error::custom)?) } map.serialize_entry(key.as_str(), &map_values)?; } @@ -27,8 +27,8 @@ where { let mut map = serializer.serialize_map(Some(headers.keys_len()))?; for key in headers.keys() { - let map_value = headers[key].to_str().map_err(S::Error::custom)?; - map.serialize_entry(key.as_str(), map_value)?; + let map_value = String::from_utf8(headers[key].as_bytes().to_vec()).map_err(S::Error::custom)?; + map.serialize_entry(key.as_str(), &map_value)?; } map.end() } @@ -187,4 +187,42 @@ mod tests { let decoded: Test = serde_json::from_value(data).unwrap(); assert!(decoded.headers.is_empty()); } + + #[test] + fn test_serialize_utf8_headers() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + } + + let content_disposition = + "inline; filename=\"Schillers schönste Szenenanweisungen -Kabale und Liebe.mp4.avif\""; + let data = serde_json::json!({ + "headers": { + "Content-Disposition": content_disposition + }, + "multi_value_headers": { + "Content-Disposition": content_disposition + } + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + assert_eq!( + content_disposition, + decoded.multi_value_headers.get("Content-Disposition").unwrap() + ); + + let recoded = serde_json::to_value(decoded).unwrap(); + let decoded: Test = serde_json::from_value(recoded).unwrap(); + assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + assert_eq!( + content_disposition, + decoded.multi_value_headers.get("Content-Disposition").unwrap() + ); + } } From a837f3c79a4632b68924e448bd14a8f6b582743c Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Mon, 29 Jan 2024 10:16:51 +0000 Subject: [PATCH 261/394] Adds more tests to investigate #797 (#798) * Adds more tests to investigate https://github.com/awslabs/aws-lambda-rust-runtime/issues/797 * Update deserializer.rs --- .../example-apigw-sam-http-request.json | 52 ++++++++ .../example-apigw-sam-rest-request.json | 111 ++++++++++++++++++ lambda-http/src/deserializer.rs | 26 ++++ 3 files changed, 189 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-sam-http-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-sam-rest-request.json diff --git a/lambda-events/src/fixtures/example-apigw-sam-http-request.json b/lambda-events/src/fixtures/example-apigw-sam-http-request.json new file mode 100644 index 00000000..c60b05a9 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-sam-http-request.json @@ -0,0 +1,52 @@ +{ + "body": "", + "cookies": [], + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Host": "127.0.0.1:3000", + "Pragma": "no-cache", + "Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"", + "Sec-Ch-Ua-Mobile": "?0", + "Sec-Ch-Ua-Platform": "\"macOS\"", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "X-Forwarded-Port": "3000", + "X-Forwarded-Proto": "http" + }, + "isBase64Encoded": false, + "pathParameters": {}, + "queryStringParameters": { + "foo": "bar" + }, + "rawPath": "/dump", + "rawQueryString": "foo=bar", + "requestContext": { + "accountId": "123456789012", + "apiId": "1234567890", + "domainName": "localhost", + "domainPrefix": "localhost", + "http": { + "method": "GET", + "path": "/dump", + "protocol": "HTTP/1.1", + "sourceIp": "127.0.0.1", + "userAgent": "Custom User Agent String" + }, + "requestId": "b3ea2f1a-9f2a-48a4-8791-0db24e5d40e5", + "routeKey": "GET /dump", + "stage": "$default", + "time": "28/Jan/2024:12:09:08 +0000", + "timeEpoch": 1706443748 + }, + "routeKey": "GET /dump", + "stageVariables": null, + "version": "2.0" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-sam-rest-request.json b/lambda-events/src/fixtures/example-apigw-sam-rest-request.json new file mode 100644 index 00000000..d125a7ab --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-sam-rest-request.json @@ -0,0 +1,111 @@ +{ + "body": null, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", + "Cache-Control": "max-age=0", + "Connection": "keep-alive", + "Host": "127.0.0.1:3000", + "Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"", + "Sec-Ch-Ua-Mobile": "?0", + "Sec-Ch-Ua-Platform": "\"macOS\"", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "X-Forwarded-Port": "3000", + "X-Forwarded-Proto": "http" + }, + "httpMethod": "GET", + "isBase64Encoded": false, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + ], + "Accept-Encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-GB,en-US;q=0.9,en;q=0.8" + ], + "Cache-Control": [ + "max-age=0" + ], + "Connection": [ + "keep-alive" + ], + "Host": [ + "127.0.0.1:3000" + ], + "Sec-Ch-Ua": [ + "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"" + ], + "Sec-Ch-Ua-Mobile": [ + "?0" + ], + "Sec-Ch-Ua-Platform": [ + "\"macOS\"" + ], + "Sec-Fetch-Dest": [ + "document" + ], + "Sec-Fetch-Mode": [ + "navigate" + ], + "Sec-Fetch-Site": [ + "none" + ], + "Sec-Fetch-User": [ + "?1" + ], + "Upgrade-Insecure-Requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ], + "X-Forwarded-Port": [ + "3000" + ], + "X-Forwarded-Proto": [ + "http" + ] + }, + "multiValueQueryStringParameters": null, + "path": "/test", + "pathParameters": null, + "queryStringParameters": null, + "requestContext": { + "accountId": "123456789012", + "apiId": "1234567890", + "domainName": "127.0.0.1:3000", + "extendedRequestId": null, + "httpMethod": "GET", + "identity": { + "accountId": null, + "apiKey": null, + "caller": null, + "cognitoAuthenticationProvider": null, + "cognitoAuthenticationType": null, + "cognitoIdentityPoolId": null, + "sourceIp": "127.0.0.1", + "user": null, + "userAgent": "Custom User Agent String", + "userArn": null + }, + "path": "/test", + "protocol": "HTTP/1.1", + "requestId": "ea86b9eb-c688-4bbb-8309-a1671442bea9", + "requestTime": "28/Jan/2024:11:05:46 +0000", + "requestTimeEpoch": 1706439946, + "resourceId": "123456", + "resourcePath": "/test", + "stage": "Prod" + }, + "resource": "/test", + "stageVariables": null, + "version": "1.0" +} \ No newline at end of file diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 1b2008c3..4c0ad519 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -78,6 +78,32 @@ mod tests { } } + #[test] + fn test_deserialize_sam_rest() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-sam-rest-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize SAM rest data"); + match req { + LambdaRequest::ApiGatewayV1(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_sam_http() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-sam-http-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize SAM http data"); + match req { + LambdaRequest::ApiGatewayV2(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + #[test] fn test_deserialize_alb() { let data = include_bytes!( From 1face57dfef9576b67e73ab3b32c2ae573115a3f Mon Sep 17 00:00:00 2001 From: Ivan Kiselev <10528835+ivan-kiselev@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:09:18 +0100 Subject: [PATCH 262/394] Make trigger reason for Cognito events Enum instead of String (#801) * Make trigger reason for cognito enums instead of strings * add module that tests each individual trigger source --- lambda-events/src/event/cognito/mod.rs | 305 +++++++++++++++++- ...cognito-event-userpools-custommessage.json | 31 +- ...-event-userpools-pretokengen-incoming.json | 9 +- ...ent-userpools-pretokengen-v2-incoming.json | 56 ++-- ...ognito-event-userpools-pretokengen-v2.json | 104 +++--- ...e-cognito-event-userpools-pretokengen.json | 15 +- 6 files changed, 404 insertions(+), 116 deletions(-) diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index decc31a5..95782ffc 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -44,11 +44,22 @@ pub struct CognitoDatasetRecord { pub struct CognitoEventUserPoolsPreSignup { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreSignupRequest, pub response: CognitoEventUserPoolsPreSignupResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreSignupTriggerSource { + #[serde(rename = "PreSignUp_SignUp")] + #[default] + SignUp, + #[serde(rename = "PreSignUp_AdminCreateUser")] + AdminCreateUser, + #[serde(rename = "PreSignUp_ExternalProvider")] + ExternalProvider, +} + /// `CognitoEventUserPoolsPreAuthentication` is sent by AWS Cognito User Pools when a user submits their information /// to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -56,11 +67,19 @@ pub struct CognitoEventUserPoolsPreSignup { pub struct CognitoEventUserPoolsPreAuthentication { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreAuthenticationRequest, pub response: CognitoEventUserPoolsPreAuthenticationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { + #[serde(rename = "PreAuthentication_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsPostConfirmation` is sent by AWS Cognito User Pools after a user is confirmed, /// allowing the Lambda to send custom messages or add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -68,11 +87,21 @@ pub struct CognitoEventUserPoolsPreAuthentication { pub struct CognitoEventUserPoolsPostConfirmation { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostConfirmationRequest, pub response: CognitoEventUserPoolsPostConfirmationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { + #[serde(rename = "PostConfirmation_ConfirmForgotPassword")] + ConfirmForgotPassword, + #[serde(rename = "PostConfirmation_ConfirmSignUp")] + #[default] + ConfirmSignUp, +} + /// `CognitoEventUserPoolsPreTokenGen` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -80,11 +109,26 @@ pub struct CognitoEventUserPoolsPostConfirmation { pub struct CognitoEventUserPoolsPreTokenGen { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequest, pub response: CognitoEventUserPoolsPreTokenGenResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { + #[serde(rename = "TokenGeneration_HostedAuth")] + HostedAuth, + #[serde(rename = "TokenGeneration_Authentication")] + #[default] + Authentication, + #[serde(rename = "TokenGeneration_NewPasswordChallenge")] + NewPasswordChallenge, + #[serde(rename = "TokenGeneration_AuthenticateDevice")] + AuthenticateDevice, + #[serde(rename = "TokenGeneration_RefreshTokens")] + RefreshTokens, +} + /// `CognitoEventUserPoolsPostAuthentication` is sent by AWS Cognito User Pools after a user is authenticated, /// allowing the Lambda to add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,11 +136,19 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub struct CognitoEventUserPoolsPostAuthentication { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostAuthenticationRequest, pub response: CognitoEventUserPoolsPostAuthenticationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { + #[serde(rename = "PostAuthentication_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsMigrateUser` is sent by AWS Cognito User Pools when a user does not exist in the /// user pool at the time of sign-in with a password, or in the forgot-password flow. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -104,13 +156,22 @@ pub struct CognitoEventUserPoolsPostAuthentication { pub struct CognitoEventUserPoolsMigrateUser { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, #[serde(rename = "request")] pub cognito_event_user_pools_migrate_user_request: CognitoEventUserPoolsMigrateUserRequest, #[serde(rename = "response")] pub cognito_event_user_pools_migrate_user_response: CognitoEventUserPoolsMigrateUserResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsMigrateUserTriggerSource { + #[serde(rename = "UserMigration_Authentication")] + #[default] + Authentication, + #[serde(rename = "UserMigration_ForgotPassword")] + ForgotPassword, +} + /// `CognitoEventUserPoolsCallerContext` contains information about the caller #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -125,11 +186,11 @@ pub struct CognitoEventUserPoolsCallerContext { /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CognitoEventUserPoolsHeader { +pub struct CognitoEventUserPoolsHeader { #[serde(default)] pub version: Option, #[serde(default)] - pub trigger_source: Option, + pub trigger_source: Option, #[serde(default)] pub region: Option, #[serde(default)] @@ -220,7 +281,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { pub struct CognitoEventUserPoolsPreTokenGenV2 { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequestV2, pub response: CognitoEventUserPoolsPreTokenGenResponseV2, } @@ -384,11 +445,19 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { pub struct CognitoEventUserPoolsDefineAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { + #[serde(rename = "DefineAuthChallenge_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -426,11 +495,19 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { pub struct CognitoEventUserPoolsCreateAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { + #[serde(rename = "CreateAuthChallenge_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -469,11 +546,19 @@ pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { pub struct CognitoEventUserPoolsVerifyAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { + #[serde(rename = "VerifyAuthChallengeResponse_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsCustomMessage` is sent by AWS Cognito User Pools before a verification or MFA message is sent, /// allowing a user to customize the message dynamically. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -481,11 +566,30 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { pub struct CognitoEventUserPoolsCustomMessage { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCustomMessageRequest, pub response: CognitoEventUserPoolsCustomMessageResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsCustomMessageTriggerSource { + #[serde(rename = "CustomMessage_SignUp")] + #[default] + SignUp, + #[serde(rename = "CustomMessage_AdminCreateUser")] + AdminCreateUser, + #[serde(rename = "CustomMessage_ResendCode")] + ResendCode, + #[serde(rename = "CustomMessage_ForgotPassword")] + ForgotPassword, + #[serde(rename = "CustomMessage_UpdateUserAttribute")] + UpdateUserAttribute, + #[serde(rename = "CustomMessage_VerifyUserAttribute")] + VerifyUserAttribute, + #[serde(rename = "CustomMessage_Authentication")] + Authentication, +} + /// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -736,3 +840,178 @@ mod test { assert_eq!(parsed, reparsed); } } + +#[cfg(test)] +#[cfg(feature = "cognito")] +mod trigger_source_tests { + use super::*; + + fn gen_header(trigger_source: &str) -> String { + format!( + r#" +{{ + "version": "1", + "triggerSource": "{trigger_source}", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": {{ + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }} +}}"# + ) + } + + #[test] + fn pre_sign_up() { + let possible_triggers = [ + "PreSignUp_AdminCreateUser", + "PreSignUp_AdminCreateUser", + "PreSignUp_ExternalProvider", + ]; + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + + #[test] + fn pre_authentication() { + let possible_triggers = ["PreAuthentication_Authentication"]; + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn post_confirmation() { + let possible_triggers = [ + "PostConfirmation_ConfirmForgotPassword", + "PostConfirmation_ConfirmSignUp", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn post_authentication() { + let possible_triggers = ["PostAuthentication_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn define_auth_challenge() { + let possible_triggers = ["DefineAuthChallenge_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + + #[test] + fn create_auth_challenge() { + let possible_triggers = ["CreateAuthChallenge_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn verify_auth_challenge() { + let possible_triggers = ["VerifyAuthChallengeResponse_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn pre_token_generation() { + let possible_triggers = [ + "TokenGeneration_HostedAuth", + "TokenGeneration_Authentication", + "TokenGeneration_NewPasswordChallenge", + "TokenGeneration_AuthenticateDevice", + "TokenGeneration_RefreshTokens", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn user_migration() { + let possible_triggers = ["UserMigration_Authentication", "UserMigration_ForgotPassword"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn custom_message() { + let possible_triggers = [ + "CustomMessage_SignUp", + "CustomMessage_AdminCreateUser", + "CustomMessage_ResendCode", + "CustomMessage_ForgotPassword", + "CustomMessage_UpdateUserAttribute", + "CustomMessage_VerifyUserAttribute", + "CustomMessage_Authentication", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json index 90e8b68e..8b2ca55d 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json @@ -1,28 +1,27 @@ { "version": "1", - "triggerSource": "CustomMessage_SignUp/CustomMessage_ResendCode/CustomMessage_ForgotPassword/CustomMessage_VerifyUserAttribute", + "triggerSource": "CustomMessage_VerifyUserAttribute", "region": "", "userPoolId": "", "userName": "", "callerContext": { - "awsSdkVersion": "", - "clientId": "" + "awsSdkVersion": "", + "clientId": "" }, "request": { - "userAttributes": { - "phone_number_verified": true, - "email_verified": false - }, - "codeParameter": "####", - "usernameParameter": "{username}", - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } + "userAttributes": { + "phone_number_verified": true, + "email_verified": false + }, + "codeParameter": "####", + "usernameParameter": "{username}", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } }, "response": { - "smsMessage": "", - "emailMessage": "", - "emailSubject": "" + "smsMessage": "", + "emailMessage": "", + "emailSubject": "" } } - diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json index fed10a51..ae78b7c7 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json @@ -1,6 +1,6 @@ { "version": "1", - "triggerSource": "PreTokenGen", + "triggerSource": "TokenGeneration_Authentication", "region": "region", "userPoolId": "userPoolId", "userName": "userName", @@ -15,7 +15,11 @@ }, "groupConfiguration": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, "clientMetadata": { @@ -26,4 +30,3 @@ "claimsOverrideDetails": null } } - diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json index 3376d6e0..e5c776d8 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json @@ -1,33 +1,33 @@ { - "version": "1", - "triggerSource": "PreTokenGen", - "region": "region", - "userPoolId": "userPoolId", - "userName": "userName", - "callerContext": { - "awsSdkVersion": "calling aws sdk with version", - "clientId": "apps client id" + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" }, - "request": { - "userAttributes": { - "email": "email", - "phone_number": "phone_number" - }, - "scopes": ["scope-1", "scope-2"], - "groupConfiguration": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" - }, - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, - "response": { - "claimsOverrideDetails": null + "clientMetadata": { + "exampleMetadataKey": "example metadata value" } + }, + "response": { + "claimsOverrideDetails": null + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json index f7ccfe2f..6046d446 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json @@ -1,58 +1,58 @@ { - "version": "1", - "triggerSource": "PreTokenGen", - "region": "region", - "userPoolId": "userPoolId", - "userName": "userName", - "callerContext": { - "awsSdkVersion": "calling aws sdk with version", - "clientId": "apps client id" + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" }, - "request": { - "userAttributes": { - "email": "email", - "phone_number": "phone_number" + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsAndScopeOverrideDetails": { + "idTokenGeneration": { + "claimsToAddOrOverride": { + "string": "string" }, - "scopes": ["scope-1", "scope-2"], - "groupConfiguration": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + "claimsToSuppress": ["string", "string"] + }, + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" }, - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } - }, - "response": { - "claimsAndScopeOverrideDetails": { - "idTokenGeneration": { - "claimsToAddOrOverride": { - "string": "string" - }, - "claimsToSuppress": ["string", "string"] - }, - "accessTokenGeneration": { - "claimsToAddOrOverride": { - "attribute_key2": "attribute_value2", - "attribute_key": "attribute_value" - }, - "claimsToSuppress": ["email", "phone"], - "scopesToAdd": ["scope-B", "scope-B"], - "scopesToSuppress": ["scope-C", "scope-D"] - }, - "groupOverrideDetails": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" - } - } + "claimsToSuppress": ["email", "phone"], + "scopesToAdd": ["scope-B", "scope-B"], + "scopesToSuppress": ["scope-C", "scope-D"] + }, + "groupOverrideDetails": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } } + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json index 7b851904..f79e8573 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json @@ -1,6 +1,6 @@ { "version": "1", - "triggerSource": "PreTokenGen", + "triggerSource": "TokenGeneration_HostedAuth", "region": "region", "userPoolId": "userPoolId", "userName": "userName", @@ -15,7 +15,11 @@ }, "groupConfiguration": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, "clientMetadata": { @@ -31,10 +35,13 @@ "claimsToSuppress": ["email"], "groupOverrideDetails": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" } } } } - From e79183bc03478b1fa98e02625844357d4f319705 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 31 Jan 2024 07:57:06 -0800 Subject: [PATCH 263/394] Add assertions about multi value query parameters. (#803) * Add assertions about multi value query parameters. Signed-off-by: David Calavera * Add test for Axum Query extractor. Use the axum_extra package as it seems to be more correct: https://github.com/tokio-rs/axum/blob/main/axum-extra/src/extract/query.rs Signed-off-by: David Calavera * Update MSRV because Axum doesn't support our MSRV. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-http/Cargo.toml | 2 + lambda-http/src/request.rs | 99 +++++++++++++++-- .../tests/data/alb_multi_value_request.json | 103 ++++++++++++------ .../data/apigw_multi_value_proxy_request.json | 48 ++++---- 8 files changed, 185 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index caee1131..d9f5c72a 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 7165a281..d9bcc989 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 026c20e0..25cd83ec 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index eeeaf2fa..43c38586 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.65, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.66, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ad48dff0..1b9cd693 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -50,6 +50,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] +axum-core = "0.4.3" +axum-extra = { version = "0.9.2", features = ["query"] } lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index c98f5c17..bce2e3d3 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -570,6 +570,9 @@ mod tests { matches!(req_context, &RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {req_context:?}" ); + + let (parts, _) = req.into_parts(); + assert_eq!("https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value", parts.uri.to_string()); } #[test] @@ -699,12 +702,16 @@ mod tests { .is_empty()); // test RequestExt#query_string_parameters_ref does the right thing - assert_eq!( - request - .query_string_parameters_ref() - .and_then(|params| params.all("multivalueName")), - Some(vec!["you", "me"]) - ); + let params = request.query_string_parameters(); + assert_eq!(Some(vec!["you", "me"]), params.all("multiValueName")); + assert_eq!(Some(vec!["me"]), params.all("name")); + + let query = request.uri().query().unwrap(); + assert!(query.contains("name=me")); + assert!(query.contains("multiValueName=you&multiValueName=me")); + let (parts, _) = request.into_parts(); + assert!(parts.uri.to_string().contains("name=me")); + assert!(parts.uri.to_string().contains("multiValueName=you&multiValueName=me")); } #[test] @@ -724,12 +731,13 @@ mod tests { .is_empty()); // test RequestExt#query_string_parameters_ref does the right thing - assert_eq!( - request - .query_string_parameters_ref() - .and_then(|params| params.all("myKey")), - Some(vec!["val1", "val2"]) - ); + let params = request.query_string_parameters(); + assert_eq!(Some(vec!["val1", "val2"]), params.all("myKey")); + assert_eq!(Some(vec!["val3", "val4"]), params.all("myOtherKey")); + + let query = request.uri().query().unwrap(); + assert!(query.contains("myKey=val1&myKey=val2")); + assert!(query.contains("myOtherKey=val3&myOtherKey=val4")); } #[test] @@ -845,4 +853,71 @@ mod tests { assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path")); assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path")); } + + #[tokio::test] + #[cfg(feature = "apigw_rest")] + async fn test_axum_query_extractor_apigw_rest() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + // from docs + // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Params { + name: Vec, + multi_value_name: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["me"], query.0.name); + assert_eq!(vec!["you", "me"], query.0.multi_value_name); + } + + #[tokio::test] + #[cfg(feature = "apigw_http")] + async fn test_axum_query_extractor_apigw_http() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + let input = include_str!("../tests/data/apigw_v2_proxy_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + struct Params { + parameter1: Vec, + parameter2: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["value1", "value2"], query.0.parameter1); + assert_eq!(vec!["value"], query.0.parameter2); + } + + #[tokio::test] + #[cfg(feature = "alb")] + async fn test_axum_query_extractor_alb() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + let input = include_str!("../tests/data/alb_multi_value_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Params { + my_key: Vec, + my_other_key: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["val1", "val2"], query.0.my_key); + assert_eq!(vec!["val3", "val4"], query.0.my_other_key); + } } diff --git a/lambda-http/tests/data/alb_multi_value_request.json b/lambda-http/tests/data/alb_multi_value_request.json index 10cf1155..49edfd23 100644 --- a/lambda-http/tests/data/alb_multi_value_request.json +++ b/lambda-http/tests/data/alb_multi_value_request.json @@ -1,37 +1,70 @@ { - "requestContext": { - "elb": { - "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" - } - }, - "httpMethod": "GET", - "path": "/", - "queryStringParameters": { "myKey": "val2" }, - "multiValueQueryStringParameters": { "myKey": ["val1", "val2"] }, - "headers": { - "accept": "text/html,application/xhtml+xml", - "accept-language": "en-US,en;q=0.8", - "content-type": "text/plain", - "cookie": "name1=value1", - "host": "lambda-846800462-us-east-2.elb.amazonaws.com", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", - "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", - "x-forwarded-for": "72.21.198.66", - "x-forwarded-port": "443", - "x-forwarded-proto": "https" - }, - "multiValueHeaders": { - "accept": ["text/html,application/xhtml+xml"], - "accept-language": ["en-US,en;q=0.8"], - "content-type": ["text/plain"], - "cookie": ["name1=value1", "name2=value2"], - "host": ["lambda-846800462-us-east-2.elb.amazonaws.com"], - "user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)"], - "x-amzn-trace-id": ["Root=1-5bdb40ca-556d8b0c50dc66f0511bf520"], - "x-forwarded-for": ["72.21.198.66"], - "x-forwarded-port": ["443"], - "x-forwarded-proto": ["https"] - }, - "isBase64Encoded": false, - "body": "request_body" + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { + "myKey": "val2", + "myOtherKey": "val3" + }, + "multiValueQueryStringParameters": { + "myKey": [ + "val1", + "val2" + ], + "myOtherKey": [ + "val3", + "val4" + ] + }, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "name1=value1", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "multiValueHeaders": { + "accept": [ + "text/html,application/xhtml+xml" + ], + "accept-language": [ + "en-US,en;q=0.8" + ], + "content-type": [ + "text/plain" + ], + "cookie": [ + "name1=value1", + "name2=value2" + ], + "host": [ + "lambda-846800462-us-east-2.elb.amazonaws.com" + ], + "user-agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)" + ], + "x-amzn-trace-id": [ + "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520" + ], + "x-forwarded-for": [ + "72.21.198.66" + ], + "x-forwarded-port": [ + "443" + ], + "x-forwarded-proto": [ + "https" + ] + }, + "isBase64Encoded": false, + "body": "request_body" } \ No newline at end of file diff --git a/lambda-http/tests/data/apigw_multi_value_proxy_request.json b/lambda-http/tests/data/apigw_multi_value_proxy_request.json index 8f84aeb9..280f49cb 100644 --- a/lambda-http/tests/data/apigw_multi_value_proxy_request.json +++ b/lambda-http/tests/data/apigw_multi_value_proxy_request.json @@ -23,74 +23,74 @@ "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, - "multiValueHeaders":{ - "Accept":[ + "multiValueHeaders": { + "Accept": [ "*/*" ], - "Accept-Encoding":[ + "Accept-Encoding": [ "gzip, deflate" ], - "cache-control":[ + "cache-control": [ "no-cache" ], - "CloudFront-Forwarded-Proto":[ + "CloudFront-Forwarded-Proto": [ "https" ], - "CloudFront-Is-Desktop-Viewer":[ + "CloudFront-Is-Desktop-Viewer": [ "true" ], - "CloudFront-Is-Mobile-Viewer":[ + "CloudFront-Is-Mobile-Viewer": [ "false" ], - "CloudFront-Is-SmartTV-Viewer":[ + "CloudFront-Is-SmartTV-Viewer": [ "false" ], - "CloudFront-Is-Tablet-Viewer":[ + "CloudFront-Is-Tablet-Viewer": [ "false" ], - "CloudFront-Viewer-Country":[ + "CloudFront-Viewer-Country": [ "US" ], - "Content-Type":[ + "Content-Type": [ "application/json" ], - "headerName":[ + "headerName": [ "headerValue" ], - "Host":[ + "Host": [ "gy415nuibc.execute-api.us-east-1.amazonaws.com" ], - "Postman-Token":[ + "Postman-Token": [ "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" ], - "User-Agent":[ + "User-Agent": [ "PostmanRuntime/2.4.5" ], - "Via":[ + "Via": [ "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" ], - "X-Amz-Cf-Id":[ + "X-Amz-Cf-Id": [ "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" ], - "X-Forwarded-For":[ + "X-Forwarded-For": [ "54.240.196.186, 54.182.214.83" ], - "X-Forwarded-Port":[ + "X-Forwarded-Port": [ "443" ], - "X-Forwarded-Proto":[ + "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": { "name": "me", - "multivalueName": "me" + "multiValueName": "me" }, - "multiValueQueryStringParameters":{ - "name":[ + "multiValueQueryStringParameters": { + "name": [ "me" ], - "multivalueName":[ + "multiValueName": [ "you", "me" ] From 4824d24c2d3fe2d3faefee326f84cc52c91814ac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 31 Jan 2024 18:36:43 -0800 Subject: [PATCH 264/394] Add example route for Axum query extractors. (#804) This helps us verify the query integration with Axum. Signed-off-by: David Calavera --- examples/http-axum/Cargo.toml | 1 + examples/http-axum/src/main.rs | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 88df4140..5257bdb0 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" axum = "0.7" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 784d4bf1..ae7b0921 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,6 +6,7 @@ //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. +use axum::extract::Query; use axum::http::StatusCode; use axum::{ extract::Path, @@ -14,9 +15,16 @@ use axum::{ Router, }; use lambda_http::{run, Error}; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::env::set_var; +#[derive(Deserialize, Serialize)] +struct Params { + first: Option, + second: Option, +} + async fn root() -> Json { Json(json!({ "msg": "I am GET /" })) } @@ -33,22 +41,26 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } +async fn get_parameters(Query(params): Query) -> Json { + Json(json!({ "request parameters": params })) +} + /// Example on how to return status codes and data from an Axum function async fn health_check() -> (StatusCode, String) { let health = true; match health { true => (StatusCode::OK, "Healthy!".to_string()), - false => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Not healthy!".to_string(), - ), + false => (StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!".to_string()), } } #[tokio::main] async fn main() -> Result<(), Error> { - // AWS Runtime can ignore Stage Name passed from json event - // Remove if you want the first section of the url to be the stage name of the API Gateway + // If you use API Gateway stages, the Rust Runtime will include the stage name + // as part of the path that your application receives. + // Setting the following environment variable, you can remove the stage from the path. + // This variable only applies to API Gateway stages, + // you can remove it if you don't use them. // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); @@ -65,6 +77,7 @@ async fn main() -> Result<(), Error> { .route("/", get(root)) .route("/foo", get(get_foo).post(post_foo)) .route("/foo/:name", post(post_foo_name)) + .route("/parameters", get(get_parameters)) .route("/health/", get(health_check)); run(app).await From 11d5669542d38ba2e1b5aa2d64b8e445d93718ea Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 5 Feb 2024 18:34:43 -0800 Subject: [PATCH 265/394] Run tests on examples (#810) Some examples include tests, run them to verify that they are correct. --- examples/check-examples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/check-examples.sh b/examples/check-examples.sh index f55bced9..36e3038e 100755 --- a/examples/check-examples.sh +++ b/examples/check-examples.sh @@ -10,7 +10,7 @@ for f in *; do if [ -d "$f" ]; then echo "==> Checking example: $f" cd $f - cargo check + cargo test cd .. fi done From 9ed543c2af5cbe728d91c7d1f2ca7895152b0ff8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 6 Feb 2024 09:22:59 -0800 Subject: [PATCH 266/394] Add CDK stack example (#807) Signed-off-by: David Calavera --- examples/http-axum/cdk/.gitignore | 8 + examples/http-axum/cdk/.npmignore | 6 + examples/http-axum/cdk/README.md | 40 + examples/http-axum/cdk/bin/cdk.ts | 21 + examples/http-axum/cdk/cdk.json | 65 + examples/http-axum/cdk/lib/cdk-stack.ts | 26 + examples/http-axum/cdk/package-lock.json | 7632 ++++++++++++++++++++++ examples/http-axum/cdk/package.json | 28 + examples/http-axum/cdk/tsconfig.json | 31 + 9 files changed, 7857 insertions(+) create mode 100644 examples/http-axum/cdk/.gitignore create mode 100644 examples/http-axum/cdk/.npmignore create mode 100644 examples/http-axum/cdk/README.md create mode 100644 examples/http-axum/cdk/bin/cdk.ts create mode 100644 examples/http-axum/cdk/cdk.json create mode 100644 examples/http-axum/cdk/lib/cdk-stack.ts create mode 100644 examples/http-axum/cdk/package-lock.json create mode 100644 examples/http-axum/cdk/package.json create mode 100644 examples/http-axum/cdk/tsconfig.json diff --git a/examples/http-axum/cdk/.gitignore b/examples/http-axum/cdk/.gitignore new file mode 100644 index 00000000..f60797b6 --- /dev/null +++ b/examples/http-axum/cdk/.gitignore @@ -0,0 +1,8 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/http-axum/cdk/.npmignore b/examples/http-axum/cdk/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/examples/http-axum/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/http-axum/cdk/README.md b/examples/http-axum/cdk/README.md new file mode 100644 index 00000000..902f576e --- /dev/null +++ b/examples/http-axum/cdk/README.md @@ -0,0 +1,40 @@ +# Axum HTTP CDK Stack + +This is a basic stack that shows how to deploy the Axum HTTP example with the AWS CDK. + +## Resources + +This stack deploys the Axum HTTP example in AWS Lambda. + +It also creates an API Gateway Rest API to expose the Axum app to the internet. When the deploy is completed, the stack will print the endpoint URL for the gateway. It will look something like this: + +``` +CdkStack.axumEndpointC1B330D3 = https://sr0e4dqg1b.execute-api.us-east-1.amazonaws.com/prod/ +``` + +If you set the environment variable `ENABLE_LAMBDA_RUST_AXUM_FUNCTION_URL=true` in your terminal before deploying the stack, it will also create a Lambda Function URL without any authentication mode. When the deploy completes, the stack will print the endpoint URL for this function. It will look something like this: + +``` +CdkStack.AxumFunctionUrl = https://7st53uq3rpk4jweki2ek765gty0icvuf.lambda-url.us-east-1.on.aws/ +``` + +## Dependencies + +1. Install the AWS CDK with NPM: `npm install -g cdk`. +2. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) + +## Deployment + +Run `npm run build` to complile the stack. + +Then, run `npm run cdk deploy` to deploy the stack on your AWS account. + +## Security + +This example doesn't provide any security configuration. It's up to you to configure the stack with the security settings that are more convenient to you. We're not responsible for resources open to the internet on your AWS account. + +## Cleanup + +Deploying this stack on your account might incur on AWS costs due to the resources that we're deploying. Don't forget to delete those resources from your account if you're not using them any longer. + +Run `npm run cdk destroy` to delete all resources in this stack from your AWS account. diff --git a/examples/http-axum/cdk/bin/cdk.ts b/examples/http-axum/cdk/bin/cdk.ts new file mode 100644 index 00000000..1d8bfd97 --- /dev/null +++ b/examples/http-axum/cdk/bin/cdk.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { CdkStack } from '../lib/cdk-stack'; + +const app = new cdk.App(); +new CdkStack(app, 'CdkStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/examples/http-axum/cdk/cdk.json b/examples/http-axum/cdk/cdk.json new file mode 100644 index 00000000..7dc211da --- /dev/null +++ b/examples/http-axum/cdk/cdk.json @@ -0,0 +1,65 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true + } +} diff --git a/examples/http-axum/cdk/lib/cdk-stack.ts b/examples/http-axum/cdk/lib/cdk-stack.ts new file mode 100644 index 00000000..a0cc27a6 --- /dev/null +++ b/examples/http-axum/cdk/lib/cdk-stack.ts @@ -0,0 +1,26 @@ +import { join } from 'path'; +import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { RustFunction } from 'cargo-lambda-cdk'; +import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway' +import { FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda"; + +export class CdkStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const handler = new RustFunction(this, 'Axum API', { + // Path to the http-axum root directory. + manifestPath: join(__dirname, '..', '..'), + }); + + if (process.env.ENABLE_LAMBDA_RUST_AXUM_FUNCTION_URL) { + const lambdaUrl = handler.addFunctionUrl({ + authType: FunctionUrlAuthType.NONE, + }); + new CfnOutput(this, 'Axum FunctionUrl ', { value: lambdaUrl.url }); + } + + new LambdaRestApi(this, 'axum', { handler }); + } +} diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json new file mode 100644 index 00000000..c35c0bf4 --- /dev/null +++ b/examples/http-axum/cdk/package-lock.json @@ -0,0 +1,7632 @@ +{ + "name": "cdk", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cdk", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "^2.126.0", + "cargo-lambda-cdk": "^0.0.17", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "cdk": "bin/cdk.js" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "20.11.14", + "aws-cdk": "2.126.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "~5.3.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", + "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.11.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz", + "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aws-cdk": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.126.0.tgz", + "integrity": "sha512-hEyy8UCEEUnkieH6JbJBN8XAbvuVZNdBmVQ8wHCqo8RSNqmpwM1qvLiyXV/2JvCqJJ0bl9uBiZ98Ytd5i3wW7g==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", + "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.5.4", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.12.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.5.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.1", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001583", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", + "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cargo-lambda-cdk": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.17.tgz", + "integrity": "sha512-KV95oV4GdczNioji8EvHbYumsArdGRusXsNhfRSBIoE94WN8jlxySaUzboNiH+HEKshyUQtnvdQd/WLBdk5NDA==", + "bundleDependencies": [ + "js-toml" + ], + "dependencies": { + "js-toml": "^0.1.1" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.1.0", + "constructs": "^10.0.5" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@babel/runtime-corejs3": { + "version": "7.22.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/gast": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/types": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/utils": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/chevrotain": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/core-js-pure": { + "version": "3.32.2", + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/js-toml": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/lodash": { + "version": "4.17.21", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regenerator-runtime": { + "version": "0.14.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regexp-to-ast": { + "version": "0.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/xregexp": { + "version": "5.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.656", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", + "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", + "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "requires": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "requires": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + } + }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + } + }, + "@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/node": { + "version": "20.11.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz", + "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aws-cdk": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.126.0.tgz", + "integrity": "sha512-hEyy8UCEEUnkieH6JbJBN8XAbvuVZNdBmVQ8wHCqo8RSNqmpwM1qvLiyXV/2JvCqJJ0bl9uBiZ98Ytd5i3wW7g==", + "dev": true, + "requires": { + "fsevents": "2.3.2" + } + }, + "aws-cdk-lib": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", + "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.5.4", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true + }, + "ajv": { + "version": "8.12.0", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fs-extra": { + "version": "11.2.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "ignore": { + "version": "5.3.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "bundled": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", + "bundled": true + }, + "semver": { + "version": "7.5.4", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slice-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.8.1", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "bundled": true + }, + "uri-js": { + "version": "4.4.1", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true + }, + "yaml": { + "version": "1.10.2", + "bundled": true + } + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001583", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", + "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "dev": true + }, + "cargo-lambda-cdk": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.17.tgz", + "integrity": "sha512-KV95oV4GdczNioji8EvHbYumsArdGRusXsNhfRSBIoE94WN8jlxySaUzboNiH+HEKshyUQtnvdQd/WLBdk5NDA==", + "requires": { + "js-toml": "^0.1.1" + }, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.22.15", + "bundled": true, + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } + }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "bundled": true + }, + "@chevrotain/utils": { + "version": "10.5.0", + "bundled": true + }, + "chevrotain": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "core-js-pure": { + "version": "3.32.2", + "bundled": true + }, + "js-toml": { + "version": "0.1.1", + "bundled": true, + "requires": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "lodash": { + "version": "4.17.21", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.14.0", + "bundled": true + }, + "regexp-to-ast": { + "version": "0.5.0", + "bundled": true + }, + "xregexp": { + "version": "5.1.1", + "bundled": true, + "requires": { + "@babel/runtime-corejs3": "^7.16.5" + } + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "requires": {} + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.656", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", + "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + } + }, + "jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "requires": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + } + }, + "jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json new file mode 100644 index 00000000..9117d19f --- /dev/null +++ b/examples/http-axum/cdk/package.json @@ -0,0 +1,28 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "20.11.14", + "aws-cdk": "2.126.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "~5.3.3" + }, + "dependencies": { + "aws-cdk-lib": "^2.126.0", + "cargo-lambda-cdk": "^0.0.17", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} diff --git a/examples/http-axum/cdk/tsconfig.json b/examples/http-axum/cdk/tsconfig.json new file mode 100644 index 00000000..aaa7dc51 --- /dev/null +++ b/examples/http-axum/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} From 94b33e864e94a1dde1462a9ad4bc72d4213a00b1 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Thu, 8 Feb 2024 12:10:59 +0900 Subject: [PATCH 267/394] Add SecretsManagerSecretRotationEvent (#811) --- Makefile | 1 + lambda-events/Cargo.toml | 2 ++ lambda-events/src/event/mod.rs | 4 ++++ lambda-events/src/event/secretsmanager/mod.rs | 24 +++++++++++++++++++ ...-secretsmanager-secret-rotation-event.json | 5 ++++ lambda-events/src/lib.rs | 4 ++++ 6 files changed, 40 insertions(+) create mode 100644 lambda-events/src/event/secretsmanager/mod.rs create mode 100644 lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json diff --git a/Makefile b/Makefile index f66ee1fd..ecfd7623 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features rabbitmq cargo test --package aws_lambda_events --no-default-features --features s3 cargo test --package aws_lambda_events --no-default-features --features s3_batch_job + cargo test --package aws_lambda_events --no-default-features --features secretsmanager cargo test --package aws_lambda_events --no-default-features --features ses cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 894b236f..c6556b64 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -75,6 +75,7 @@ default = [ "s3", "s3_batch_job", "ses", + "secretsmanager", "sns", "sqs", "streams", @@ -119,6 +120,7 @@ lex = [] rabbitmq = [] s3 = ["bytes", "chrono", "http", "http-body", "http-serde"] s3_batch_job = ["s3"] +secretsmanager = [] ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index ea0ca7df..d63acc4d 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -133,6 +133,10 @@ pub mod rabbitmq; #[cfg(feature = "s3")] pub mod s3; +/// AWS Lambda event definitions for secretsmanager. +#[cfg(feature = "secretsmanager")] +pub mod secretsmanager; + /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] pub mod ses; diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs new file mode 100644 index 00000000..3ed8d238 --- /dev/null +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SecretsManagerSecretRotationEvent { + pub step: String, + pub secret_id: String, + pub client_request_token: String, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "secretsmanager")] + fn example_secretsmanager_secret_rotation_event() { + let data = include_bytes!("../../fixtures/example-secretsmanager-secret-rotation-event.json"); + let parsed: SecretsManagerSecretRotationEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SecretsManagerSecretRotationEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json b/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json new file mode 100644 index 00000000..f137ddbc --- /dev/null +++ b/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json @@ -0,0 +1,5 @@ +{ + "Step": "createSecret", + "SecretId": "arn:aws:secretsmanager:us-east-1:111122223333:secret:id-ABCD1E", + "ClientRequestToken": "1ab23456-cde7-8912-34fg-h56i78j9k12l" +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 4fd294e6..e21cdc13 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -153,6 +153,10 @@ pub use event::s3; #[cfg(feature = "s3")] pub use event::s3::batch_job as s3_batch_job; +/// AWS Lambda event definitions for secretsmanager. +#[cfg(feature = "secretsmanager")] +pub use event::secretsmanager; + /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] pub use event::ses; From 7bf39b13a2cdddb207947b2a58b12a0e5fff86ba Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Sun, 11 Feb 2024 00:22:22 +0900 Subject: [PATCH 268/394] Fix typo in types.rs (#815) --- lambda-runtime/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 85f6af78..3d89e0a0 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -109,7 +109,7 @@ impl Default for Context { } impl Context { - /// Create a new [Context] struct based on the fuction configuration + /// Create a new [Context] struct based on the function configuration /// and the incoming request data. pub fn new(request_id: &str, env_config: RefConfig, headers: &HeaderMap) -> Result { let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { From 3567f2808b06dd6bc6e426fa6b78ee79cf6d8492 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Mon, 12 Feb 2024 03:26:13 +0900 Subject: [PATCH 269/394] Add websocket event fields for route (#816) --- lambda-events/src/event/apigw/mod.rs | 14 ++++++++ ...gw-websocket-request-disconnect-route.json | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index f2cb097a..22c508b2 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -433,6 +433,10 @@ where pub route_key: Option, #[serde(default)] pub status: Option, + #[serde(default)] + pub disconnect_status_code: Option, + #[serde(default)] + pub disconnect_reason: Option, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. @@ -936,6 +940,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request_disconnect_route() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request-disconnect-route.json"); + let parsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_v2_custom_authorizer_v1_request() { diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json b/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json new file mode 100644 index 00000000..3a6f0cee --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json @@ -0,0 +1,33 @@ +{ + "headers": { + "Host": "abcd1234.execute-api.us-east-1.amazonaws.com", + "x-api-key": "", + "X-Forwarded-For": "", + "x-restapi": "" + }, + "multiValueHeaders": { + "Host": [ "abcd1234.execute-api.us-east-1.amazonaws.com" ], + "x-api-key": [ "" ], + "X-Forwarded-For": [ "" ], + "x-restapi": [ "" ] + }, + "requestContext": { + "routeKey": "$disconnect", + "disconnectStatusCode": 1005, + "eventType": "DISCONNECT", + "extendedRequestId": "ABCD1234=", + "requestTime": "09/Feb/2024:18:23:28 +0000", + "messageDirection": "IN", + "disconnectReason": "Client-side close frame status not set", + "stage": "prod", + "connectedAt": 1707503007396, + "requestTimeEpoch": 1707503008941, + "identity": { "sourceIp": "192.0.2.1" }, + "requestId": "ABCD1234=", + "domainName": "abcd1234.execute-api.us-east-1.amazonaws.com", + "connectionId": "AAAA1234=", + "apiId": "abcd1234" + }, + "isBase64Encoded": false +} + \ No newline at end of file From 09819be9da158ef336b4f9aff372f3ebb25c8372 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 14 Feb 2024 00:16:36 +0800 Subject: [PATCH 270/394] Release events and http (#819) * Release lambda-events 0.13.2 * Release lambda-event 0.14.0 and lambda-http 0.9.3 --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index c6556b64..3010aedc 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.13.1" +version = "0.14.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1b9cd693..3843b7e0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.2" +version = "0.9.3" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.13.1" +version = "0.14.0" default-features = false features = ["alb", "apigw"] From 73736fdae3a7a3aa40e3d7d8ef75c0d1b954fe03 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Wed, 14 Feb 2024 01:20:22 +0000 Subject: [PATCH 271/394] Update lib.rs: Response vs Request in trace! log (#809) * Update lib.rs: Response vs Request in trace! log * Update lambda-runtime/src/lib.rs Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- lambda-runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 3a91d943..871991a7 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -126,7 +126,7 @@ impl Runtime { // Group the handling in one future and instrument it with the span async { let body = body.collect().await?.to_bytes(); - trace!("response body - {}", std::str::from_utf8(&body)?); + trace!(body = std::str::from_utf8(&body)?, "raw JSON event received from Lambda"); #[cfg(debug_assertions)] if parts.status.is_server_error() { From a4aa23ecd1d98fdba8f5a671f0959ad743bfe138 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 13 Feb 2024 17:33:07 -0800 Subject: [PATCH 272/394] Release runtime version 0.9.2 (#820) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 3843b7e0..9facc13d 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -33,7 +33,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.9", path = "../lambda-runtime" } +lambda_runtime = { version = "0.9.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 221aa6f0..94a3c201 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.1" +version = "0.9.2" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 871991a7..effb0561 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -126,7 +126,10 @@ impl Runtime { // Group the handling in one future and instrument it with the span async { let body = body.collect().await?.to_bytes(); - trace!(body = std::str::from_utf8(&body)?, "raw JSON event received from Lambda"); + trace!( + body = std::str::from_utf8(&body)?, + "raw JSON event received from Lambda" + ); #[cfg(debug_assertions)] if parts.status.is_server_error() { From b87214dc2ce3cdd8985b34cc1c7c5cad9dca1b05 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Tue, 20 Feb 2024 21:10:57 -0800 Subject: [PATCH 273/394] Allow null answerCorrect when deserializing (#826) CognitoEventUserPoolsVerifyAuthChallengeResponse. Co-authored-by: Michael Wallace --- lambda-events/src/event/cognito/mod.rs | 17 +++++++++- ...fy-auth-challenge-null-answer-correct.json | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 95782ffc..6eb1c001 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -535,7 +535,7 @@ where #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub answer_correct: bool, } @@ -826,6 +826,21 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge_null_answer_correct() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json" + ); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(!parsed.response.answer_correct); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_verify_auth_challenge_user_not_found() { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json new file mode 100644 index 00000000..964061f2 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json @@ -0,0 +1,31 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": false + }, + "response": { + "answerCorrect": null + } +} From fabffbcb9eec856ab060de67522c356470d190fd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 21 Feb 2024 08:55:54 -0800 Subject: [PATCH 274/394] Api Gateway authorizer improvements (#827) * Improve Api Gateway events struct. - Remove the generic attribute since the authorizer values are not always of the same type, just use Value. - Match authorizer type to use the same type for all events. - Make the authorizer fields match the Java and DotNet implementation, which both use a Map for Rest and WebSocket events. -- https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyRequest.cs -- https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayV2WebSocketEvent.java#L225 Signed-off-by: David Calavera * Expose RequestContext::authorizer() for Api Gateway requests. Signed-off-by :David Calavera * Add example on how to use Api Gateway authorizers in Axum. This also shows how to work with the RequestExt trait and the RequestContext object. Signed-off-by: David Calavera * Rename the Authorizer structs. Make the name more consistent since it's used for multiple versions of Api Gateway. Keep the old names as deprecated. Signed-off-by: David Calavera * Add example without using Axum extractor. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .../http-axum-apigw-authorizer/Cargo.toml | 14 ++ examples/http-axum-apigw-authorizer/README.md | 13 ++ .../http-axum-apigw-authorizer/src/main.rs | 93 +++++++++++ lambda-events/src/event/apigw/mod.rs | 158 +++++++++++------- lambda-http/src/request.rs | 34 ++++ 5 files changed, 249 insertions(+), 63 deletions(-) create mode 100644 examples/http-axum-apigw-authorizer/Cargo.toml create mode 100644 examples/http-axum-apigw-authorizer/README.md create mode 100644 examples/http-axum-apigw-authorizer/src/main.rs diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml new file mode 100644 index 00000000..5c3e806e --- /dev/null +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-axum-apigw-authorizer" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.196" +serde_json = "1.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-apigw-authorizer/README.md b/examples/http-axum-apigw-authorizer/README.md new file mode 100644 index 00000000..2d05df59 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/README.md @@ -0,0 +1,13 @@ +# Axum example that integrates with Api Gateway authorizers + +This example shows how to extract information from the Api Gateway Request Authorizer in an Axum handler. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs new file mode 100644 index 00000000..bb03de07 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -0,0 +1,93 @@ +use axum::{ + async_trait, + extract::{FromRequest, Request}, + http::StatusCode, + response::Json, + routing::get, + Router, +}; +use lambda_http::{run, Error, RequestExt}; +use serde_json::{json, Value}; +use std::{collections::HashMap, env::set_var}; + +struct AuthorizerField(String); +struct AuthorizerFields(HashMap); + +#[async_trait] +impl FromRequest for AuthorizerField +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .and_then(|a| a.fields.get("field_name")) + .and_then(|f| f.as_str()) + .map(|v| Self(v.to_string())) + .ok_or((StatusCode::BAD_REQUEST, "`field_name` authorizer field is missing")) + } +} + +#[async_trait] +impl FromRequest for AuthorizerFields +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .map(|a| Self(a.fields.clone())) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing")) + } +} + +async fn extract_field(AuthorizerField(field): AuthorizerField) -> Json { + Json(json!({ "field extracted": field })) +} + +async fn extract_all_fields(AuthorizerFields(fields): AuthorizerFields) -> Json { + Json(json!({ "authorizer fields": fields })) +} + +async fn authorizer_without_extractor(req: Request) -> Result, (StatusCode, &'static str)> { + let auth = req + .request_context_ref() + .and_then(|r| r.authorizer()) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing"))?; + + let field1 = auth.fields.get("field1").and_then(|v| v.as_str()).unwrap_or_default(); + let field2 = auth.fields.get("field2").and_then(|v| v.as_str()).unwrap_or_default(); + + Ok(Json(json!({ "field1": field1, "field2": field2 }))) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // If you use API Gateway stages, the Rust Runtime will include the stage name + // as part of the path that your application receives. + // Setting the following environment variable, you can remove the stage from the path. + // This variable only applies to API Gateway stages, + // you can remove it if you don't use them. + // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` + set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); + + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let app = Router::new() + .route("/extract-field", get(extract_field)) + .route("/extract-all-fields", get(extract_all_fields)) + .route("/authorizer-without-extractor", get(authorizer_without_extractor)); + + run(app).await +} diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 22c508b2..1a9b1f1a 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -5,19 +5,14 @@ use crate::custom_serde::{ use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -44,7 +39,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayProxyRequestContext, + pub request_context: ApiGatewayProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -72,11 +67,7 @@ pub struct ApiGatewayProxyResponse { /// Lambda function. It also includes Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -99,10 +90,13 @@ where pub resource_path: Option, #[serde(default)] pub path: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - pub authorizer: HashMap, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(with = "http_method")] pub http_method: Method, #[serde(default)] @@ -168,11 +162,7 @@ pub struct ApiGatewayV2httpRequest { /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayV2httpRequestContext { #[serde(default)] pub route_key: Option, #[serde(default)] @@ -181,9 +171,9 @@ where pub stage: Option, #[serde(default)] pub request_id: Option, - #[serde(bound = "", default)] + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub authorizer: Option>, + pub authorizer: Option, /// The API Gateway HTTP API Id #[serde(default)] #[serde(rename = "apiId")] @@ -201,29 +191,27 @@ where pub authentication: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerDescription` contains authorizer information for the request context. +/// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerDescription -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayRequestAuthorizer { #[serde(skip_serializing_if = "Option::is_none")] - pub jwt: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub lambda: HashMap, + pub jwt: Option, + #[serde( + bound = "", + rename = "lambda", + default, + skip_serializing_if = "HashMap::is_empty", + deserialize_with = "deserialize_lambda_map" + )] + pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] - pub iam: Option, + pub iam: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerJwtDescription` contains JWT authorizer information for the request context. +/// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { +pub struct ApiGatewayRequestAuthorizerJwtDescription { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub claims: HashMap, @@ -231,10 +219,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { pub scopes: Option>, } -/// `ApiGatewayV2httpRequestContextAuthorizerIamDescription` contains IAM information for the request context. +/// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { +pub struct ApiGatewayRequestAuthorizerIamDescription { #[serde(default)] pub access_key: Option, #[serde(default)] @@ -242,7 +230,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { #[serde(default)] pub caller_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub cognito_identity: Option, + pub cognito_identity: Option, #[serde(default)] pub principal_org_id: Option, #[serde(default)] @@ -251,10 +239,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { pub user_id: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +/// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity { +pub struct ApiGatewayRequestAuthorizerCognitoIdentity { pub amr: Vec, #[serde(default)] pub identity_id: Option, @@ -332,13 +320,7 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, - T2: DeserializeOwned + Default, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -367,7 +349,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayWebsocketProxyRequestContext, + pub request_context: ApiGatewayWebsocketProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -379,13 +361,7 @@ where /// Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, - T2: DeserializeOwned, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -398,8 +374,13 @@ where pub identity: ApiGatewayRequestIdentity, #[serde(default)] pub resource_path: Option, - #[serde(bound = "")] - pub authorizer: Option, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(deserialize_with = "http_method::deserialize_optional")] #[serde(serialize_with = "http_method::serialize_optional")] #[serde(skip_serializing_if = "Option::is_none")] @@ -425,7 +406,7 @@ where #[serde(default)] pub message_direction: Option, #[serde(bound = "")] - pub message_id: Option, + pub message_id: Option, #[serde(default)] pub request_time: Option, pub request_time_epoch: i64, @@ -768,6 +749,45 @@ fn default_http_method() -> Method { Method::GET } +#[deprecated = "use `ApiGatewayRequestAuthorizer` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerDescription = ApiGatewayRequestAuthorizer; +#[deprecated = "use `ApiGatewayRequestAuthorizerJwtDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerJwtDescription = ApiGatewayRequestAuthorizerJwtDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerIamDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerIamDescription = ApiGatewayRequestAuthorizerIamDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerCognitoIdentity` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity = ApiGatewayRequestAuthorizerCognitoIdentity; + +impl ApiGatewayRequestAuthorizer { + fn is_empty(&self) -> bool { + self.fields.is_empty() + } +} + +fn deserialize_authorizer_fields<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let fields: Option> = Option::deserialize(deserializer)?; + let mut authorizer = ApiGatewayRequestAuthorizer::default(); + if let Some(fields) = fields { + authorizer.fields = fields; + } + + Ok(authorizer) +} + +pub fn serialize_authorizer_fields( + authorizer: &ApiGatewayRequestAuthorizer, + ser: S, +) -> Result { + let mut map = ser.serialize_map(Some(authorizer.fields.len()))?; + for (k, v) in &authorizer.fields { + map.serialize_entry(k, v)?; + } + map.end() +} + #[cfg(test)] mod test { use super::*; @@ -991,4 +1011,16 @@ mod test { let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request_authorizer_fields() { + let data = include_bytes!("../../fixtures/example-apigw-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + + let fields = parsed.request_context.authorizer.fields; + assert_eq!(Some("admin"), fields.get("principalId").unwrap().as_str()); + assert_eq!(Some(1), fields.get("clientId").unwrap().as_u64()); + assert_eq!(Some("Exata"), fields.get("clientName").unwrap().as_str()); + } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index bce2e3d3..9e789270 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -17,6 +17,8 @@ use crate::ext::extensions::{PathParameters, StageVariables}; use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] +use aws_lambda_events::apigw::ApiGatewayRequestAuthorizer; #[cfg(feature = "apigw_rest")] use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext}; #[cfg(feature = "apigw_http")] @@ -425,6 +427,22 @@ impl From for http::Request { } } +impl RequestContext { + /// Returns the Api Gateway Authorizer information for a request. + #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] + pub fn authorizer(&self) -> Option<&ApiGatewayRequestAuthorizer> { + match self { + #[cfg(feature = "apigw_rest")] + Self::ApiGatewayV1(ag) => Some(&ag.authorizer), + #[cfg(feature = "apigw_http")] + Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(), + #[cfg(feature = "apigw_websockets")] + Self::WebSocket(ag) => Some(&ag.authorizer), + _ => None, + } + } +} + /// Deserializes a `Request` from a `Read` impl providing JSON events. /// /// # Example @@ -920,4 +938,20 @@ mod tests { assert_eq!(vec!["val1", "val2"], query.0.my_key); assert_eq!(vec!["val3", "val4"], query.0.my_other_key); } + + #[test] + #[cfg(feature = "apigw_rest")] + fn deserializes_request_authorizer() { + let input = include_str!("../../lambda-events/src/fixtures/example-apigw-request.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); + let authorizer = req_context.authorizer().expect("authorizer is missing"); + assert_eq!(Some("admin"), authorizer.fields.get("principalId").unwrap().as_str()); + } } From 387b07c7250616e9fefcafada410eb2a44d1c4b3 Mon Sep 17 00:00:00 2001 From: Kikuo Emoto Date: Thu, 22 Feb 2024 08:50:08 +0900 Subject: [PATCH 275/394] Allow error response customization (#828) * Feat: custom error type support Error responses of `lambda_runtime` are now customizable. One use case is to avoid using `std::any::type_name` for error types, which is not reliable for determining subsequent program behavior. `F::Error` of `Runtime::run` and `run` is required to implement `Into>` instead of `Display`. `EventErrorRequest` accepts a value implementing `Into>` instead of the error type and message. `Diagnostic` becomes public because users may implement their own conversions. Its fields are wrapped in `Cow` so that they can carry both borrowed and owned strings. `Diagnostic` is implemented for every type that implements `Display` as a fallback, which also ensures backward compatibility. * Chore: add example to comments on Diagnostic The comments on `Diagnositc` shows how to customize error responses with an example. --- lambda-runtime/src/lib.rs | 35 ++++++++++------- lambda-runtime/src/requests.rs | 11 ++---- lambda-runtime/src/types.rs | 68 +++++++++++++++++++++++++++++++--- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index effb0561..b4964954 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -14,8 +14,9 @@ use hyper::{body::Incoming, http::Request}; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, env, - fmt::{self, Debug, Display}, + fmt::{self, Debug}, future::Future, panic, sync::Arc, @@ -33,7 +34,9 @@ pub mod streaming; mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; -pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; +pub use types::{ + Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, +}; use types::invoke_request_id; @@ -96,7 +99,7 @@ impl Runtime { where F: Service>, F::Future: Future>, - F::Error: fmt::Debug + fmt::Display, + F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, @@ -173,7 +176,14 @@ impl Runtime { } else { "Lambda panicked".to_string() }; - EventErrorRequest::new(request_id, error_type, &msg).into_req() + EventErrorRequest::new( + request_id, + Diagnostic { + error_type: Cow::Borrowed(error_type), + error_message: Cow::Owned(msg), + }, + ) + .into_req() } } } @@ -224,7 +234,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service>, F::Future: Future>, - F::Error: fmt::Debug + fmt::Display, + F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, @@ -249,15 +259,12 @@ fn type_name_of_val(_: T) -> &'static str { std::any::type_name::() } -fn build_event_error_request(request_id: &str, err: T) -> Result, Error> +fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, Error> where - T: Display + Debug, + T: Into> + Debug, { error!("{:?}", err); // logs the error in CloudWatch - let error_type = type_name_of_val(&err); - let msg = format!("{err}"); - - EventErrorRequest::new(request_id, error_type, &msg).into_req() + EventErrorRequest::new(request_id, err).into_req() } #[cfg(test)] @@ -274,7 +281,7 @@ mod endpoint_tests { use httpmock::prelude::*; use lambda_runtime_api_client::Client; - use std::{env, sync::Arc}; + use std::{borrow::Cow, env, sync::Arc}; use tokio_stream::StreamExt; #[tokio::test] @@ -341,8 +348,8 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { let diagnostic = Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data"), }; let body = serde_json::to_string(&diagnostic)?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index c9274cf4..d1e25e32 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -194,13 +194,10 @@ pub(crate) struct EventErrorRequest<'a> { } impl<'a> EventErrorRequest<'a> { - pub(crate) fn new(request_id: &'a str, error_type: &'a str, error_message: &'a str) -> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, diagnostic: impl Into>) -> EventErrorRequest<'a> { EventErrorRequest { request_id, - diagnostic: Diagnostic { - error_type, - error_message, - }, + diagnostic: diagnostic.into(), } } } @@ -226,8 +223,8 @@ fn test_event_error_request() { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", + error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), + error_message: std::borrow::Cow::Borrowed("Error parsing event data"), }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 3d89e0a0..478f88fd 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -5,19 +5,75 @@ use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, collections::HashMap, env, - fmt::Debug, + fmt::{Debug, Display}, time::{Duration, SystemTime}, }; use tokio_stream::Stream; use tracing::Span; +/// Diagnostic information about an error. +/// +/// `Diagnostic` is automatically derived for types that implement +/// [`Display`][std::fmt::Display]; e.g., [`Error`][std::error::Error]. +/// +/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of +/// the original error with [`std::any::type_name`] as a fallback, which may +/// not be reliable for conditional error handling. +/// You can define your own error container that implements `Into` +/// if you need to handle errors based on error types. +/// +/// Example: +/// ``` +/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; +/// use std::borrow::Cow; +/// +/// #[derive(Debug)] +/// struct ErrorResponse(Error); +/// +/// impl<'a> Into> for ErrorResponse { +/// fn into(self) -> Diagnostic<'a> { +/// Diagnostic { +/// error_type: Cow::Borrowed("MyError"), +/// error_message: Cow::Owned(self.0.to_string()), +/// } +/// } +/// } +/// +/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { +/// // ... do something +/// Ok(()) +/// } +/// ``` #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct Diagnostic<'a> { - pub(crate) error_type: &'a str, - pub(crate) error_message: &'a str, +pub struct Diagnostic<'a> { + /// Error type. + /// + /// `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`] as a fallback. + /// Please implement your own `Into` if you need more reliable + /// error types. + pub error_type: Cow<'a, str>, + /// Error message. + /// + /// `error_message` is the output from the [`Display`][std::fmt::Display] + /// implementation of the original error as a fallback. + pub error_message: Cow<'a, str>, +} + +impl<'a, T> From for Diagnostic<'a> +where + T: Display, +{ + fn from(value: T) -> Self { + Diagnostic { + error_type: Cow::Borrowed(std::any::type_name::()), + error_message: Cow::Owned(format!("{value}")), + } + } } /// Client context sent by the AWS Mobile SDK. @@ -315,8 +371,8 @@ mod test { }); let actual = Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data.", + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data."), }; let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); assert_eq!(expected, actual); From 0d92dd347d6841182a954399fe80421b7cf214c0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 23 Feb 2024 11:32:42 -0800 Subject: [PATCH 276/394] Implement CloudWatch alarm SNS payloads. (#829) Signed-off-by: David Calavera --- lambda-events/src/custom_serde/mod.rs | 1 + lambda-events/src/event/sns/mod.rs | 80 ++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 82723c3f..46d121d1 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -80,6 +80,7 @@ where feature = "cloudwatch_events", feature = "code_commit", feature = "cognito", + feature = "sns", test ))] pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index e9809630..d72b926a 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::custom_serde::deserialize_lambda_map; +use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// @@ -175,6 +175,78 @@ pub struct MessageAttribute { pub value: String, } +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchAlarmPayload { + pub alarm_name: String, + pub alarm_description: String, + #[serde(rename = "AWSAccountId")] + pub aws_account_id: String, + pub new_state_value: String, + pub new_state_reason: String, + pub state_change_time: String, + pub region: String, + pub alarm_arn: String, + pub old_state_value: String, + pub trigger: CloudWatchAlarmTrigger, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchAlarmTrigger { + pub period: i64, + pub evaluation_periods: i64, + pub comparison_operator: String, + pub threshold: f64, + pub treat_missing_data: String, + pub evaluate_low_sample_count_percentile: String, + #[serde(default)] + pub metrics: Vec, + pub metric_name: Option, + pub namespace: Option, + pub statistic_type: Option, + pub statistic: Option, + pub unit: Option, + #[serde(default)] + pub dimensions: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetricDataQuery { + pub id: String, + pub expression: Option, + pub label: Option, + pub metric_stat: Option, + pub period: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub return_data: bool, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetricStat { + pub metric: CloudWatchMetric, + pub period: i64, + pub stat: String, + pub unit: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetric { + #[serde(default)] + pub dimensions: Vec, + pub metric_name: Option, + pub namespace: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CloudWatchDimension { + pub name: String, + pub value: String, +} + #[cfg(test)] mod test { use super::*; @@ -209,6 +281,12 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + + let parsed: SnsEventObj = + serde_json::from_slice(data).expect("failed to parse CloudWatch Alarm payload"); + + let record = parsed.records.first().unwrap(); + assert_eq!("EXAMPLE", record.sns.message.alarm_name); } #[test] From af4df1f7db21dbe694b4a6ce720626854939a242 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 18:39:01 -0800 Subject: [PATCH 277/394] Remove unused warning. (#831) Signed-off-by: David Calavera --- lambda-http/src/request.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9e789270..9476da3b 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -438,6 +438,7 @@ impl RequestContext { Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(), #[cfg(feature = "apigw_websockets")] Self::WebSocket(ag) => Some(&ag.authorizer), + #[cfg(any(feature = "alb", feature = "pass_through"))] _ => None, } } From f51589c247171984e905e0c42b3bf3faad4ea11d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 19:43:30 -0800 Subject: [PATCH 278/394] Advanced logging controls (#830) * Implement Lambda's advance logging controls. Provide a feature that exposes a initialization function for `tracing-subscriber` that sets the right logging controls based on Lambda's configuration. The feature is enabled by default, but it can be disabled if a user doesn't want to use it. Signed-off-by: David Calavera * Update examples to use the new tracing-subscriber feature. Signed-off-by: David Calavera * Remove tracing from the runtime client. It makes logs too verbose. Signed-off-by: David Calavera * Make the tracing dependency optional. Signed-off-by: David Calavera * Fix ambiguous name in old versions of Rust. Identify the dependency as the top dependency. Signed-off-by: David Calavera * Fix formatting. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- README.md | 18 ++++++++ .../consumer/Cargo.toml | 7 +--- .../consumer/src/main.rs | 11 ++--- .../producer/Cargo.toml | 6 +-- .../producer/src/main.rs | 13 ++---- .../Cargo.toml | 2 - .../src/main.rs | 15 +++---- examples/basic-error-handling/Cargo.toml | 11 ----- examples/basic-error-handling/src/main.rs | 10 +---- .../basic-lambda-external-runtime/Cargo.toml | 7 ++-- .../basic-lambda-external-runtime/src/main.rs | 10 +---- examples/basic-lambda/Cargo.toml | 11 +---- examples/basic-lambda/src/main.rs | 10 +---- .../Cargo.toml | 2 - .../src/main.rs | 10 +---- .../src/s3.rs | 1 + examples/basic-s3-thumbnail/Cargo.toml | 2 - examples/basic-s3-thumbnail/src/main.rs | 10 +---- examples/basic-s3-thumbnail/src/s3.rs | 1 + examples/basic-sdk/Cargo.toml | 2 - examples/basic-sdk/src/main.rs | 10 +---- examples/basic-shared-resource/Cargo.toml | 10 ----- examples/basic-shared-resource/src/main.rs | 10 +---- examples/basic-sqs/Cargo.toml | 2 - examples/basic-sqs/src/main.rs | 10 +---- examples/basic-streaming-response/Cargo.toml | 2 - examples/basic-streaming-response/src/main.rs | 10 +---- examples/extension-basic/Cargo.toml | 11 ----- examples/extension-basic/src/main.rs | 10 +---- examples/extension-combined/Cargo.toml | 11 ----- examples/extension-combined/src/main.rs | 15 ++----- examples/extension-custom-events/Cargo.toml | 11 ----- examples/extension-custom-events/src/main.rs | 10 +---- examples/extension-custom-service/Cargo.toml | 11 ----- examples/extension-custom-service/src/main.rs | 10 +---- examples/extension-internal-flush/src/main.rs | 3 +- examples/extension-logs-basic/Cargo.toml | 11 ----- examples/extension-logs-basic/src/main.rs | 10 +---- .../extension-logs-custom-service/Cargo.toml | 11 ----- .../extension-logs-custom-service/src/main.rs | 15 ++----- .../Cargo.toml | 9 ---- .../src/main.rs | 10 +---- examples/extension-telemetry-basic/Cargo.toml | 11 ----- .../extension-telemetry-basic/src/main.rs | 21 ++++------ .../http-axum-apigw-authorizer/Cargo.toml | 2 - .../http-axum-apigw-authorizer/src/main.rs | 10 +---- examples/http-axum-diesel-ssl/Cargo.toml | 9 ---- examples/http-axum-diesel-ssl/src/main.rs | 18 ++------ examples/http-axum-diesel/Cargo.toml | 9 ---- examples/http-axum-diesel/src/main.rs | 10 +---- examples/http-axum-middleware/Cargo.toml | 7 +--- examples/http-axum-middleware/src/main.rs | 8 +--- examples/http-axum/Cargo.toml | 9 ---- examples/http-axum/src/main.rs | 10 +---- examples/http-basic-lambda/Cargo.toml | 11 ----- examples/http-basic-lambda/src/main.rs | 10 +---- examples/http-cors/Cargo.toml | 11 ----- examples/http-cors/src/main.rs | 10 +---- examples/http-dynamodb/Cargo.toml | 12 ------ examples/http-dynamodb/src/main.rs | 17 +++----- examples/http-query-parameters/Cargo.toml | 11 ----- examples/http-query-parameters/src/main.rs | 10 +---- examples/http-raw-path/Cargo.toml | 11 ----- examples/http-raw-path/src/main.rs | 10 +---- examples/http-shared-resource/Cargo.toml | 11 ----- examples/http-shared-resource/src/main.rs | 10 +---- examples/http-tower-trace/Cargo.toml | 9 ---- examples/http-tower-trace/src/main.rs | 10 +---- lambda-extension/Cargo.toml | 6 ++- lambda-extension/src/lib.rs | 4 ++ lambda-http/Cargo.toml | 3 +- lambda-http/src/lib.rs | 6 ++- lambda-runtime-api-client/Cargo.toml | 6 +++ lambda-runtime-api-client/src/lib.rs | 3 ++ lambda-runtime-api-client/src/tracing.rs | 41 +++++++++++++++++++ lambda-runtime/Cargo.toml | 4 +- lambda-runtime/src/lib.rs | 7 +++- 77 files changed, 187 insertions(+), 541 deletions(-) create mode 100644 lambda-runtime-api-client/src/tracing.rs diff --git a/README.md b/README.md index 43c38586..8a5920b7 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,24 @@ You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/c Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. +## Tracing and Logging + +The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. + +By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subsriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: + +```rust +use lambda_runtime::{run, service_fn, tracing, Error}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + run(service_fn(|event| tracing::info!(?event))).await +} +``` + +The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) if they're configured for your function. By default, the log level to emit events is `INFO`. + ## AWS event objects This project includes Lambda event struct definitions, [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events). This crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml index 8555a073..69ec04a0 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -3,19 +3,14 @@ name = "consumer" version = "0.1.0" edition = "2021" - [dependencies] -#tracing -tracing = "0.1.40" -tracing-subscriber = "0.3.17" - #aws dependencies aws-sdk-config = "0.35.0" aws-sdk-sqs = "0.35.0" aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } #lambda runtime -lambda_runtime = "0.8.1" +lambda_runtime = { path = "../../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } #shared lib diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs index 42290192..c076a502 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs @@ -1,15 +1,10 @@ use aws_lambda_events::event::sqs::SqsEventObj; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use pizza_lib::Pizza; #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .with_ansi(false) - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(func); lambda_runtime::run(func).await?; Ok(()) @@ -18,7 +13,7 @@ async fn main() -> Result<(), Error> { async fn func(event: LambdaEvent>) -> Result<(), Error> { for record in event.payload.records.iter() { let pizza = &record.body; - println!("Pizza name: {} with toppings: {:?}", pizza.name, pizza.toppings); + tracing::info!(name = pizza.name, toppings = ?pizza.toppings, "pizza order received"); } Ok(()) } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml index 557ac6e5..83aa48ab 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -7,17 +7,13 @@ edition = "2021" env = { "QUEUE_URL" = "https://changeMe" } [dependencies] -#tracing -tracing = "0.1.40" -tracing-subscriber = "0.3.17" - #aws dependencies aws-config = "0.57.1" aws-sdk-config = "0.35.0" aws-sdk-sqs = "0.35.0" #lambda runtime -lambda_runtime = "0.8.1" +lambda_runtime = { path = "../../../lambda-runtime" } serde_json = "1.0.108" tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs index 2cc2541b..2a70dce3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -1,4 +1,4 @@ -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use pizza_lib::Pizza; use serde_json::{json, Value}; @@ -15,12 +15,7 @@ impl SQSManager { #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .with_ansi(false) - .without_time() - .init(); + tracing::init_default_subscriber(); // read the queue url from the environment let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); @@ -31,9 +26,7 @@ async fn main() -> Result<(), Error> { let sqs_manager_ref = &sqs_manager; // no need to create a SQS Client for each incoming request, let's use a shared state - let handler_func_closure = |event: LambdaEvent| async move { - process_event(event, sqs_manager_ref).await - }; + let handler_func_closure = |event: LambdaEvent| async move { process_event(event, sqs_manager_ref).await }; lambda_runtime::run(service_fn(handler_func_closure)).await?; Ok(()) } diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 04158320..95050b9a 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -12,5 +12,3 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } futures = "0.3" -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 23faa68f..42bb2253 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -3,9 +3,12 @@ use aws_lambda_events::{ sqs::{BatchItemFailure, SqsBatchResponse, SqsMessageObj}, }; use futures::Future; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{ + run, service_fn, + tracing::{self, Instrument}, + Error, LambdaEvent, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tracing::Instrument; /// [To customize] Your object definition, sent to the SQS queue triggering this lambda. #[derive(Deserialize, Serialize)] @@ -29,13 +32,7 @@ async fn data_handler(data: Data) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run_sqs_partial_batch_failure(data_handler).await } diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index e8699141..1039a139 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -3,20 +3,9 @@ name = "error-handling" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" serde_json = "1.0.81" simple-error = "0.2.3" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 0939d2d0..528d6f02 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,5 +1,5 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::fs::File; @@ -50,13 +50,7 @@ impl std::fmt::Display for CustomError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // call the actual handler of the request let func = service_fn(func); diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index 0682efaf..d6d023d8 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -6,10 +6,9 @@ edition = "2021" [dependencies] async-channel = "1.8.0" futures-lite = "1.13.0" -lambda_runtime = "0.8.0" -lambda_runtime_api_client = "0.8.0" +lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.163" tokio = "1.28.2" + +[dev-dependencies] tokio-test = "0.4.2" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index 9419b17b..bd3b4e6c 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -1,7 +1,7 @@ use std::{io, thread}; use futures_lite::future; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use tokio::runtime::Builder; @@ -25,13 +25,7 @@ struct Response { fn main() -> Result<(), io::Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Create a channel used to send and receive outputs from our lambda handler. Realistically, this would be either an unbounded channel // or a bounded channel with a higher capacity as needed. diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index cd2efa42..6fad6990 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -3,17 +3,10 @@ name = "basic-lambda" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + +[dev-dependencies] tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index 09145bb3..8630e78c 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -1,7 +1,7 @@ // This example requires the following input to succeed: // { "command": "do something" } -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -25,13 +25,7 @@ struct Response { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_handler); lambda_runtime::run(func).await?; diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index 79640cc2..ba750df9 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -19,8 +19,6 @@ aws_lambda_events = "0.8.3" lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 328e7500..bb1c4f9c 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -2,7 +2,7 @@ use std::error; use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; use aws_sdk_s3::Client as S3Client; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use s3::{GetFile, SendFile}; mod s3; @@ -57,13 +57,7 @@ fn get_thumbnail(vec: Vec, size: u32) -> Vec { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::TRACE) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs index 71e03ffc..daba3739 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use lambda_runtime::tracing; use std::{error, io::Read}; pub trait GetFile { diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 6bbe11b7..1cd3b834 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -19,8 +19,6 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } aws-config = "0.55" aws-smithy-http = "0.55.3" aws-sdk-s3 = "0.28" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d92c822b..3eb5bfe9 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -1,6 +1,6 @@ use aws_lambda_events::{event::s3::S3Event, s3::S3EventRecord}; use aws_sdk_s3::Client as S3Client; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use s3::{GetFile, PutFile}; mod s3; @@ -109,13 +109,7 @@ fn get_thumbnail(vec: Vec, size: u32) -> Result, String> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 17d7f975..0dd8629d 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::Client as S3Client; use aws_smithy_http::byte_stream::ByteStream; +use lambda_runtime::tracing; #[async_trait] pub trait GetFile { diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 0e930f7c..454a970f 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -12,8 +12,6 @@ aws-sdk-s3 = "0.24" lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index 6e2654a4..d49c84e1 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use aws_sdk_s3::{output::ListObjectsV2Output, Client as S3Client}; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// The request defines what bucket to list @@ -34,13 +34,7 @@ impl ListObjects for S3Client { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index b3e2faa5..2aad5886 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -3,17 +3,7 @@ name = "shared-resource" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - diff --git a/examples/basic-shared-resource/src/main.rs b/examples/basic-shared-resource/src/main.rs index 15ababa0..795c2c97 100644 --- a/examples/basic-shared-resource/src/main.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -4,7 +4,7 @@ // Run it with the following input: // { "command": "do something" } -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -45,13 +45,7 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let client = SharedClient::new("Shared Client 1 (perhaps a database)"); let client_ref = &client; diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 9d259218..0df7d8e2 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -19,5 +19,3 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs index 63967893..f04272be 100644 --- a/examples/basic-sqs/src/main.rs +++ b/examples/basic-sqs/src/main.rs @@ -1,5 +1,5 @@ use aws_lambda_events::event::sqs::SqsEventObj; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// Object that you send to SQS and plan to process on the function. @@ -21,13 +21,7 @@ async fn function_handler(event: LambdaEvent>) -> Result<(), E #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index e9b7499c..3f1bacac 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -8,6 +8,4 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } serde_json = "1.0" \ No newline at end of file diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index c8932554..8533c8e3 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,7 +1,7 @@ use lambda_runtime::{ service_fn, streaming::{channel, Body, Response}, - Error, LambdaEvent, + tracing, Error, LambdaEvent, }; use serde_json::Value; use std::{thread, time::Duration}; @@ -24,13 +24,7 @@ async fn func(_event: LambdaEvent) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); lambda_runtime::run(service_fn(func)).await?; Ok(()) diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index caf0818c..48e2ed51 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-basic/src/main.rs b/examples/extension-basic/src/main.rs index f9838c6b..e76d638f 100644 --- a/examples/extension-basic/src/main.rs +++ b/examples/extension-basic/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -15,13 +15,7 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_extension); lambda_extension::run(func).await diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index d776f488..2a745c7b 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-combined" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-combined/src/main.rs b/examples/extension-combined/src/main.rs index e05b1b7d..ce6054e8 100644 --- a/examples/extension-combined/src/main.rs +++ b/examples/extension-combined/src/main.rs @@ -1,7 +1,6 @@ use lambda_extension::{ - service_fn, Error, Extension, LambdaEvent, LambdaLog, LambdaLogRecord, NextEvent, SharedService, + service_fn, tracing, Error, Extension, LambdaEvent, LambdaLog, LambdaLogRecord, NextEvent, SharedService, }; -use tracing::info; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -18,8 +17,8 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { async fn my_log_processor(logs: Vec) -> Result<(), Error> { for log in logs { match log.record { - LambdaLogRecord::Function(record) => info!("[logs] [function] {}", record), - LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}", record), + LambdaLogRecord::Function(record) => tracing::info!("[logs] [function] {}", record), + LambdaLogRecord::Extension(record) => tracing::info!("[logs] [extension] {}", record), _ => (), } } @@ -30,13 +29,7 @@ async fn my_log_processor(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_extension); let logs_processor = SharedService::new(service_fn(my_log_processor)); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index a826a137..c2f813c3 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-custom-events" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-custom-events/src/main.rs b/examples/extension-custom-events/src/main.rs index 1d39e20f..1590e14a 100644 --- a/examples/extension-custom-events/src/main.rs +++ b/examples/extension-custom-events/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -17,13 +17,7 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); Extension::new() .with_events(&["SHUTDOWN"]) diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index c9ff789a..b51eae8e 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-custom-service" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-custom-service/src/main.rs b/examples/extension-custom-service/src/main.rs index ec8ca68f..9a97732c 100644 --- a/examples/extension-custom-service/src/main.rs +++ b/examples/extension-custom-service/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{run, Error, InvokeEvent, LambdaEvent, NextEvent, Service}; +use lambda_extension::{run, tracing, Error, InvokeEvent, LambdaEvent, NextEvent, Service}; use std::{ future::{ready, Future}, pin::Pin, @@ -34,13 +34,7 @@ impl Service for MyExtension { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(MyExtension::default()).await } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index 3706809d..ff1d10da 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; -use lambda_extension::{service_fn, Error, Extension, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, Extension, NextEvent}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::Mutex; @@ -81,6 +81,7 @@ impl EventHandler { #[tokio::main] async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); let (request_done_sender, request_done_receiver) = unbounded_channel::<()>(); let flush_extension = Arc::new(FlushExtension::new(request_done_receiver)); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index d1983db8..dccc1ec0 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-logs-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-logs-basic/src/main.rs b/examples/extension-logs-basic/src/main.rs index 77065cca..4d2662e4 100644 --- a/examples/extension-logs-basic/src/main.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; use tracing::info; async fn handler(logs: Vec) -> Result<(), Error> { @@ -16,13 +16,7 @@ async fn handler(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let logs_processor = SharedService::new(service_fn(handler)); diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index cbbe20f6..1b1eea0a 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-logs-custom-service" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-logs-custom-service/src/main.rs b/examples/extension-logs-custom-service/src/main.rs index ebe1330d..ad2db5bc 100644 --- a/examples/extension-logs-custom-service/src/main.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{ future::{ready, Future}, pin::Pin, @@ -8,7 +8,6 @@ use std::{ }, task::Poll, }; -use tracing::info; /// Custom log processor that increments a counter for each log record. /// @@ -44,8 +43,8 @@ impl Service> for MyLogsProcessor { let counter = self.counter.fetch_add(1, SeqCst); for log in logs { match log.record { - LambdaLogRecord::Function(record) => info!("[logs] [function] {}: {}", counter, record), - LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}: {}", counter, record), + LambdaLogRecord::Function(record) => tracing::info!("[logs] [function] {}: {}", counter, record), + LambdaLogRecord::Extension(record) => tracing::info!("[logs] [extension] {}: {}", counter, record), _ => (), } } @@ -57,13 +56,7 @@ impl Service> for MyLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let logs_processor = SharedService::new(MyLogsProcessor::new()); diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index 0e056b1c..c6675e5a 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -3,17 +3,8 @@ name = "lambda-logs-firehose-extension" version = "0.1.0" edition = "2021" -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } tokio = { version = "1.17.0", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.13.0" aws-sdk-firehose = "0.13.0" - diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 8586e1a9..7871ce52 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -1,5 +1,5 @@ use aws_sdk_firehose::{model::Record, types::Blob, Client}; -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{future::Future, pin::Pin, task::Poll}; #[derive(Clone)] @@ -54,13 +54,7 @@ impl Service> for FirehoseLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let config = aws_config::load_from_env().await; let logs_processor = SharedService::new(FirehoseLogsProcessor::new(Client::new(&config))); diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index 869b604d..1b8b1ba4 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-telemetry-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs index 03974bf6..7159cf93 100644 --- a/examples/extension-telemetry-basic/src/main.rs +++ b/examples/extension-telemetry-basic/src/main.rs @@ -1,28 +1,27 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; -use tracing::info; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; async fn handler(events: Vec) -> Result<(), Error> { for event in events { match event.record { - LambdaTelemetryRecord::Function(record) => info!("[logs] [function] {}", record), + LambdaTelemetryRecord::Function(record) => tracing::info!("[logs] [function] {}", record), LambdaTelemetryRecord::PlatformInitStart { initialization_type: _, phase: _, runtime_version: _, runtime_version_arn: _, - } => info!("[platform] Initialization started"), + } => tracing::info!("[platform] Initialization started"), LambdaTelemetryRecord::PlatformInitRuntimeDone { initialization_type: _, phase: _, status: _, error_type: _, spans: _, - } => info!("[platform] Initialization finished"), + } => tracing::info!("[platform] Initialization finished"), LambdaTelemetryRecord::PlatformStart { request_id, version: _, tracing: _, - } => info!("[platform] Handling of request {} started", request_id), + } => tracing::info!("[platform] Handling of request {} started", request_id), LambdaTelemetryRecord::PlatformRuntimeDone { request_id, status: _, @@ -30,7 +29,7 @@ async fn handler(events: Vec) -> Result<(), Error> { metrics: _, spans: _, tracing: _, - } => info!("[platform] Handling of request {} finished", request_id), + } => tracing::info!("[platform] Handling of request {} finished", request_id), _ => (), } } @@ -41,13 +40,7 @@ async fn handler(events: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let telemetry_processor = SharedService::new(service_fn(handler)); diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml index 5c3e806e..c757aa94 100644 --- a/examples/http-axum-apigw-authorizer/Cargo.toml +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -10,5 +10,3 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs index bb03de07..513a6cd8 100644 --- a/examples/http-axum-apigw-authorizer/src/main.rs +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -6,7 +6,7 @@ use axum::{ routing::get, Router, }; -use lambda_http::{run, Error, RequestExt}; +use lambda_http::{run, tracing, Error, RequestExt}; use serde_json::{json, Value}; use std::{collections::HashMap, env::set_var}; @@ -76,13 +76,7 @@ async fn main() -> Result<(), Error> { set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/extract-field", get(extract_field)) diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index 006a82ce..69366957 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum-diesel" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" bb8 = "0.8.0" @@ -18,8 +11,6 @@ diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } futures-util = "0.3.21" rustls = "0.20.8" rustls-native-certs = "0.6.2" diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs index c2f6b933..b340b44d 100755 --- a/examples/http-axum-diesel-ssl/src/main.rs +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -12,7 +12,7 @@ use axum::{ use bb8::Pool; use diesel::prelude::*; use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; -use lambda_http::{http::StatusCode, run, Error}; +use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; table! { @@ -98,22 +98,13 @@ fn internal_server_error(err: E) -> ServerError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Set up the database connection // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); - let mgr = AsyncDieselConnectionManager::::new_with_setup( - db_url, - establish_connection, - ); + let mgr = AsyncDieselConnectionManager::::new_with_setup(db_url, establish_connection); let pool = Pool::builder() .max_size(10) @@ -122,7 +113,7 @@ async fn main() -> Result<(), Error> { .idle_timeout(Some(Duration::from_secs(60 * 2))) .build(mgr) .await?; - + // Set up the API routes let posts_api = Router::new() .route("/", get(list_posts).post(create_post)) @@ -134,7 +125,6 @@ async fn main() -> Result<(), Error> { run(app).await } - fn establish_connection(config: &str) -> BoxFuture> { let fut = async { // We first set up the way we want rustls to work. diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 0366f32d..39fc813e 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum-diesel" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" bb8 = "0.8.0" @@ -19,5 +12,3 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index bb47152d..b7247be4 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -7,7 +7,7 @@ use axum::{ use bb8::Pool; use diesel::prelude::*; use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; -use lambda_http::{http::StatusCode, run, Error}; +use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; table! { @@ -93,13 +93,7 @@ fn internal_server_error(err: E) -> ServerError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Set up the database connection let db_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable"); diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml index ea437052..228fc0ae 100644 --- a/examples/http-axum-middleware/Cargo.toml +++ b/examples/http-axum-middleware/Cargo.toml @@ -6,13 +6,8 @@ edition = "2021" [dependencies] axum = "0.7" lambda_http = { path = "../../lambda-http", default-features = false, features = [ - "apigw_rest", + "apigw_rest", "tracing" ] } lambda_runtime = { path = "../../lambda-runtime" } serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = [ - "fmt", -] } - diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs index 41c5bf4b..b1e92811 100644 --- a/examples/http-axum-middleware/src/main.rs +++ b/examples/http-axum-middleware/src/main.rs @@ -11,7 +11,7 @@ use axum::{response::Json, routing::post, Router}; use lambda_http::request::RequestContext::ApiGatewayV1; -use lambda_http::{run, Error}; +use lambda_http::{run, tracing, Error}; use serde_json::{json, Value}; // Sample middleware that logs the request id @@ -29,11 +29,7 @@ async fn handler_sample(body: Json) -> Json { #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/testStage/hello/world", post(handler_sample)) diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 5257bdb0..7ab3c0ec 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" lambda_http = { path = "../../lambda-http" } @@ -17,5 +10,3 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index ae7b0921..dcd5d154 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -14,7 +14,7 @@ use axum::{ routing::{get, post}, Router, }; -use lambda_http::{run, Error}; +use lambda_http::{run, tracing, Error}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::env::set_var; @@ -65,13 +65,7 @@ async fn main() -> Result<(), Error> { set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/", get(root)) diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index 1a218330..c7a51507 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -3,18 +3,7 @@ name = "http-basic-lambda" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index 5794dc8b..d0e41561 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; /// This is the main body for the function. /// Write your code inside it. @@ -20,13 +20,7 @@ async fn function_handler(_event: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 059a3f63..b8e51031 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -3,19 +3,8 @@ name = "http-cors" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.5", features = ["cors"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index e60fb441..ffac0a9e 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -1,18 +1,12 @@ use lambda_http::{ - http::Method, service_fn, tower::ServiceBuilder, Body, Error, IntoResponse, Request, RequestExt, Response, + http::Method, service_fn, tower::ServiceBuilder, tracing, Body, Error, IntoResponse, Request, RequestExt, Response, }; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Define a layer to inject CORS headers let cors_layer = CorsLayer::new() diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index be95f867..f2b8db98 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -3,24 +3,12 @@ name = "http-dynamodb" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] simple-error = "0.3.0" serde_json = "1.0.107" serde = { version = "1.0.189", features = ["derive"] } serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } aws-sdk-dynamodb = "0.33.0" aws-config = "0.56.1" tokio = { version = "1.33.0", features = ["macros"] } -tracing = { version = "0.1.40", features = ["log"] } -tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index b2e8af20..e5cbb2a3 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,8 +1,7 @@ -use aws_sdk_dynamodb::{Client}; -use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use aws_sdk_dynamodb::Client; +use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; use serde::{Deserialize, Serialize}; use serde_dynamo::to_attribute_value; -use tracing::info; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { @@ -22,7 +21,7 @@ async fn handle_request(db_client: &Client, event: Request) -> Result Result Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); //Get config from environment. let config = aws_config::load_from_env().await; @@ -93,7 +86,7 @@ pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Er .item("first_name", first_av) .item("last_name", last_av); - info!("adding item to DynamoDB"); + tracing::info!("adding item to DynamoDB"); let _resp = request.send().await?; diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 7aeb1189..2cadda95 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -3,18 +3,7 @@ name = "http-query-parameters" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index e189d12d..1f7110a5 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{run, service_fn, tracing, Error, IntoResponse, Request, RequestExt, Response}; /// This is the main body for the function. /// Write your code inside it. @@ -23,13 +23,7 @@ async fn function_handler(event: Request) -> Result { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index f4060428..f6c56526 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -3,18 +3,7 @@ name = "http-raw-path" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index 7fa6e6d5..70b4df4d 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -1,15 +1,9 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; +use lambda_http::{service_fn, tracing, Error, IntoResponse, Request, RequestExt}; #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); lambda_http::run(service_fn(func)).await?; Ok(()) diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 207f253b..923ceecc 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -3,18 +3,7 @@ name = "http-shared-resource" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index d76ccec4..cff9e785 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{service_fn, Body, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{service_fn, tracing, Body, Error, IntoResponse, Request, RequestExt, Response}; struct SharedClient { name: &'static str, @@ -13,13 +13,7 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Create the "client" and a reference to it, so that we can pass this into the handler closure below. let shared_client = SharedClient { diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 0b0c46a9..36389f0e 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -3,17 +3,8 @@ name = "http-tower-trace" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.5", features = ["trace"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index 072f8256..df2b9007 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -1,7 +1,7 @@ +use lambda_http::tracing::{self, Level}; use lambda_http::{run, tower::ServiceBuilder, Error}; use lambda_http::{Request, Response}; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; -use tracing::Level; async fn handler(_req: Request) -> Result, Error> { Ok(Response::new("Success".into())) @@ -10,13 +10,7 @@ async fn handler(_req: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let layer = TraceLayer::new_for_http() .on_request(DefaultOnRequest::new().level(Level::INFO)) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index ba0898a3..aa11f1cb 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -13,6 +13,10 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "README.md" +[features] +default = ["tracing"] +tracing = ["lambda_runtime_api_client/tracing"] + [dependencies] async-stream = "0.3" bytes = { workspace = true } @@ -24,7 +28,6 @@ hyper-util = { workspace = true } lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" -tracing = { version = "0.1", features = ["log"] } tokio = { version = "1.0", features = [ "macros", "io-util", @@ -33,3 +36,4 @@ tokio = { version = "1.0", features = [ ] } tokio-stream = "0.1.2" tower = { version = "0.4", features = ["make", "util"] } +tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 796eb3ef..81c16337 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -23,6 +23,10 @@ pub use telemetry::*; /// Include several request builders to interact with the Extension API. pub mod requests; +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime_api_client::tracing; + /// Execute the given events processor pub async fn run(events_processor: E) -> Result<(), Error> where diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9facc13d..8617e7ce 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -16,12 +16,13 @@ categories = ["web-programming::http-server"] readme = "README.md" [features] -default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"] +default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb", "tracing"] apigw_rest = [] apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] +tracing = ["lambda_runtime/tracing"] [dependencies] base64 = { workspace = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index bc9e753d..265f5ef0 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -65,8 +65,10 @@ extern crate maplit; pub use http::{self, Response}; -use lambda_runtime::LambdaEvent; -pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime::tracing; +pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service}; use request::RequestFuture; use response::ResponseFuture; diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 12b26043..2fa13207 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -13,6 +13,10 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "README.md" +[features] +default = ["tracing"] +tracing = ["dep:tracing", "dep:tracing-subscriber"] + [dependencies] bytes = { workspace = true } futures-channel = { workspace = true } @@ -30,3 +34,5 @@ hyper-util = { workspace = true, features = [ tower = { workspace = true, features = ["util"] } tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } +tracing = { version = "0.1", features = ["log"], optional = true } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index ec7418ba..8e52d416 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -17,6 +17,9 @@ mod error; pub use error::*; pub mod body; +#[cfg(feature = "tracing")] +pub mod tracing; + /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] pub struct Client { diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs new file mode 100644 index 00000000..babc817c --- /dev/null +++ b/lambda-runtime-api-client/src/tracing.rs @@ -0,0 +1,41 @@ +//! This module provides primitives to work with `tracing` +//! and `tracing-subscriber` in Lambda functions. +//! +//! The `tracing` and `tracing-subscriber` crates are re-exported +//! so you don't have to include them as direct dependencies in +//! your projects. + +use std::{env, str::FromStr}; + +use subscriber::filter::{EnvFilter, LevelFilter}; +/// Re-export the `tracing` crate to have access to tracing macros +/// like `info!`, `debug!`, `trace!` and so on. +pub use tracing::*; + +/// Re-export the `tracing-subscriber` crate to build your own subscribers. +pub use tracing_subscriber as subscriber; + +/// Initialize `tracing-subscriber` with default options. +/// The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. +/// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// if they're configured for your function. +/// By default, the log level to emit events is `INFO`. +pub fn init_default_subscriber() { + let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); + let log_level = Level::from_str(&env::var("AWS_LAMBDA_LOG_LEVEL").unwrap_or_default()).unwrap_or(Level::INFO); + + let collector = tracing_subscriber::fmt() + .with_target(false) + .without_time() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::from_level(log_level).into()) + .from_env_lossy(), + ); + + if log_format.eq_ignore_ascii_case("json") { + collector.json().init() + } else { + collector.init() + } +} diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 94a3c201..1a34a67d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -14,8 +14,8 @@ keywords = ["AWS", "Lambda", "API"] readme = "../README.md" [features] -default = ["simulated"] -simulated = [] +default = ["tracing"] +tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index b4964954..3fe56b03 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,6 +7,7 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. +use ::tracing::{error, trace, Instrument}; use bytes::Bytes; use futures::FutureExt; use http_body_util::BodyExt; @@ -24,12 +25,16 @@ use std::{ use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; -use tracing::{error, trace, Instrument}; mod deserializer; mod requests; /// Utilities for Lambda Streaming functions. pub mod streaming; + +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime_api_client::tracing; + /// Types available to a Lambda function. mod types; From e0e95e61cba0a8a695d4766d9215cbe839caf3a7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 20:03:13 -0800 Subject: [PATCH 279/394] Release runtime 0.10 and events 0.15 (#832) - Support advanced logging controls. - Support CloudWatch SNS notifications. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 8 ++++---- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 3010aedc..2144a9a9 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.14.0" +version = "0.15.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index aa11f1cb..a7523e17 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "David Calavera ", @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8617e7ce..e05347ec 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.3" +version = "0.10.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.9.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.10.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -46,14 +46,14 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.14.0" +version = "0.15.0" default-features = false features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 2fa13207..819d5ed7 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1a34a67d..d61e5594 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.2" +version = "0.10.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" From 29e148a1b69c4e869008ba7f1cbefc46ebff851e Mon Sep 17 00:00:00 2001 From: Benjamen Pyle Date: Mon, 4 Mar 2024 14:55:09 -0600 Subject: [PATCH 280/394] Add struct for CodeDeployLifecycleEvent (#835) CodeDeploy Hooks Lifecycle Event support * Added struct * Added unit test --- lambda-events/src/event/codedeploy/mod.rs | 21 +++++++++++++++++++ .../example-codedeploy-lifecycle-event.json | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 896f509f..d51bf8aa 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -65,10 +65,31 @@ pub struct CodeDeployEventDetail { pub deployment_group: Option, } +#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct CodeDeployLifecycleEvent { + pub deployment_id: String, + pub lifecycle_event_hook_execution_id: String, +} + #[cfg(test)] mod test { use super::*; + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_lifecycle_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-lifecycle-event.json"); + let parsed: CodeDeployLifecycleEvent = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.deployment_id, "d-deploymentId".to_string()); + assert_eq!(parsed.lifecycle_event_hook_execution_id, "eyJlbmNyeXB0ZWREYXRhIjoiY3VHU2NjdkJXUTJQUENVd2dkYUNGRVg0dWlpME9UWVdHTVhZcDRXVW5LYUVKc21EaUFPMkNLNXMwMWFrNDlYVStlbXdRb29xS3NJTUNVQ3RYRGFZSXc1VTFwUllvMDhmMzdlbDZFeDVVdjZrNFc0eU5waGh6YTRvdkNWcmVveVR6OWdERlM2SmlIYW1TZz09IiwiaXZQYXJhbWV0ZXJTcGVjIjoiTm1ZNFR6RzZxQVhHamhhLyIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ==".to_string()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployLifecycleEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "codedeploy")] fn example_codedeploy_deployment_event() { diff --git a/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json b/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json new file mode 100644 index 00000000..41baa76c --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json @@ -0,0 +1,5 @@ +{ + "DeploymentId": "d-deploymentId", + "LifecycleEventHookExecutionId": "eyJlbmNyeXB0ZWREYXRhIjoiY3VHU2NjdkJXUTJQUENVd2dkYUNGRVg0dWlpME9UWVdHTVhZcDRXVW5LYUVKc21EaUFPMkNLNXMwMWFrNDlYVStlbXdRb29xS3NJTUNVQ3RYRGFZSXc1VTFwUllvMDhmMzdlbDZFeDVVdjZrNFc0eU5waGh6YTRvdkNWcmVveVR6OWdERlM2SmlIYW1TZz09IiwiaXZQYXJhbWV0ZXJTcGVjIjoiTm1ZNFR6RzZxQVhHamhhLyIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ==" +} + From f610ff6a377de8e7fc814df741def896503dcd97 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Mon, 11 Mar 2024 16:35:57 -0700 Subject: [PATCH 281/394] Add CloudFormationCustomResourceResponse struct. (#838) Co-authored-by: Michael Wallace --- lambda-events/src/event/cloudformation/mod.rs | 30 +++++++++++++++++++ ...oudformation-custom-resource-response.json | 14 +++++++++ 2 files changed, 44 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 0d3f816c..3123d8af 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -1,5 +1,6 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; +use std::collections::HashMap; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] @@ -75,6 +76,26 @@ where pub resource_properties: P2, } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudFormationCustomResourceResponse { + pub status: CloudFormationCustomResourceResponseStatus, + pub reason: Option, + pub physical_resource_id: String, + pub stack_id: String, + pub request_id: String, + pub logical_resource_id: String, + pub no_echo: bool, + pub data: HashMap, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CloudFormationCustomResourceResponseStatus { + Success, + Failed, +} + #[cfg(test)] mod test { use std::collections::HashMap; @@ -136,4 +157,13 @@ mod test { let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + fn example_cloudformation_custom_resource_response() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-response.json"); + let parsed: CloudFormationCustomResourceResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudFormationCustomResourceResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json new file mode 100644 index 00000000..86d018ad --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json @@ -0,0 +1,14 @@ +{ + "Status": "FAILED", + "Reason": "This is a test failure.", + "PhysicalResourceId": "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "RequestId": "49347ca5-c603-44e5-a34b-10cf1854a887", + "LogicalResourceId": "CustomResource", + "NoEcho": false, + "Data": { + "Key1": "a", + "Key2": "b", + "Key3": "c" + } +} \ No newline at end of file From 240ae6d86f7fc2c7ce7402724f65fe9158d5d3a0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 11 Mar 2024 19:15:44 -0700 Subject: [PATCH 282/394] Stop using deprecated chrono's api. (#839) --- Cargo.toml | 1 + lambda-events/Cargo.toml | 2 +- lambda-events/src/encodings/time.rs | 40 +++++++++++++++-------------- lambda-extension/Cargo.toml | 2 +- lambda-extension/src/logs.rs | 4 +-- lambda-extension/src/telemetry.rs | 12 ++++----- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51d57ff4..cba3ba3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = ["examples"] [workspace.dependencies] base64 = "0.21" bytes = "1" +chrono = "0.4.35" futures = "0.3" futures-channel = "0.3" futures-util = "0.3" diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 2144a9a9..eb5d9c31 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -18,7 +18,7 @@ edition = "2021" [dependencies] base64 = { workspace = true } bytes = { workspace = true, features = ["serde"], optional = true } -chrono = { version = "0.4.31", default-features = false, features = [ +chrono = { workspace = true, default-features = false, features = [ "clock", "serde", "std", diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 203aff75..6d77b5cf 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Duration, TimeZone, Utc}; +use chrono::{DateTime, TimeDelta, TimeZone, Utc}; use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError}, @@ -55,11 +55,11 @@ impl DerefMut for SecondTimestamp { pub struct SecondDuration( #[serde(deserialize_with = "deserialize_duration_seconds")] #[serde(serialize_with = "serialize_duration_seconds")] - pub Duration, + pub TimeDelta, ); impl Deref for SecondDuration { - type Target = Duration; + type Target = TimeDelta; fn deref(&self) -> &Self::Target { &self.0 @@ -77,11 +77,11 @@ impl DerefMut for SecondDuration { pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] #[serde(serialize_with = "serialize_duration_minutes")] - pub Duration, + pub TimeDelta, ); impl Deref for MinuteDuration { - type Target = Duration; + type Target = TimeDelta; fn deref(&self) -> &Self::Target { &self.0 @@ -144,7 +144,7 @@ where .ok_or_else(|| D::Error::custom("invalid timestamp")) } -fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result +fn serialize_duration_seconds(duration: &TimeDelta, serializer: S) -> Result where S: Serializer, { @@ -153,15 +153,16 @@ where serializer.serialize_i64(seconds) } -fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result +fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let seconds = f64::deserialize(deserializer)?; - Ok(Duration::seconds(seconds as i64)) + TimeDelta::try_seconds(seconds as i64) + .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{}`", seconds))) } -fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result +fn serialize_duration_minutes(duration: &TimeDelta, serializer: S) -> Result where S: Serializer, { @@ -170,12 +171,13 @@ where serializer.serialize_i64(minutes) } -fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result +fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let minutes = f64::deserialize(deserializer)?; - Ok(Duration::minutes(minutes as i64)) + TimeDelta::try_minutes(minutes as i64) + .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{}`", minutes))) } fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> @@ -291,10 +293,10 @@ mod test { #[derive(Deserialize)] struct Test { #[serde(deserialize_with = "deserialize_duration_seconds")] - v: Duration, + v: TimeDelta, } - let expected = Duration::seconds(36); + let expected = TimeDelta::try_seconds(36).unwrap(); let data = serde_json::json!({ "v": 36, @@ -314,10 +316,10 @@ mod test { #[derive(Serialize)] struct Test { #[serde(serialize_with = "serialize_duration_seconds")] - v: Duration, + v: TimeDelta, } let instance = Test { - v: Duration::seconds(36), + v: TimeDelta::try_seconds(36).unwrap(), }; let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":36}"#)); @@ -328,10 +330,10 @@ mod test { #[derive(Deserialize)] struct Test { #[serde(deserialize_with = "deserialize_duration_minutes")] - v: Duration, + v: TimeDelta, } - let expected = Duration::minutes(36); + let expected = TimeDelta::try_minutes(36).unwrap(); let data = serde_json::json!({ "v": 36, @@ -351,10 +353,10 @@ mod test { #[derive(Serialize)] struct Test { #[serde(serialize_with = "serialize_duration_minutes")] - v: Duration, + v: TimeDelta, } let instance = Test { - v: Duration::minutes(36), + v: TimeDelta::try_minutes(36).unwrap(), }; let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":36}"#)); diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index a7523e17..fba19357 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -20,7 +20,7 @@ tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" bytes = { workspace = true } -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 4d1948a0..c3b0cda2 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -233,7 +233,7 @@ where #[cfg(test)] mod tests { use super::*; - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; #[test] fn deserialize_full() { @@ -242,7 +242,7 @@ mod tests { time: Utc .with_ymd_and_hms(2020, 8, 20, 12, 31, 32) .unwrap() - .checked_add_signed(Duration::milliseconds(123)) + .checked_add_signed(TimeDelta::try_milliseconds(123).unwrap()) .unwrap(), record: LambdaLogRecord::Function("hello world".to_string()), }; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index cfb4dde2..a7760892 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -316,7 +316,7 @@ where #[cfg(test)] mod deserialization_tests { use super::*; - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; macro_rules! deserialize_tests { ($($name:ident: $value:expr,)*) => { @@ -385,7 +385,7 @@ mod deserialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) .unwrap() - .checked_add_signed(Duration::milliseconds(165)) + .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap()) .unwrap(), duration_ms: 2598.0 }, @@ -394,7 +394,7 @@ mod deserialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) .unwrap() - .checked_add_signed(Duration::milliseconds(763)) + .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap()) .unwrap(), duration_ms: 0.0 }, @@ -473,7 +473,7 @@ mod deserialization_tests { #[cfg(test)] mod serialization_tests { - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; use super::*; macro_rules! serialize_tests { @@ -557,7 +557,7 @@ mod serialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) .unwrap() - .checked_add_signed(Duration::milliseconds(165)) + .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap()) .unwrap(), duration_ms: 2598.0 }, @@ -566,7 +566,7 @@ mod serialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) .unwrap() - .checked_add_signed(Duration::milliseconds(763)) + .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap()) .unwrap(), duration_ms: 0.0 }, From 5b399cfc90da0a6d9b92d8707108c13457d72546 Mon Sep 17 00:00:00 2001 From: FalkWoldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:48:07 +0100 Subject: [PATCH 283/394] Fix typo (#840) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a5920b7..1c84ae43 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. -By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subsriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: +By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subscriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: ```rust use lambda_runtime::{run, service_fn, tracing, Error}; From aeee3b6e8282aec74f589beb1af5166c9937f9b2 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Wed, 13 Mar 2024 15:54:20 +0000 Subject: [PATCH 284/394] feat: Add default Value type for EventBridgeEvent detail (#843) --- lambda-events/src/event/eventbridge/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index ed9bf447..49743e0a 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,15 +1,20 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use serde_json::Value; /// Parse EventBridge events. /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// /// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] -pub struct EventBridgeEvent { +pub struct EventBridgeEvent +where + T1: Serialize, + T1: DeserializeOwned, +{ #[serde(default)] pub version: Option, #[serde(default)] @@ -24,8 +29,8 @@ pub struct EventBridgeEvent { pub region: Option, #[serde(default)] pub resources: Option>, - #[serde(bound(deserialize = "T: DeserializeOwned"))] - pub detail: T, + #[serde(bound = "")] + pub detail: T1, } #[cfg(test)] From af639e86c773001f91e55c50511ef57b73c69040 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Thu, 14 Mar 2024 01:59:03 +0000 Subject: [PATCH 285/394] Chore: Adds eventbridge schedule event data-example fixture (#842) * Chore: Adds eventbridge schedule event data-example fixture * chore: added test for the new Scheduled event --- lambda-events/src/event/eventbridge/mod.rs | 13 +++++++++++++ .../src/fixtures/example-eventbridge-schedule.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 lambda-events/src/fixtures/example-eventbridge-schedule.json diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 49743e0a..990a853d 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -58,4 +58,17 @@ mod test { let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + fn example_eventbridge_schedule_event() { + let data = include_bytes!("../../fixtures/example-eventbridge-schedule.json"); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); + + assert_eq!("aws.events", parsed.source); + assert_eq!("Scheduled Event", parsed.detail_type); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-eventbridge-schedule.json b/lambda-events/src/fixtures/example-eventbridge-schedule.json new file mode 100644 index 00000000..602f83d2 --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-schedule.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "53dc4d37-cffa-4f76-80c9-8b7d4a4d2eaa", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "2015-10-08T16:53:06Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/my-scheduled-rule" + ], + "detail": {} +} From bae37bd22c943642f6a9cb955ced5ab4aa8462e1 Mon Sep 17 00:00:00 2001 From: noid11 <12472231+noid11@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:17:53 +0900 Subject: [PATCH 286/394] fix: Correct Markdown format for Tracing link in README (#844) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c84ae43..8f4838c5 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt ## Tracing and Logging -The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. +The Rust Runtime for Lambda integrates with the [Tracing](https://tracing.rs) libraries to provide tracing and logging. By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subscriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: From 731d201f4b82ec553c0f1e52c6fe6625aca2d82a Mon Sep 17 00:00:00 2001 From: Oliver Borchert Date: Fri, 22 Mar 2024 03:49:25 +0100 Subject: [PATCH 287/394] feat: Implement RFC for layering of runtime (#845) * feat: Implement RFC for layering of runtime * Add example * Fix compilation errors * Remove Send and 'static * Fix ci * Reduce diff * Implement review comments --- .github/workflows/build-events.yml | 6 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- Cargo.toml | 3 +- README.md | 2 +- examples/opentelemetry-tracing/Cargo.toml | 36 ++ examples/opentelemetry-tracing/src/lib.rs | 113 ++++ examples/opentelemetry-tracing/src/main.rs | 34 ++ lambda-events/src/custom_serde/mod.rs | 8 +- .../src/event/dynamodb/attributes.rs | 2 +- lambda-runtime-api-client/src/lib.rs | 15 +- lambda-runtime/Cargo.toml | 2 + lambda-runtime/src/layers/api_client.rs | 85 ++++ lambda-runtime/src/layers/api_response.rs | 173 +++++++ lambda-runtime/src/layers/mod.rs | 12 + lambda-runtime/src/layers/panic.rs | 118 +++++ lambda-runtime/src/layers/trace.rs | 68 +++ lambda-runtime/src/lib.rs | 413 +-------------- lambda-runtime/src/runtime.rs | 481 ++++++++++++++++++ lambda-runtime/src/types.rs | 20 - 20 files changed, 1160 insertions(+), 435 deletions(-) create mode 100644 examples/opentelemetry-tracing/Cargo.toml create mode 100644 examples/opentelemetry-tracing/src/lib.rs create mode 100644 examples/opentelemetry-tracing/src/main.rs create mode 100644 lambda-runtime/src/layers/api_client.rs create mode 100644 lambda-runtime/src/layers/api_response.rs create mode 100644 lambda-runtime/src/layers/mod.rs create mode 100644 lambda-runtime/src/layers/panic.rs create mode 100644 lambda-runtime/src/layers/trace.rs create mode 100644 lambda-runtime/src/runtime.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index d9f5c72a..3026f1ac 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -3,10 +3,10 @@ name: Check Lambda Events on: push: paths: - - 'lambda-events/**' + - "lambda-events/**" pull_request: paths: - - 'lambda-events/**' + - "lambda-events/**" jobs: build: @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index d9bcc989..d09b08c0 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 25cd83ec..a52927b5 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index cba3ba3b..09b046e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "lambda-runtime-api-client", "lambda-runtime", "lambda-extension", - "lambda-events" + "lambda-events", ] exclude = ["examples"] @@ -26,4 +26,5 @@ hyper = "1.0" hyper-util = "0.1.1" pin-project-lite = "0.2" tower = "0.4" +tower-layer = "0.3" tower-service = "0.3" diff --git a/README.md b/README.md index 8f4838c5..331635d2 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.66, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml new file mode 100644 index 00000000..27a778b5 --- /dev/null +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "opentelemetry-tracing" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Library dependencies +lambda_runtime = { path = "../../lambda-runtime" } +pin-project = "1" +opentelemetry-semantic-conventions = "0.14" +tower = "0.4" +tracing = "0.1" + +# Binary dependencies +opentelemetry = { version = "0.22", optional = true } +opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"], optional = true } +opentelemetry-stdout = { version = "0.3", features = ["trace"], optional = true } +serde_json = { version = "1.0", optional = true } +tokio = { version = "1", optional = true } +tracing-opentelemetry = { version = "0.23", optional = true } +tracing-subscriber = { version = "0.3", optional = true } + +[features] +build-binary = [ + "opentelemetry", + "opentelemetry_sdk", + "opentelemetry-stdout", + "serde_json", + "tokio", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[bin]] +name = "opentelemetry-tracing" +required-features = ["build-binary"] diff --git a/examples/opentelemetry-tracing/src/lib.rs b/examples/opentelemetry-tracing/src/lib.rs new file mode 100644 index 00000000..82f12a16 --- /dev/null +++ b/examples/opentelemetry-tracing/src/lib.rs @@ -0,0 +1,113 @@ +use std::future::Future; +use std::pin::Pin; +use std::task; + +use lambda_runtime::LambdaInvocation; +use opentelemetry_semantic_conventions::trace as traceconv; +use pin_project::pin_project; +use tower::{Layer, Service}; +use tracing::instrument::Instrumented; +use tracing::Instrument; + +/// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts +/// a function to flush OpenTelemetry after the end of the invocation. +pub struct OpenTelemetryLayer { + flush_fn: F, +} + +impl OpenTelemetryLayer +where + F: Fn() + Clone, +{ + pub fn new(flush_fn: F) -> Self { + Self { flush_fn } + } +} + +impl Layer for OpenTelemetryLayer +where + F: Fn() + Clone, +{ + type Service = OpenTelemetryService; + + fn layer(&self, inner: S) -> Self::Service { + OpenTelemetryService { + inner, + flush_fn: self.flush_fn.clone(), + coldstart: true, + } + } +} + +/// Tower service created by [OpenTelemetryLayer]. +pub struct OpenTelemetryService { + inner: S, + flush_fn: F, + coldstart: bool, +} + +impl Service for OpenTelemetryService +where + S: Service, + F: Fn() + Clone, +{ + type Error = S::Error; + type Response = (); + type Future = OpenTelemetryFuture, F>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let span = tracing::info_span!( + "Lambda function invocation", + "otel.name" = req.context.env_config.function_name, + { traceconv::FAAS_TRIGGER } = "http", + { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, + { traceconv::FAAS_COLDSTART } = self.coldstart + ); + + // After the first execution, we can set 'coldstart' to false + self.coldstart = false; + + let fut = self.inner.call(req).instrument(span); + OpenTelemetryFuture { + future: Some(fut), + flush_fn: self.flush_fn.clone(), + } + } +} + +/// Future created by [OpenTelemetryService]. +#[pin_project] +pub struct OpenTelemetryFuture { + #[pin] + future: Option, + flush_fn: F, +} + +impl Future for OpenTelemetryFuture +where + Fut: Future, + F: Fn(), +{ + type Output = Fut::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + // First, try to get the ready value of the future + let ready = task::ready!(self + .as_mut() + .project() + .future + .as_pin_mut() + .expect("future polled after completion") + .poll(cx)); + + // If we got the ready value, we first drop the future: this ensures that the + // OpenTelemetry span attached to it is closed and included in the subsequent flush. + Pin::set(&mut self.as_mut().project().future, None); + (self.project().flush_fn)(); + task::Poll::Ready(ready) + } +} diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs new file mode 100644 index 00000000..68038366 --- /dev/null +++ b/examples/opentelemetry-tracing/src/main.rs @@ -0,0 +1,34 @@ +use lambda_runtime::{LambdaEvent, Runtime}; +use opentelemetry::trace::TracerProvider; +use opentelemetry_sdk::{runtime, trace}; +use opentelemetry_tracing::OpenTelemetryLayer; +use tower::{service_fn, BoxError}; +use tracing_subscriber::prelude::*; + +async fn echo(event: LambdaEvent) -> Result { + Ok(event.payload) +} + +#[tokio::main] +async fn main() -> Result<(), BoxError> { + // Set up OpenTelemetry tracer provider that writes spans to stdout for debugging purposes + let exporter = opentelemetry_stdout::SpanExporter::default(); + let tracer_provider = trace::TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .build(); + + // Set up link between OpenTelemetry and tracing crate + tracing_subscriber::registry() + .with(tracing_opentelemetry::OpenTelemetryLayer::new( + tracer_provider.tracer("my-app"), + )) + .init(); + + // Initialize the Lambda runtime and add OpenTelemetry tracing + let runtime = Runtime::new(service_fn(echo)).layer(OpenTelemetryLayer::new(|| { + // Make sure that the trace is exported before the Lambda runtime is frozen + tracer_provider.force_flush(); + })); + runtime.run().await?; + Ok(()) +} diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 46d121d1..030cb5b3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -177,18 +177,18 @@ mod test { let test = r#"{"v": null}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); let test = r#"{}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); let test = r#"{"v": true}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(true, decoded.v); + assert!(decoded.v); let test = r#"{"v": false}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); } } diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index aad2cd4b..e1a42c83 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -83,7 +83,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { - AttributeValue::Bool(b) => assert_eq!(true, b), + AttributeValue::Bool(b) => assert!(b), other => panic!("unexpected value {:?}", other), } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 8e52d416..4315dfd7 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -4,10 +4,11 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. +use futures_util::{future::BoxFuture, FutureExt, TryFutureExt}; use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; use hyper::body::Incoming; use hyper_util::client::legacy::connect::HttpConnector; -use std::{convert::TryInto, fmt::Debug}; +use std::{convert::TryInto, fmt::Debug, future}; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); @@ -42,9 +43,15 @@ impl Client { impl Client { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. - pub async fn call(&self, req: Request) -> Result, BoxError> { - let req = self.set_origin(req)?; - self.client.request(req).await.map_err(Into::into) + pub fn call(&self, req: Request) -> BoxFuture<'static, Result, BoxError>> { + // NOTE: This method returns a boxed future such that the future has a static lifetime. + // Due to limitations around the Rust async implementation as of Mar 2024, this is + // required to minimize constraints on the handler passed to [lambda_runtime::run]. + let req = match self.set_origin(req) { + Ok(req) => req, + Err(err) => return future::ready(Err(err)).boxed(), + }; + self.client.request(req).map_err(Into::into).boxed() } /// Create a new client with a given base URI and HTTP connector. diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d61e5594..d9eca35a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -37,6 +37,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" @@ -48,6 +49,7 @@ tokio = { version = "1.0", features = [ ] } tokio-stream = "0.1.2" tower = { workspace = true, features = ["util"] } +tower-layer = { workspace = true } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] diff --git a/lambda-runtime/src/layers/api_client.rs b/lambda-runtime/src/layers/api_client.rs new file mode 100644 index 00000000..b6d9acf8 --- /dev/null +++ b/lambda-runtime/src/layers/api_client.rs @@ -0,0 +1,85 @@ +use crate::LambdaInvocation; +use futures::{future::BoxFuture, ready, FutureExt, TryFutureExt}; +use hyper::body::Incoming; +use lambda_runtime_api_client::{body::Body, BoxError, Client}; +use pin_project::pin_project; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task; +use tower::Service; +use tracing::error; + +/// Tower service that sends a Lambda Runtime API response to the Lambda Runtime HTTP API using +/// a previously initialized client. +/// +/// This type is only meant for internal use in the Lambda runtime crate. It neither augments the +/// inner service's request type nor its error type. However, this service returns an empty +/// response `()` as the Lambda request has been completed. +pub struct RuntimeApiClientService { + inner: S, + client: Arc, +} + +impl RuntimeApiClientService { + pub fn new(inner: S, client: Arc) -> Self { + Self { inner, client } + } +} + +impl Service for RuntimeApiClientService +where + S: Service, + S::Future: Future, BoxError>>, +{ + type Response = (); + type Error = S::Error; + type Future = RuntimeApiClientFuture; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let request_fut = self.inner.call(req); + let client = self.client.clone(); + RuntimeApiClientFuture::First(request_fut, client) + } +} + +#[pin_project(project = RuntimeApiClientFutureProj)] +pub enum RuntimeApiClientFuture { + First(#[pin] F, Arc), + Second(#[pin] BoxFuture<'static, Result, BoxError>>), +} + +impl Future for RuntimeApiClientFuture +where + F: Future, BoxError>>, +{ + type Output = Result<(), BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + // NOTE: We loop here to directly poll the second future once the first has finished. + task::Poll::Ready(loop { + match self.as_mut().project() { + RuntimeApiClientFutureProj::First(fut, client) => match ready!(fut.poll(cx)) { + Ok(ok) => { + // NOTE: We use 'client.call_boxed' here to obtain a future with static + // lifetime. Otherwise, this future would need to be self-referential... + let next_fut = client + .call(ok) + .map_err(|err| { + error!(error = ?err, "failed to send request to Lambda Runtime API"); + err + }) + .boxed(); + self.set(RuntimeApiClientFuture::Second(next_fut)); + } + Err(err) => break Err(err), + }, + RuntimeApiClientFutureProj::Second(fut) => break ready!(fut.poll(cx)).map(|_| ()), + } + }) + } +} diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs new file mode 100644 index 00000000..266402cf --- /dev/null +++ b/lambda-runtime/src/layers/api_response.rs @@ -0,0 +1,173 @@ +use crate::requests::{EventCompletionRequest, IntoRequest}; +use crate::runtime::LambdaInvocation; +use crate::types::Diagnostic; +use crate::{deserializer, IntoFunctionResponse}; +use crate::{EventErrorRequest, LambdaEvent}; +use futures::ready; +use futures::Stream; +use lambda_runtime_api_client::{body::Body, BoxError}; +use pin_project::pin_project; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; +use std::task; +use tower::Service; +use tracing::{error, trace}; + +/// Tower service that turns the result or an error of a handler function into a Lambda Runtime API +/// response. +/// +/// This type is only meant for internal use in the Lambda runtime crate. The service augments both +/// inputs and outputs: the input is converted from a [LambdaInvocation] into a [LambdaEvent] +/// while any errors encountered during the conversion are turned into error responses. The service +/// outputs either a HTTP request to send to the Lambda Runtime API or a boxed error which ought to +/// be propagated to the caller to terminate the runtime. +pub struct RuntimeApiResponseService< + S, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, +> { + inner: S, + _phantom: PhantomData<( + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + )>, +} + +impl + RuntimeApiResponseService +{ + pub fn new(inner: S) -> Self { + Self { + inner, + _phantom: PhantomData, + } + } +} + +impl<'a, S, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> + Service + for RuntimeApiResponseService< + S, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + > +where + S: Service, Response = Response, Error = Diagnostic<'a>>, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + type Response = http::Request; + type Error = BoxError; + type Future = + RuntimeApiResponseFuture<'a, S::Future, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner + .poll_ready(cx) + .map_err(|err| BoxError::from(format!("{}: {}", err.error_type, err.error_message))) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + #[cfg(debug_assertions)] + if req.parts.status.is_server_error() { + error!("Lambda Runtime server returned an unexpected error"); + return RuntimeApiResponseFuture::Ready(Some(Err(req.parts.status.to_string().into()))); + } + + // Utility closure to propagate potential error from conditionally executed trace + let trace_fn = || { + trace!( + body = std::str::from_utf8(&req.body)?, + "raw JSON event received from Lambda" + ); + Ok(()) + }; + if let Err(err) = trace_fn() { + error!(error = ?err, "failed to parse raw JSON event received from Lambda"); + return RuntimeApiResponseFuture::Ready(Some(Err(err))); + }; + + let request_id = req.context.request_id.clone(); + let lambda_event = match deserializer::deserialize::(&req.body, req.context) { + Ok(lambda_event) => lambda_event, + Err(err) => match build_event_error_request(&request_id, err) { + Ok(request) => return RuntimeApiResponseFuture::Ready(Some(Ok(request))), + Err(err) => { + error!(error = ?err, "failed to build error response for Lambda Runtime API"); + return RuntimeApiResponseFuture::Ready(Some(Err(err))); + } + }, + }; + + // Once the handler input has been generated successfully, the + let fut = self.inner.call(lambda_event); + RuntimeApiResponseFuture::Future(fut, request_id, PhantomData) + } +} + +fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, BoxError> +where + T: Into> + Debug, +{ + error!(error = ?err, "building error response for Lambda Runtime API"); + EventErrorRequest::new(request_id, err).into_req() +} + +#[pin_project(project = RuntimeApiResponseFutureProj)] +pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> { + Future( + #[pin] F, + String, + PhantomData<( + &'a (), + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + )>, + ), + Ready(Option, BoxError>>), +} + +impl<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> Future + for RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +where + F: Future>>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + type Output = Result, BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + task::Poll::Ready(match self.as_mut().project() { + RuntimeApiResponseFutureProj::Future(fut, request_id, _) => match ready!(fut.poll(cx)) { + Ok(ok) => EventCompletionRequest::new(request_id, ok).into_req(), + Err(err) => EventErrorRequest::new(request_id, err).into_req(), + }, + RuntimeApiResponseFutureProj::Ready(ready) => ready.take().expect("future polled after completion"), + }) + } +} diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs new file mode 100644 index 00000000..27ce0d68 --- /dev/null +++ b/lambda-runtime/src/layers/mod.rs @@ -0,0 +1,12 @@ +// Internally used services. +mod api_client; +mod api_response; +mod panic; + +// Publicly available services. +mod trace; + +pub(crate) use api_client::RuntimeApiClientService; +pub(crate) use api_response::RuntimeApiResponseService; +pub(crate) use panic::CatchPanicService; +pub use trace::TracingLayer; diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs new file mode 100644 index 00000000..26ceeecc --- /dev/null +++ b/lambda-runtime/src/layers/panic.rs @@ -0,0 +1,118 @@ +use crate::{Diagnostic, LambdaEvent}; +use futures::{future::CatchUnwind, FutureExt}; +use pin_project::pin_project; +use std::any::Any; +use std::borrow::Cow; +use std::fmt::Debug; +use std::future::Future; +use std::marker::PhantomData; +use std::panic::AssertUnwindSafe; +use std::pin::Pin; +use std::task; +use tower::Service; +use tracing::error; + +/// Tower service that transforms panics into an error. Panics are converted to errors both when +/// constructed in [tower::Service::call] and when constructed in the returned +/// [tower::Service::Future]. +/// +/// This type is only meant for internal use in the Lambda runtime crate. It neither augments the +/// inner service's request type, nor its response type. It merely transforms the error type +/// from `Into + Debug` into `Diagnostic<'a>` to turn panics into diagnostics. +#[derive(Clone)] +pub struct CatchPanicService<'a, S> { + inner: S, + _phantom: PhantomData<&'a ()>, +} + +impl<'a, S> CatchPanicService<'a, S> { + pub fn new(inner: S) -> Self { + Self { + inner, + _phantom: PhantomData, + } + } +} + +impl<'a, S, Payload> Service> for CatchPanicService<'a, S> +where + S: Service>, + S::Future: 'a, + S::Error: Into> + Debug, +{ + type Error = Diagnostic<'a>; + type Response = S::Response; + type Future = CatchPanicFuture<'a, S::Future>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx).map_err(|err| err.into()) + } + + fn call(&mut self, req: LambdaEvent) -> Self::Future { + // Catch panics that result from calling `call` on the service + let task = std::panic::catch_unwind(AssertUnwindSafe(|| self.inner.call(req))); + + // Catch panics that result from polling the future returned from `call` + match task { + Ok(task) => { + let fut = AssertUnwindSafe(task).catch_unwind(); + CatchPanicFuture::Future(fut, PhantomData) + } + Err(err) => { + error!(error = ?err, "user handler panicked"); + CatchPanicFuture::Error(err) + } + } + } +} + +/// Future returned by [CatchPanicService]. +#[pin_project(project = CatchPanicFutureProj)] +pub enum CatchPanicFuture<'a, F> { + Future(#[pin] CatchUnwind>, PhantomData<&'a ()>), + Error(Box), +} + +impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> +where + F: Future>, + E: Into> + Debug, +{ + type Output = Result>; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + use task::Poll; + match self.project() { + CatchPanicFutureProj::Future(fut, _) => match fut.poll(cx) { + Poll::Ready(ready) => match ready { + Ok(inner_result) => Poll::Ready(inner_result.map_err(|err| err.into())), + Err(err) => { + error!(error = ?err, "user handler panicked"); + Poll::Ready(Err(Self::build_panic_diagnostic(&err))) + } + }, + Poll::Pending => Poll::Pending, + }, + CatchPanicFutureProj::Error(err) => Poll::Ready(Err(Self::build_panic_diagnostic(err))), + } + } +} + +impl<'a, F> CatchPanicFuture<'a, F> { + fn build_panic_diagnostic(err: &Box) -> Diagnostic<'a> { + let error_type = type_name_of_val(&err); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {msg}") + } else { + "Lambda panicked".to_string() + }; + Diagnostic { + error_type: Cow::Borrowed(error_type), + error_message: Cow::Owned(msg), + } + } +} + +fn type_name_of_val(_: T) -> &'static str { + std::any::type_name::() +} diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs new file mode 100644 index 00000000..0d635154 --- /dev/null +++ b/lambda-runtime/src/layers/trace.rs @@ -0,0 +1,68 @@ +use std::env; +use tower::{Layer, Service}; +use tracing::{instrument::Instrumented, Instrument}; + +use crate::{Context, LambdaInvocation}; +use lambda_runtime_api_client::BoxError; +use std::task; + +/// Tower middleware to create a tracing span for invocations of the Lambda function. +#[derive(Default)] +pub struct TracingLayer {} + +impl TracingLayer { + /// Create a new tracing layer. + pub fn new() -> Self { + Self::default() + } +} + +impl Layer for TracingLayer { + type Service = TracingService; + + fn layer(&self, inner: S) -> Self::Service { + TracingService { inner } + } +} + +/// Tower service returned by [TracingLayer]. +pub struct TracingService { + inner: S, +} + +impl Service for TracingService +where + S: Service, +{ + type Response = (); + type Error = BoxError; + type Future = Instrumented; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let span = request_span(&req.context); + self.inner.call(req).instrument(span) + } +} + +/* ------------------------------------------- UTILS ------------------------------------------- */ + +fn request_span(ctx: &Context) -> tracing::Span { + match &ctx.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!( + "Lambda runtime invoke", + requestId = &ctx.request_id, + xrayTraceId = trace_id + ) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = &ctx.request_id) + } + } +} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 3fe56b03..9638df64 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,27 +7,22 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. -use ::tracing::{error, trace, Instrument}; -use bytes::Bytes; -use futures::FutureExt; -use http_body_util::BodyExt; -use hyper::{body::Incoming, http::Request}; -use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ - borrow::Cow, env, fmt::{self, Debug}, future::Future, - panic, sync::Arc, }; -use tokio_stream::{Stream, StreamExt}; +use tokio_stream::Stream; +use tower::util::ServiceFn; pub use tower::{self, service_fn, Service}; -use tower::{util::ServiceFn, ServiceExt}; mod deserializer; +/// Tower middleware to be applied to runtime invocatinos. +pub mod layers; mod requests; +mod runtime; /// Utilities for Lambda Streaming functions. pub mod streaming; @@ -38,13 +33,12 @@ pub use lambda_runtime_api_client::tracing; /// Types available to a Lambda function. mod types; -use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; +use requests::EventErrorRequest; +pub use runtime::{LambdaInvocation, Runtime}; pub use types::{ Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, }; -use types::invoke_request_id; - /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::BoxError; @@ -90,135 +84,12 @@ where service_fn(move |req: LambdaEvent| f(req.payload, req.context)) } -struct Runtime { - client: Client, - config: RefConfig, -} - -impl Runtime { - async fn run( - &self, - incoming: impl Stream, Error>> + Send, - mut handler: F, - ) -> Result<(), BoxError> - where - F: Service>, - F::Future: Future>, - F::Error: for<'a> Into> + fmt::Debug, - A: for<'de> Deserialize<'de>, - R: IntoFunctionResponse, - B: Serialize, - S: Stream> + Unpin + Send + 'static, - D: Into + Send, - E: Into + Send + Debug, - { - let client = &self.client; - tokio::pin!(incoming); - while let Some(next_event_response) = incoming.next().await { - trace!("New event arrived (run loop)"); - let event = next_event_response?; - let (parts, body) = event.into_parts(); - let request_id = invoke_request_id(&parts.headers)?; - - #[cfg(debug_assertions)] - if parts.status == http::StatusCode::NO_CONTENT { - // Ignore the event if the status code is 204. - // This is a way to keep the runtime alive when - // there are no events pending to be processed. - continue; - } - - let ctx: Context = Context::new(request_id, self.config.clone(), &parts.headers)?; - let request_span = ctx.request_span(); - - // Group the handling in one future and instrument it with the span - async { - let body = body.collect().await?.to_bytes(); - trace!( - body = std::str::from_utf8(&body)?, - "raw JSON event received from Lambda" - ); - - #[cfg(debug_assertions)] - if parts.status.is_server_error() { - error!("Lambda Runtime server returned an unexpected error"); - return Err(parts.status.to_string().into()); - } - - let lambda_event = match deserializer::deserialize(&body, ctx) { - Ok(lambda_event) => lambda_event, - Err(err) => { - let req = build_event_error_request(request_id, err)?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); - return Ok(()); - } - }; - - let req = match handler.ready().await { - Ok(handler) => { - // Catches panics outside of a `Future` - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); - - let task = match task { - // Catches panics inside of the `Future` - Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, - Err(err) => Err(err), - }; - - match task { - Ok(response) => match response { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionRequest::new(request_id, response).into_req() - } - Err(err) => build_event_error_request(request_id, err), - }, - Err(err) => { - error!("{:?}", err); - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {msg}") - } else { - "Lambda panicked".to_string() - }; - EventErrorRequest::new( - request_id, - Diagnostic { - error_type: Cow::Borrowed(error_type), - error_message: Cow::Owned(msg), - }, - ) - .into_req() - } - } - } - Err(err) => build_event_error_request(request_id, err), - }?; - - client.call(req).await.expect("Unable to send response to Runtime APIs"); - Ok::<(), Error>(()) - } - .instrument(request_span) - .await?; - } - Ok(()) - } -} - -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ { - async_stream::stream! { - loop { - trace!("Waiting for next event (incoming loop)"); - let req = NextEventRequest.into_req().expect("Unable to construct request"); - let res = client.call(req).await; - yield res; - } - } -} - /// Starts the Lambda Rust runtime and begins polling for events on the [Lambda /// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). /// +/// If you need more control over the runtime and add custom middleware, use the +/// [Runtime] type directly. +/// /// # Example /// ```no_run /// use lambda_runtime::{Error, service_fn, LambdaEvent}; @@ -237,272 +108,16 @@ fn incoming(client: &Client) -> impl Stream(handler: F) -> Result<(), Error> where - F: Service>, + F: Service, Response = R>, F::Future: Future>, F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, S: Stream> + Unpin + Send + 'static, - D: Into + Send, + D: Into + Send, E: Into + Send + Debug, { - trace!("Loading config from env"); - let config = Config::from_env(); - let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { - client, - config: Arc::new(config), - }; - - let client = &runtime.client; - let incoming = incoming(client); - runtime.run(incoming, handler).await -} - -fn type_name_of_val(_: T) -> &'static str { - std::any::type_name::() -} - -fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, Error> -where - T: Into> + Debug, -{ - error!("{:?}", err); // logs the error in CloudWatch - EventErrorRequest::new(request_id, err).into_req() -} - -#[cfg(test)] -mod endpoint_tests { - use crate::{ - incoming, - requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, - types::Diagnostic, - Config, Error, Runtime, - }; - use futures::future::BoxFuture; - use http::{HeaderValue, StatusCode}; - use http_body_util::BodyExt; - use httpmock::prelude::*; - - use lambda_runtime_api_client::Client; - use std::{borrow::Cow, env, sync::Arc}; - use tokio_stream::StreamExt; - - #[tokio::test] - async fn test_next_event() -> Result<(), Error> { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let mock = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = NextEventRequest.into_req()?; - let rsp = client.call(req).await.expect("Unable to send request"); - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - assert_eq!( - rsp.headers()["lambda-runtime-aws-request-id"], - &HeaderValue::from_static(request_id) - ); - assert_eq!( - rsp.headers()["lambda-runtime-deadline-ms"], - &HeaderValue::from_static(deadline) - ); - - let body = rsp.into_body().collect().await?.to_bytes(); - assert_eq!("{}", std::str::from_utf8(&body)?); - Ok(()) - } - - #[tokio::test] - async fn test_ok_response() -> Result<(), Error> { - let server = MockServer::start(); - - let mock = server.mock(|when, then| { - when.method(POST) - .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") - .body("\"{}\""); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); - let req = req.into_req()?; - - let rsp = client.call(req).await?; - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - Ok(()) - } - - #[tokio::test] - async fn test_error_response() -> Result<(), Error> { - let diagnostic = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data"), - }; - let body = serde_json::to_string(&diagnostic)?; - - let server = MockServer::start(); - let mock = server.mock(|when, then| { - when.method(POST) - .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") - .header("lambda-runtime-function-error-type", "unhandled") - .body(body); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = EventErrorRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - diagnostic, - }; - let req = req.into_req()?; - let rsp = client.call(req).await?; - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - Ok(()) - } - - #[tokio::test] - async fn successful_end_to_end_run() -> Result<(), Error> { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let next_request = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - let next_response = server.mock(|when, then| { - when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) - .body("{}"); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - async fn func(event: crate::LambdaEvent) -> Result { - let (event, _) = event.into_parts(); - Ok(event) - } - let f = crate::service_fn(func); - - // set env vars needed to init Config if they are not already set in the environment - if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { - env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); - } - if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); - } - if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); - } - if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); - } - if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); - } - if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); - } - let config = Config::from_env(); - - let runtime = Runtime { - client, - config: Arc::new(config), - }; - let client = &runtime.client; - let incoming = incoming(client).take(1); - runtime.run(incoming, f).await?; - - next_request.assert_async().await; - next_response.assert_async().await; - Ok(()) - } - - async fn run_panicking_handler(func: F) -> Result<(), Error> - where - F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result>, - { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let next_request = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - - let next_response = server.mock(|when, then| { - when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) - .header("lambda-runtime-function-error-type", "unhandled"); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let f = crate::service_fn(func); - - let config = Arc::new(Config { - function_name: "test_fn".to_string(), - memory: 128, - version: "1".to_string(), - log_stream: "test_stream".to_string(), - log_group: "test_log".to_string(), - }); - - let runtime = Runtime { client, config }; - let client = &runtime.client; - let incoming = incoming(client).take(1); - runtime.run(incoming, f).await?; - - next_request.assert_async().await; - next_response.assert_async().await; - Ok(()) - } - - #[tokio::test] - async fn panic_in_async_run() -> Result<(), Error> { - run_panicking_handler(|_| Box::pin(async { panic!("This is intentionally here") })).await - } - - #[tokio::test] - async fn panic_outside_async_run() -> Result<(), Error> { - run_panicking_handler(|_| { - panic!("This is intentionally here"); - }) - .await - } + let runtime = Runtime::new(handler).layer(layers::TracingLayer::new()); + runtime.run().await } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs new file mode 100644 index 00000000..0fc328cf --- /dev/null +++ b/lambda-runtime/src/runtime.rs @@ -0,0 +1,481 @@ +use super::requests::{IntoRequest, NextEventRequest}; +use super::types::{invoke_request_id, Diagnostic, IntoFunctionResponse, LambdaEvent}; +use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; +use crate::{Config, Context}; +use http_body_util::BodyExt; +use lambda_runtime_api_client::BoxError; +use lambda_runtime_api_client::Client as ApiClient; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::future::Future; +use std::sync::Arc; +use tokio_stream::{Stream, StreamExt}; +use tower::Layer; +use tower::{Service, ServiceExt}; +use tracing::trace; + +/* ----------------------------------------- INVOCATION ---------------------------------------- */ + +/// A simple container that provides information about a single invocation of a Lambda function. +pub struct LambdaInvocation { + /// The header of the request sent to invoke the Lambda function. + pub parts: http::response::Parts, + /// The body of the request sent to invoke the Lambda function. + pub body: bytes::Bytes, + /// The context of the Lambda invocation. + pub context: Context, +} + +/* ------------------------------------------ RUNTIME ------------------------------------------ */ + +/// Lambda runtime executing a handler function on incoming requests. +/// +/// Middleware can be added to a runtime using the [Runtime::layer] method in order to execute +/// logic prior to processing the incoming request and/or after the response has been sent back +/// to the Lambda Runtime API. +/// +/// # Example +/// ```no_run +/// use lambda_runtime::{Error, LambdaEvent, Runtime}; +/// use serde_json::Value; +/// use tower::service_fn; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let func = service_fn(func); +/// Runtime::new(func).run().await?; +/// Ok(()) +/// } +/// +/// async fn func(event: LambdaEvent) -> Result { +/// Ok(event.payload) +/// } +/// ```` +pub struct Runtime { + service: S, + config: Arc, + client: Arc, +} + +impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> + Runtime< + RuntimeApiClientService< + RuntimeApiResponseService< + CatchPanicService<'a, F>, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + >, + >, + > +where + F: Service, Response = Response>, + F::Future: Future>, + F::Error: Into> + Debug, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + /// Create a new runtime that executes the provided handler for incoming requests. + /// + /// In order to start the runtime and poll for events on the [Lambda Runtime + /// APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html), you must call + /// [Runtime::run]. + /// + /// Note that manually creating a [Runtime] does not add tracing to the executed handler + /// as is done by [super::run]. If you want to add the default tracing functionality, call + /// [Runtime::layer] with a [super::layers::TracingLayer]. + pub fn new(handler: F) -> Self { + trace!("Loading config from env"); + let config = Arc::new(Config::from_env()); + let client = Arc::new(ApiClient::builder().build().expect("Unable to create a runtime client")); + Self { + service: wrap_handler(handler, client.clone()), + config, + client, + } + } +} + +impl Runtime { + /// Add a new layer to this runtime. For an incoming request, this layer will be executed + /// before any layer that has been added prior. + /// + /// # Example + /// ```no_run + /// use lambda_runtime::{layers, Error, LambdaEvent, Runtime}; + /// use serde_json::Value; + /// use tower::service_fn; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// let runtime = Runtime::new(service_fn(echo)).layer( + /// layers::TracingLayer::new() + /// ); + /// runtime.run().await?; + /// Ok(()) + /// } + /// + /// async fn echo(event: LambdaEvent) -> Result { + /// Ok(event.payload) + /// } + /// ``` + pub fn layer(self, layer: L) -> Runtime + where + L: Layer, + L::Service: Service, + { + Runtime { + client: self.client, + config: self.config, + service: layer.layer(self.service), + } + } +} + +impl Runtime +where + S: Service, +{ + /// Start the runtime and begin polling for events on the Lambda Runtime API. + pub async fn run(self) -> Result<(), BoxError> { + let incoming = incoming(&self.client); + Self::run_with_incoming(self.service, self.config, incoming).await + } + + /// Internal utility function to start the runtime with a customized incoming stream. + /// This implements the core of the [Runtime::run] method. + pub(crate) async fn run_with_incoming( + mut service: S, + config: Arc, + incoming: impl Stream, BoxError>> + Send, + ) -> Result<(), BoxError> { + tokio::pin!(incoming); + while let Some(next_event_response) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = next_event_response?; + let (parts, incoming) = event.into_parts(); + + #[cfg(debug_assertions)] + if parts.status == http::StatusCode::NO_CONTENT { + // Ignore the event if the status code is 204. + // This is a way to keep the runtime alive when + // there are no events pending to be processed. + continue; + } + + // Build the invocation such that it can be sent to the service right away + // when it is ready + let body = incoming.collect().await?.to_bytes(); + let context = Context::new(invoke_request_id(&parts.headers)?, config.clone(), &parts.headers)?; + let invocation = LambdaInvocation { parts, body, context }; + + // Wait for service to be ready + let ready = service.ready().await?; + + // Once ready, call the service which will respond to the Lambda runtime API + ready.call(invocation).await?; + } + Ok(()) + } +} + +/* ------------------------------------------- UTILS ------------------------------------------- */ + +#[allow(clippy::type_complexity)] +fn wrap_handler<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>( + handler: F, + client: Arc, +) -> RuntimeApiClientService< + RuntimeApiResponseService< + CatchPanicService<'a, F>, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + >, +> +where + F: Service, Response = Response>, + F::Future: Future>, + F::Error: Into> + Debug, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + let safe_service = CatchPanicService::new(handler); + let response_service = RuntimeApiResponseService::new(safe_service); + RuntimeApiClientService::new(response_service, client) +} + +fn incoming( + client: &ApiClient, +) -> impl Stream, BoxError>> + Send + '_ { + async_stream::stream! { + loop { + trace!("Waiting for next event (incoming loop)"); + let req = NextEventRequest.into_req().expect("Unable to construct request"); + let res = client.call(req).await; + yield res; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ +/* TESTS */ +/* --------------------------------------------------------------------------------------------- */ + +#[cfg(test)] +mod endpoint_tests { + use super::{incoming, wrap_handler}; + use crate::{ + requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, + types::Diagnostic, + Config, Error, Runtime, + }; + use futures::future::BoxFuture; + use http::{HeaderValue, StatusCode}; + use http_body_util::BodyExt; + use httpmock::prelude::*; + + use lambda_runtime_api_client::Client; + use std::{borrow::Cow, env, sync::Arc}; + use tokio_stream::StreamExt; + + #[tokio::test] + async fn test_next_event() -> Result<(), Error> { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let mock = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = NextEventRequest.into_req()?; + let rsp = client.call(req).await.expect("Unable to send request"); + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + assert_eq!( + rsp.headers()["lambda-runtime-aws-request-id"], + &HeaderValue::from_static(request_id) + ); + assert_eq!( + rsp.headers()["lambda-runtime-deadline-ms"], + &HeaderValue::from_static(deadline) + ); + + let body = rsp.into_body().collect().await?.to_bytes(); + assert_eq!("{}", std::str::from_utf8(&body)?); + Ok(()) + } + + #[tokio::test] + async fn test_ok_response() -> Result<(), Error> { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") + .body("\"{}\""); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); + let req = req.into_req()?; + + let rsp = client.call(req).await?; + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) + } + + #[tokio::test] + async fn test_error_response() -> Result<(), Error> { + let diagnostic = Diagnostic { + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data"), + }; + let body = serde_json::to_string(&diagnostic)?; + + let server = MockServer::start(); + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") + .header("lambda-runtime-function-error-type", "unhandled") + .body(body); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = EventErrorRequest { + request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", + diagnostic, + }; + let req = req.into_req()?; + let rsp = client.call(req).await?; + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) + } + + #[tokio::test] + async fn successful_end_to_end_run() -> Result<(), Error> { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .body("{}"); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + async fn func(event: crate::LambdaEvent) -> Result { + let (event, _) = event.into_parts(); + Ok(event) + } + let f = crate::service_fn(func); + + // set env vars needed to init Config if they are not already set in the environment + if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { + env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); + } + if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); + } + if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); + } + if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); + } + if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); + } + if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); + } + let config = Config::from_env(); + + let client = Arc::new(client); + let runtime = Runtime { + client: client.clone(), + config: Arc::new(config), + service: wrap_handler(f, client), + }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + Runtime::run_with_incoming(runtime.service, runtime.config, incoming).await?; + + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) + } + + async fn run_panicking_handler(func: F) -> Result<(), Error> + where + F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result> + + Send + + 'static, + { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .header("lambda-runtime-function-error-type", "unhandled"); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let f = crate::service_fn(func); + + let config = Arc::new(Config { + function_name: "test_fn".to_string(), + memory: 128, + version: "1".to_string(), + log_stream: "test_stream".to_string(), + log_group: "test_log".to_string(), + }); + + let client = Arc::new(client); + let runtime = Runtime { + client: client.clone(), + config, + service: wrap_handler(f, client), + }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + Runtime::run_with_incoming(runtime.service, runtime.config, incoming).await?; + + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) + } + + #[tokio::test] + async fn panic_in_async_run() -> Result<(), Error> { + run_panicking_handler(|_| Box::pin(async { panic!("This is intentionally here") })).await + } + + #[tokio::test] + async fn panic_outside_async_run() -> Result<(), Error> { + run_panicking_handler(|_| { + panic!("This is intentionally here"); + }) + .await + } +} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 478f88fd..b4f10f71 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -7,12 +7,10 @@ use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, collections::HashMap, - env, fmt::{Debug, Display}, time::{Duration, SystemTime}, }; use tokio_stream::Stream; -use tracing::Span; /// Diagnostic information about an error. /// @@ -209,24 +207,6 @@ impl Context { pub fn deadline(&self) -> SystemTime { SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) } - - /// Create a new [`tracing::Span`] for an incoming invocation. - pub(crate) fn request_span(&self) -> Span { - match &self.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!( - "Lambda runtime invoke", - requestId = &self.request_id, - xrayTraceId = trace_id - ) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = &self.request_id) - } - } - } } /// Extract the invocation request id from the incoming request. From ddd22a3cf9d0d1e8428abd2549253871dea81495 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Mon, 25 Mar 2024 19:26:07 -0700 Subject: [PATCH 288/394] Add events for CDK custom resource provider framework. (#846) Co-authored-by: Michael Wallace --- lambda-events/src/event/cloudformation/mod.rs | 2 + .../src/event/cloudformation/provider.rs | 159 ++++++++++++++++++ ...stom-resource-provider-create-request.json | 12 ++ ...stom-resource-provider-delete-request.json | 13 ++ ...ion-custom-resource-provider-response.json | 9 + ...stom-resource-provider-update-request.json | 18 ++ 6 files changed, 213 insertions(+) create mode 100644 lambda-events/src/event/cloudformation/provider.rs create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 3123d8af..8dbf8b5e 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -2,6 +2,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +pub mod provider; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs new file mode 100644 index 00000000..e3ba5bea --- /dev/null +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -0,0 +1,159 @@ +//! These events are to be used with the CDK custom resource provider framework. +//! +//! Note that they are similar (but not the same) as the events in the `super` module. +//! +//! See https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html for details. + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(tag = "RequestType")] +pub enum CloudFormationCustomResourceRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(bound = "")] + Create(CreateRequest), + #[serde(bound = "")] + Update(UpdateRequest), + #[serde(bound = "")] + Delete(DeleteRequest), +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CreateRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct UpdateRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + pub physical_resource_id: String, + + #[serde(bound = "")] + pub old_resource_properties: P1, + + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct DeleteRequest +where + P2: DeserializeOwned + Serialize, +{ + pub physical_resource_id: String, + + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CommonRequestParams +where + P2: DeserializeOwned + Serialize, +{ + pub logical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, + pub resource_type: String, + pub request_id: String, + pub stack_id: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[serde(rename_all = "PascalCase")] +pub struct CloudFormationCustomResourceResponse +where + D: DeserializeOwned + Serialize, +{ + pub physical_resource_id: Option, + #[serde(bound = "")] + pub data: D, + pub no_echo: bool, +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use super::CloudFormationCustomResourceRequest::*; + use super::*; + + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] + #[serde(rename_all = "PascalCase")] + struct TestProperties { + key_1: String, + key_2: Vec, + key_3: HashMap, + } + + type TestRequest = CloudFormationCustomResourceRequest; + + #[test] + fn example_create_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-create-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Create(_) => (), + _ => panic!("expected Create request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_update_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-update-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Update(_) => (), + _ => panic!("expected Update request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_delete_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-delete-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Delete(_) => (), + _ => panic!("expected Delete request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_response() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-response.json"); + let parsed: CloudFormationCustomResourceResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudFormationCustomResourceResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json new file mode 100644 index 00000000..074a3712 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json @@ -0,0 +1,12 @@ +{ + "RequestType" : "Create", + "RequestId" : "82304eb2-bdda-469f-a33b-a3f1406d0a52", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json new file mode 100644 index 00000000..3fb58391 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json @@ -0,0 +1,13 @@ +{ + "RequestType" : "Delete", + "RequestId" : "ef70561d-d4ba-42a4-801b-33ad88dafc37", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json new file mode 100644 index 00000000..cff06191 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json @@ -0,0 +1,9 @@ +{ + "PhysicalResourceId": "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "NoEcho": false, + "Data": { + "Key1": "a", + "Key2": "b", + "Key3": "c" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json new file mode 100644 index 00000000..90280f72 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json @@ -0,0 +1,18 @@ +{ + "RequestType" : "Update", + "RequestId" : "49347ca5-c603-44e5-a34b-10cf1854a887", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "new-string", + "Key2" : [ "new-list" ], + "Key3" : { "Key4" : "new-map" } + }, + "OldResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} From e0a3827ec1c425364fa10be2122d15f61f436d5f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 27 Mar 2024 18:40:44 -0700 Subject: [PATCH 289/394] Release version 0.11.0 (#847) - Release the new layering system for the runtime. Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index fba19357..706dd4db 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index e05347ec..ef89aaee 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.10.0" +version = "0.11.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.10.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -53,7 +53,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 819d5ed7..e5c920bd 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d9eca35a..6386ae16 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.10.0" +version = "0.11.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From 165abcf3ae39e1cbb9ca3df72dc8f0dd1819cec7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 29 Mar 2024 18:51:21 -0700 Subject: [PATCH 290/394] Always export the _X_AMZN_TRACE_ID env variable. (#850) This variable is expected across runtimes regardless of other features. Signed-off-by: David Calavera --- lambda-runtime/src/layers/trace.rs | 3 --- lambda-runtime/src/runtime.rs | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 0d635154..7a9f8370 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -1,4 +1,3 @@ -use std::env; use tower::{Layer, Service}; use tracing::{instrument::Instrumented, Instrument}; @@ -53,7 +52,6 @@ where fn request_span(ctx: &Context) -> tracing::Span { match &ctx.xray_trace_id { Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); tracing::info_span!( "Lambda runtime invoke", requestId = &ctx.request_id, @@ -61,7 +59,6 @@ fn request_span(ctx: &Context) -> tracing::Span { ) } None => { - env::remove_var("_X_AMZN_TRACE_ID"); tracing::info_span!("Lambda runtime invoke", requestId = &ctx.request_id) } } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 0fc328cf..5ded610c 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -6,6 +6,7 @@ use http_body_util::BodyExt; use lambda_runtime_api_client::BoxError; use lambda_runtime_api_client::Client as ApiClient; use serde::{Deserialize, Serialize}; +use std::env; use std::fmt::Debug; use std::future::Future; use std::sync::Arc; @@ -176,6 +177,9 @@ where let context = Context::new(invoke_request_id(&parts.headers)?, config.clone(), &parts.headers)?; let invocation = LambdaInvocation { parts, body, context }; + // Setup Amazon's default tracing data + amzn_trace_env(&invocation.context); + // Wait for service to be ready let ready = service.ready().await?; @@ -232,6 +236,13 @@ fn incoming( } } +fn amzn_trace_env(ctx: &Context) { + match &ctx.xray_trace_id { + Some(trace_id) => env::set_var("_X_AMZN_TRACE_ID", trace_id), + None => env::remove_var("_X_AMZN_TRACE_ID"), + } +} + /* --------------------------------------------------------------------------------------------- */ /* TESTS */ /* --------------------------------------------------------------------------------------------- */ From c7a301484f49c4a2347a07424da67ca58de2d4a8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 29 Mar 2024 19:12:02 -0700 Subject: [PATCH 291/394] Release runtime 0.11.1 (#851) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ef89aaee..f8fe06c3 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.0" +version = "0.11.1" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.1", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6386ae16..8745b59f 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.0" +version = "0.11.1" authors = [ "David Calavera ", "Harold Sun ", From 5b1de1dc9331cb91907362598e67bcc3b00fadc4 Mon Sep 17 00:00:00 2001 From: Janosch Reppnow <40561502+jreppnow@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:54:31 +0900 Subject: [PATCH 292/394] fix: do not duplicate headers (single val, multi val) on return (#852) * fix: do not duplicate headers (single val, multi val) on return * fix: add explainer comment * fix: rustfmt --- lambda-http/src/response.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index cc721d46..e73a584d 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -69,7 +69,9 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }), #[cfg(feature = "apigw_http")] @@ -91,7 +93,9 @@ impl LambdaResponse { is_base64_encoded, status_code: status_code as i64, cookies, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }) } @@ -100,7 +104,9 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, status_description: Some(format!( "{} {}", @@ -113,7 +119,9 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }), #[cfg(feature = "pass_through")] @@ -465,7 +473,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-encoding":"gzip"},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# ) } @@ -483,7 +491,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# ) } @@ -501,7 +509,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -519,7 +527,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -553,7 +561,7 @@ mod tests { let json = serde_json::to_string(&res).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# ) } From 4a3dbc99c5c6802c363f214472f864fc5c721f0d Mon Sep 17 00:00:00 2001 From: Adam Esterline Date: Tue, 2 Apr 2024 19:33:40 -0600 Subject: [PATCH 293/394] fix: support string or string slices when deserializing `IamPolicyStatement` (#854) `Action` and `Resource` allow both `string` and `[string]` as values. Support deserializing `IamPolicyStatement` with either of these values. fixes: https://github.com/awslabs/aws-lambda-rust-runtime/issues/853 --- lambda-events/src/custom_serde/mod.rs | 21 +++++++++++++++ lambda-events/src/event/apigw/mod.rs | 26 +++++++++++++++++-- ...uth-response-with-single-value-action.json | 19 ++++++++++++++ ...h-response-with-single-value-resource.json | 19 ++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 030cb5b3..9d68c8d3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -92,6 +92,27 @@ where Ok(opt.unwrap_or_default()) } +/// Deserializes `Vec`, from a JSON `string` or `[string]`. +#[cfg(any(feature = "apigw", test))] +pub(crate) fn deserialize_string_or_slice<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum StringOrSlice { + String(String), + Slice(Vec), + } + + let string_or_slice = StringOrSlice::deserialize(deserializer)?; + + match string_or_slice { + StringOrSlice::Slice(slice) => Ok(slice), + StringOrSlice::String(s) => Ok(vec![s]), + } +} + #[cfg(test)] #[allow(deprecated)] mod test { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 1a9b1f1a..1777cf76 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,6 +1,6 @@ use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, - serialize_multi_value_headers, + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, deserialize_string_or_slice, http_method, + serialize_headers, serialize_multi_value_headers, }; use crate::encodings::Body; use http::{HeaderMap, Method}; @@ -737,11 +737,13 @@ pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(rename_all = "camelCase")] pub struct IamPolicyStatement { #[serde(rename = "Action")] + #[serde(deserialize_with = "deserialize_string_or_slice")] pub action: Vec, #[serde(default)] #[serde(rename = "Effect")] pub effect: Option, #[serde(rename = "Resource")] + #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, } @@ -832,6 +834,26 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_single_value_action() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-single-value-action.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_single_value_resource() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-single-value-resource.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_request() { diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json new file mode 100644 index 00000000..e656caaa --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json @@ -0,0 +1,19 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": ["arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"] + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json new file mode 100644 index 00000000..af96bb17 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json @@ -0,0 +1,19 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["execute-api:Invoke"], + "Effect": "Allow", + "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} From 115822a230acf9521a722b5877707ba39492c94c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 2 Apr 2024 20:30:22 -0700 Subject: [PATCH 294/394] Pin webp version (#855) The latest patch version updates a dependency that breaks our examples. Signed-off-by: David Calavera --- examples/basic-s3-object-lambda-thumbnail/Cargo.toml | 3 ++- examples/basic-s3-thumbnail/Cargo.toml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index ba750df9..905362c9 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -21,11 +21,12 @@ serde = "1" tokio = { version = "1", features = ["macros"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" -thumbnailer = "0.4.0" +thumbnailer = "0.5.1" mime = "0.3.16" async-trait = "0.1.66" ureq = "2.6.2" aws-smithy-http = "0.55.3" +webp = "=0.2.1" [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 1cd3b834..4b9ef3da 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -22,9 +22,10 @@ tokio = { version = "1", features = ["macros"] } aws-config = "0.55" aws-smithy-http = "0.55.3" aws-sdk-s3 = "0.28" -thumbnailer = "0.4.0" +thumbnailer = "0.5.1" mime = "0.3.16" async-trait = "0.1.68" +webp = "=0.2.1" [dev-dependencies] mockall = "0.11" From bb5a25ce665198a28f0fe44f18e4b54142df83c2 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Thu, 4 Apr 2024 05:55:12 +0300 Subject: [PATCH 295/394] Allow disabling `tracing` feature (#857) Without this PR, the `tracing` in the `lambda_runtime_api_client` is not really optional, when that crate is added to the dep tree by the main `lambda_runtime` crate. --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 8745b59f..c4b8e35b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client", default-features = false } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From a68de584154958c524692cb43dc208d520d05a13 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 4 Apr 2024 18:04:19 -0700 Subject: [PATCH 296/394] Cleanup IAM definitions (#856) * Cleanup IAM definitions We have two different definitions of IAM policies and statement. These changes centralize them both into one. These changes also add the `Condition` field that was missing from both implementations. These changes also make `Effect` to be an enum with the default `Allow`, instead of an optional string. This is more ergonomic than checking whether the effect is none or `allow`. Signed-off-by: David Calavera * Remove unnecesary module. Keep all deserialization logic together, it's easier to manage. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-events/src/custom_serde/mod.rs | 21 --- lambda-events/src/event/apigw/mod.rs | 42 +++-- lambda-events/src/event/iam/mod.rs | 171 ++++++++++++++++-- ...w-custom-auth-response-with-condition.json | 30 +++ .../example-apigw-custom-auth-response.json | 40 ++-- 6 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index eb5d9c31..8f2fca99 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -85,7 +85,7 @@ default = [ activemq = [] alb = ["bytes", "http", "http-body", "http-serde", "query_map"] -apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] +apigw = ["bytes", "http", "http-body", "http-serde", "iam", "query_map"] appsync = [] autoscaling = ["chrono"] bedrock_agent_runtime = [] diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 9d68c8d3..030cb5b3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -92,27 +92,6 @@ where Ok(opt.unwrap_or_default()) } -/// Deserializes `Vec`, from a JSON `string` or `[string]`. -#[cfg(any(feature = "apigw", test))] -pub(crate) fn deserialize_string_or_slice<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(serde::Deserialize)] - #[serde(untagged)] - enum StringOrSlice { - String(String), - Slice(Vec), - } - - let string_or_slice = StringOrSlice::deserialize(deserializer)?; - - match string_or_slice { - StringOrSlice::Slice(slice) => Ok(slice), - StringOrSlice::String(s) => Ok(vec![s]), - } -} - #[cfg(test)] #[allow(deprecated)] mod test { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 1777cf76..533bb77e 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,8 +1,9 @@ use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, deserialize_string_or_slice, http_method, - serialize_headers, serialize_multi_value_headers, + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, }; use crate::encodings::Body; +use crate::iam::IamPolicyStatement; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -723,30 +724,13 @@ where /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "PascalCase")] pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(default)] - #[serde(rename = "Version")] pub version: Option, - #[serde(rename = "Statement")] pub statement: Vec, } -/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct IamPolicyStatement { - #[serde(rename = "Action")] - #[serde(deserialize_with = "deserialize_string_or_slice")] - pub action: Vec, - #[serde(default)] - #[serde(rename = "Effect")] - pub effect: Option, - #[serde(rename = "Resource")] - #[serde(deserialize_with = "deserialize_string_or_slice")] - pub resource: Vec, -} - fn default_http_method() -> Method { Method::GET } @@ -1045,4 +1029,22 @@ mod test { assert_eq!(Some(1), fields.get("clientId").unwrap().as_u64()); assert_eq!(Some("Exata"), fields.get("clientName").unwrap().as_str()); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_statement_condition() { + use crate::iam::IamPolicyEffect; + + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-condition.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + let statement = parsed.policy_document.statement.first().unwrap(); + assert_eq!(IamPolicyEffect::Deny, statement.effect); + + let condition = statement.condition.as_ref().unwrap(); + assert_eq!(vec!["xxx"], condition["StringEquals"]["aws:SourceIp"]); + } } diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 12bf7ba9..ee35bbc8 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,25 +1,172 @@ -use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, collections::HashMap, fmt}; + +use serde::{ + de::{Error as DeError, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; /// `IamPolicyDocument` represents an IAM policy document. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { #[serde(default)] - #[serde(rename = "Version")] pub version: Option, - #[serde(rename = "Statement")] pub statement: Vec, } -/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] +/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] pub struct IamPolicyStatement { - #[serde(rename = "Action")] + #[serde(deserialize_with = "deserialize_string_or_slice")] pub action: Vec, - #[serde(default)] - #[serde(rename = "Effect")] - pub effect: Option, - #[serde(rename = "Resource")] + #[serde(default = "default_statement_effect")] + pub effect: IamPolicyEffect, + #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, + #[serde(default, deserialize_with = "deserialize_policy_condition")] + pub condition: Option, +} + +pub type IamPolicyCondition = HashMap>>; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub enum IamPolicyEffect { + #[default] + Allow, + Deny, +} + +fn default_statement_effect() -> IamPolicyEffect { + IamPolicyEffect::Allow +} + +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum StringOrSlice { + String(String), + Slice(Vec), +} + +/// Deserializes `Vec`, from a JSON `string` or `[string]`. +fn deserialize_string_or_slice<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let string_or_slice = StringOrSlice::deserialize(deserializer)?; + + match string_or_slice { + StringOrSlice::Slice(slice) => Ok(slice), + StringOrSlice::String(s) => Ok(vec![s]), + } +} + +fn deserialize_policy_condition<'de, D>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + de.deserialize_option(IamPolicyConditionVisitor) +} + +struct IamPolicyConditionVisitor; + +impl<'de> Visitor<'de> for IamPolicyConditionVisitor { + type Value = Option; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("lots of things can go wrong with a IAM Policy Condition") + } + + fn visit_unit(self) -> Result + where + E: DeError, + { + Ok(None) + } + + fn visit_none(self) -> Result + where + E: DeError, + { + Ok(None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(self) + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0)); + + while let Some((key, val)) = access.next_entry::, HashMap, StringOrSlice>>()? { + let mut value = HashMap::with_capacity(val.len()); + for (val_key, string_or_slice) in val { + let val = match string_or_slice { + StringOrSlice::Slice(slice) => slice, + StringOrSlice::String(s) => vec![s], + }; + value.insert(val_key.into_owned(), val); + } + + map.insert(key.into_owned(), value); + } + + Ok(Some(map)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_string_condition() { + let data = serde_json::json!({ + "condition": { + "StringEquals": { + "iam:RegisterSecurityKey": "Activate", + "iam:FIDO-certification": "L1plus" + } + } + }); + + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_policy_condition")] + condition: Option, + } + + let test: Test = serde_json::from_value(data).unwrap(); + let condition = test.condition.unwrap(); + assert_eq!(1, condition.len()); + + assert_eq!(vec!["Activate"], condition["StringEquals"]["iam:RegisterSecurityKey"]); + assert_eq!(vec!["L1plus"], condition["StringEquals"]["iam:FIDO-certification"]); + } + + #[test] + fn test_deserialize_slide_condition() { + let data = serde_json::json!({ + "condition": {"StringLike": {"s3:prefix": ["janedoe/*"]}} + }); + + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_policy_condition")] + condition: Option, + } + + let test: Test = serde_json::from_value(data).unwrap(); + let condition = test.condition.unwrap(); + assert_eq!(1, condition.len()); + + assert_eq!(vec!["janedoe/*"], condition["StringLike"]["s3:prefix"]); + } } diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json new file mode 100644 index 00000000..53a09b39 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json @@ -0,0 +1,30 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "execute-api:Invoke" + ], + "Effect": "Deny", + "Resource": [ + "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + ], + "Condition": { + "StringEquals": { + "aws:SourceIp": [ + "xxx" + ] + } + } + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json index 9b624141..b1502cde 100644 --- a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json @@ -1,19 +1,23 @@ { - "principalId": "yyyyyyyy", - "policyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": ["execute-api:Invoke"], - "Effect": "Allow|Deny", - "Resource": ["arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"] - } - ] - }, - "context": { - "stringKey": "value", - "numberKey": "1", - "booleanKey": "true" - }, - "usageIdentifierKey": "{api-key}" -} + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "execute-api:Invoke" + ], + "Effect": "Deny", + "Resource": [ + "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + ] + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} \ No newline at end of file From 46ba8c7a3747d554fd27f785629ac0c0de550669 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Fri, 12 Apr 2024 08:45:33 +0100 Subject: [PATCH 297/394] Update tracing.rs - AWS_LAMBDA_LOG_LEVEL rather than RUST_LOG (#859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tracing.rs - AWS_LAMBDA_LOG_LEVEL rather than RUST_LOG * Update tracing.rs LEVEL 😅 --- lambda-runtime-api-client/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index babc817c..6a15f36c 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -16,7 +16,7 @@ pub use tracing::*; pub use tracing_subscriber as subscriber; /// Initialize `tracing-subscriber` with default options. -/// The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. +/// The subscriber uses `AWS_LAMBDA_LOG_LEVEL` as the environment variable to determine the log level for your function. /// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. /// By default, the log level to emit events is `INFO`. From aed829506623ef0887be6fa845f5b1f166d2a803 Mon Sep 17 00:00:00 2001 From: HVKukkonen <54474620+HVKukkonen@users.noreply.github.com> Date: Mon, 22 Apr 2024 04:19:49 +0300 Subject: [PATCH 298/394] ApiGatewayV2CustomAuthorizerV2Request identity_source as optional (#860) --- lambda-events/src/event/apigw/mod.rs | 14 +++++- ...er-v2-request-without-identity-source.json | 50 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 533bb77e..d8310755 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -565,7 +565,8 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { /// nolint: stylecheck #[serde(default)] pub route_arn: Option, - pub identity_source: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub identity_source: Option>, #[serde(default)] pub route_key: Option, #[serde(default)] @@ -1008,6 +1009,17 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v2_request_without_identity_source() { + let data = + include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json"); + let parsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_console_request() { diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json new file mode 100644 index 00000000..cc79dda3 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json @@ -0,0 +1,50 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": ["cookie1", "cookie2"], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "$default", + "stage": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "pathParameters": { "parameter1": "value1" }, + "stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" } +} + From de822f9d870c21c06b504d218293099f691ced9f Mon Sep 17 00:00:00 2001 From: Christian Hoffmann <352753+hffmnn@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:51:55 +0200 Subject: [PATCH 299/394] fix(IamPolicyStatement): don't serialize None condition (#864) --- lambda-events/src/event/iam/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index ee35bbc8..36f59c7b 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -25,6 +25,7 @@ pub struct IamPolicyStatement { #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, #[serde(default, deserialize_with = "deserialize_policy_condition")] + #[serde(skip_serializing_if = "Option::is_none")] pub condition: Option, } @@ -169,4 +170,24 @@ mod tests { assert_eq!(vec!["janedoe/*"], condition["StringLike"]["s3:prefix"]); } + + #[test] + fn test_serialize_none_condition() { + let policy = IamPolicyStatement { + action: vec!["some:action".into()], + effect: IamPolicyEffect::Allow, + resource: vec!["some:resource".into()], + condition: None, + }; + let policy_ser = serde_json::to_value(policy).unwrap(); + + assert_eq!( + policy_ser, + serde_json::json!({ + "Action": ["some:action"], + "Effect": "Allow", + "Resource": ["some:resource"] + }) + ); + } } From 41f5fb5bcf0da5af10eb078b38075698343ac016 Mon Sep 17 00:00:00 2001 From: Jakub Wieczorek Date: Mon, 6 May 2024 01:46:02 +0200 Subject: [PATCH 300/394] Upgrade base64 to 0.22. (#867) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 09b046e8..0b01ee05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ exclude = ["examples"] [workspace.dependencies] -base64 = "0.21" +base64 = "0.22" bytes = "1" chrono = "0.4.35" futures = "0.3" From 94f7e100fde9008a0cc5b7a4f60ff7d59624bde7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 5 May 2024 20:58:44 -0700 Subject: [PATCH 301/394] Remove unused code. (#868) Cleanup an unused trait. Move tests into a tests module. Signed-off-by: David Calavera --- lambda-runtime/src/requests.rs | 107 +++++++++++++++------------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index d1e25e32..ec893710 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -2,7 +2,7 @@ use crate::types::ToStreamErrorTrailer; use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; -use http::{Method, Request, Response, Uri}; +use http::{Method, Request, Uri}; use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; use std::fmt::Debug; @@ -14,10 +14,6 @@ pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; } -pub(crate) trait IntoResponse { - fn into_rsp(self) -> Result, Error>; -} - // /runtime/invocation/next #[derive(Debug, Eq, PartialEq)] pub(crate) struct NextEventRequest; @@ -46,30 +42,6 @@ pub struct NextEventResponse<'a> { pub body: Vec, } -impl<'a> IntoResponse for NextEventResponse<'a> { - fn into_rsp(self) -> Result, Error> { - // let body: BoxyBody< = BoxBody::new(); - let rsp = Response::builder() - .header("lambda-runtime-aws-request-id", self.request_id) - .header("lambda-runtime-deadline-ms", self.deadline) - .header("lambda-runtime-invoked-function-arn", self.arn) - .header("lambda-runtime-trace-id", self.trace_id) - .body(Body::from(self.body))?; - Ok(rsp) - } -} -#[test] -fn test_next_event_request() { - let req = NextEventRequest; - let req = req.into_req().unwrap(); - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri(), &Uri::from_static("/2018-06-01/runtime/invocation/next")); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); -} - // /runtime/invocation/{AwsRequestId}/response pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> where @@ -218,25 +190,6 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { } } -#[test] -fn test_event_error_request() { - let req = EventErrorRequest { - request_id: "id", - diagnostic: Diagnostic { - error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), - error_message: std::borrow::Cow::Borrowed("Error parsing event data"), - }, - }; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); -} - // /runtime/init/error struct InitErrorRequest; @@ -254,15 +207,51 @@ impl IntoRequest for InitErrorRequest { } } -#[test] -fn test_init_error_request() { - let req = InitErrorRequest; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/init/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_next_event_request() { + let req = NextEventRequest; + let req = req.into_req().unwrap(); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.uri(), &Uri::from_static("/2018-06-01/runtime/invocation/next")); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } + + #[test] + fn test_event_error_request() { + let req = EventErrorRequest { + request_id: "id", + diagnostic: Diagnostic { + error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), + error_message: std::borrow::Cow::Borrowed("Error parsing event data"), + }, + }; + let req = req.into_req().unwrap(); + let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/error"); + assert_eq!(req.method(), Method::POST); + assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } + + #[test] + fn test_init_error_request() { + let req = InitErrorRequest; + let req = req.into_req().unwrap(); + let expected = Uri::from_static("/2018-06-01/runtime/init/error"); + assert_eq!(req.method(), Method::POST); + assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } } From cfdf6f5741ed6113a5591f3075f54637ee1d2f81 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 May 2024 18:58:57 -0700 Subject: [PATCH 302/394] Support RUST_LOG as variable to set the logging level. (#869) Improve the documentation about log levels and formats. Signed-off-by: David Calavera --- lambda-runtime-api-client/src/tracing.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 6a15f36c..51e187e4 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -15,14 +15,25 @@ pub use tracing::*; /// Re-export the `tracing-subscriber` crate to build your own subscribers. pub use tracing_subscriber as subscriber; -/// Initialize `tracing-subscriber` with default options. -/// The subscriber uses `AWS_LAMBDA_LOG_LEVEL` as the environment variable to determine the log level for your function. -/// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +const DEFAULT_LOG_LEVEL: &str = "INFO"; + +/// Initialize `tracing-subscriber` with default logging options. +/// +/// This function uses environment variables set with [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. -/// By default, the log level to emit events is `INFO`. +/// +/// This subscriber sets the logging level based on environment variables: +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes predecence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. +/// - if none of those two variables are set, use `INFO` as the logging level. +/// +/// The logging format can also be changed based on Lambda's advanced logging controls. +/// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, +/// otherwise they will be formatted with the default tracing format. pub fn init_default_subscriber() { let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); - let log_level = Level::from_str(&env::var("AWS_LAMBDA_LOG_LEVEL").unwrap_or_default()).unwrap_or(Level::INFO); + let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); + let log_level = Level::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(Level::INFO); let collector = tracing_subscriber::fmt() .with_target(false) From f9aeac7412ad15c57c29d53aa18a4cef9361daf1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 May 2024 21:15:41 -0700 Subject: [PATCH 303/394] Release patch versions. (#870) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 8f2fca99..6d496b43 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.15.0" +version = "0.15.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index f8fe06c3..8f1b1d26 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.1" +version = "0.11.2" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.1", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -53,7 +53,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index e5c920bd..57fc4bca 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.11.0" +version = "0.11.1" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index c4b8e35b..1883a18d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.1" +version = "0.11.2" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client", default-features = false } +lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From fc49dd5e0f1dd0c81bdb51c6bfcfb755583ba7ff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 May 2024 18:35:10 -0700 Subject: [PATCH 304/394] Fix Lambda Function URL responses. (#872) Add the headers field to ensure that Lambda Function URL responses are encoded correctly. Signed-off-by: David Calavera --- lambda-http/src/response.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e73a584d..c1b48ef2 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -104,9 +104,11 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers - headers: HeaderMap::new(), + // ALB responses are used for ALB integrations as well as + // Lambda Function URLs. The former uses the `multi_value_headers` field, + // while the later uses the `headers` field. We need to return + // both fields to ensure both integrations work correctly. + headers: headers.clone(), multi_value_headers: headers, status_description: Some(format!( "{} {}", From 343159e92c09fb84d47f4b2a2a106ce1d6059efd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 May 2024 09:20:06 -0700 Subject: [PATCH 305/394] Release version 0.11.3 (#875) Fixes APIGW duplicated response headers. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8f1b1d26..9bd5923e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.2" +version = "0.11.3" authors = [ "David Calavera ", "Harold Sun ", From 447d9fb0c877100109536d82c9925f5a23b1b741 Mon Sep 17 00:00:00 2001 From: fluxth Date: Fri, 17 May 2024 10:11:17 +0900 Subject: [PATCH 306/394] fix: return correct header keys for each integration (#877) Each integration handle header keys differently, this patch tries to address these differences so that we have proper headers in responses. **ALB Integration** https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers-response The names of the fields used for headers differ depending on whether you enable multi-value headers for the target group. You must use multiValueHeaders if you have enabled multi-value headers and headers otherwise. **APIGW v1 Integration** https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format If you specify values for both headers and multiValueHeaders, API Gateway merges them into a single list. If the same key-value pair is specified in both, only the values from multiValueHeaders will appear in the merged list. **APIGW v2 Integration** https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. Duplicate headers are combined with commas and included in the headers field. Duplicate query strings are combined with commas and included in the queryStringParameters field. **`awslabs/aws-lambda-go-api-proxy` source code** - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/response.go#L117 - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/responseALB.go#L108 - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/responsev2.go#L117 --- lambda-http/src/response.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index c1b48ef2..6a31cb79 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -69,8 +69,8 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers + // Explicitly empty, as API gateway v1 will merge "headers" and + // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, }), @@ -93,10 +93,10 @@ impl LambdaResponse { is_base64_encoded, status_code: status_code as i64, cookies, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers - headers: HeaderMap::new(), - multi_value_headers: headers, + // API gateway v2 doesn't have "multi_value_headers" field. Duplicate headers + // are combined with commas and included in the headers field. + headers, + multi_value_headers: HeaderMap::new(), }) } #[cfg(feature = "alb")] @@ -104,10 +104,9 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - // ALB responses are used for ALB integrations as well as - // Lambda Function URLs. The former uses the `multi_value_headers` field, - // while the later uses the `headers` field. We need to return - // both fields to ensure both integrations work correctly. + // ALB responses are used for ALB integration, which can be configured to use + // either "headers" or "multi_value_headers" field. We need to return both fields + // to ensure both configuration work correctly. headers: headers.clone(), multi_value_headers: headers, status_description: Some(format!( @@ -121,8 +120,8 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers + // Explicitly empty, as API gateway v1 will merge "headers" and + // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, }), @@ -475,7 +474,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-encoding":"gzip"},"multiValueHeaders":{},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# ) } @@ -493,7 +492,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{},"body":"000000","isBase64Encoded":false,"cookies":[]}"# ) } @@ -511,7 +510,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/json; charset=utf-16"},"multiValueHeaders":{},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -529,7 +528,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } From b60807efa6b6f5c7a3261e995342ab10dbd309ed Mon Sep 17 00:00:00 2001 From: Luke Ward Date: Fri, 24 May 2024 11:32:22 -0400 Subject: [PATCH 307/394] Update README.md (#881) updating docs to match changes in cargo lambda https://github.com/cargo-lambda/cargo-lambda/commit/3d8eff163aa6b8e7bd388ca3e67562b880334c95 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 331635d2..c379f1dc 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ fn test_my_lambda_handler() { [Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project. You can run the following subcommand to compile your function(s) and start the server. ```bash -cargo lambda watch -a 127.0.0.1 -p 9001 +cargo lambda watch ``` Now you can use the `cargo lambda invoke` to send requests to your function. For example: @@ -358,7 +358,7 @@ An simpler alternative is to cURL the following endpoint based on the address an ```bash curl -v -X POST \ - 'http://127.0.0.1:9001/lambda-url//' \ + 'http://127.0.0.1:9000/lambda-url//' \ -H 'content-type: application/json' \ -d '{ "command": "hi" }' ``` From ee78eaa28fa4ebd8a464af226dc4fe8d26099eb6 Mon Sep 17 00:00:00 2001 From: Rene B <157360283+rebu-dt@users.noreply.github.com> Date: Tue, 28 May 2024 01:16:11 +0200 Subject: [PATCH 308/394] Fix extension compatibility issues with AWS Lambda Runtime Interface Emulator (#879) * fix: consider status codes 200-299 successful in Extension API * fix: Make `tracing` JSON field optional --- lambda-extension/src/events.rs | 3 ++- lambda-extension/src/extension.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lambda-extension/src/events.rs b/lambda-extension/src/events.rs index c61fc9f0..db872d96 100644 --- a/lambda-extension/src/events.rs +++ b/lambda-extension/src/events.rs @@ -1,7 +1,7 @@ use serde::Deserialize; /// Request tracing information -#[derive(Debug, Deserialize)] +#[derive(Debug, Default, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tracing { /// The type of tracing exposed to the extension @@ -20,6 +20,7 @@ pub struct InvokeEvent { /// The function's Amazon Resource Name pub invoked_function_arn: String, /// The request tracing information + #[serde(default)] pub tracing: Tracing, } diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index cac1c7ec..e57940ae 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -272,7 +272,7 @@ where self.log_port_number, )?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to initialize the logs api: {}", res.status()); return Err(ExtensionError::boxed(err)); } @@ -318,7 +318,7 @@ where self.telemetry_port_number, )?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to initialize the telemetry api: {}", res.status()); return Err(ExtensionError::boxed(err)); } @@ -491,7 +491,7 @@ async fn register<'a>( let req = requests::register_request(&name, events)?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to register the extension: {}", res.status()); return Err(ExtensionError::boxed(err)); } From 7500b2b83528e8d323538472c6fe33d34ef99ca0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:53:22 -0700 Subject: [PATCH 309/394] Refresh readmes (#883) - Remove old tooling information that we don't test or use. - Update to recommend AL2023 - Remove any mention to AL1 - Cleanup code examples - Move lambda_http specific docs to its own readme Signed-off-by: David Calavera --- README.md | 118 +++--------------------------------------- lambda-http/README.md | 22 ++++++++ 2 files changed, 28 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index c379f1dc..32763e3e 100644 --- a/README.md +++ b/README.md @@ -87,25 +87,14 @@ By default, Cargo Lambda builds your functions to run on x86_64 architectures. I #### 1.2. Build your Lambda functions -__Amazon Linux 2__ +__Amazon Linux 2023__ -We recommend you to use Amazon Linux 2 runtimes (such as `provided.al2`) as much as possible for building Lambda functions in Rust. To build your Lambda functions for Amazon Linux 2 runtimes, run: +We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: ```bash cargo lambda build --release --arm64 ``` -__Amazon Linux 1__ - -Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with Cargo Lambda, you can specify a different version of glibc. - -If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: - -```bash -cargo lambda build --release --target aarch64-unknown-linux-gnu.2.17 -``` -> **Note** -> Replace "aarch64" with "x86_64" if you are building for x86_64 ### 2. Deploying the binary to AWS Lambda For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated executable to `bootstrap` and add it to a zip archive. @@ -117,8 +106,7 @@ You can find the `bootstrap` binary for your function under the `target/lambda` Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: ```bash -cargo lambda deploy \ - --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role +cargo lambda deploy ``` > **Warning** @@ -128,9 +116,7 @@ This command will create a Lambda function with the same name of your rust packa of the function by adding the argument at the end of the command: ```bash -cargo lambda deploy \ - --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ - my-first-lambda-function +cargo lambda deploy my-first-lambda-function ``` > **Note** @@ -162,7 +148,7 @@ You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip $ aws lambda create-function --function-name rustTest \ --handler bootstrap \ --zip-file fileb://./target/lambda/basic/bootstrap.zip \ - --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 (or to provided.al for Amazon Linux 1). + --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ --tracing-config Mode=Active @@ -229,74 +215,6 @@ $ aws lambda invoke $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} ``` -#### 2.4. Serverless Framework - -Alternatively, you can build a Rust-based Lambda function declaratively using the [Serverless framework Rust plugin](https://github.com/softprops/serverless-rust). - -A number of getting started Serverless application templates exist to get you up and running quickly: - -- a minimal [echo function](https://github.com/softprops/serverless-aws-rust) to demonstrate what the smallest Rust function setup looks like -- a minimal [http function](https://github.com/softprops/serverless-aws-rust-http) to demonstrate how to interface with API Gateway using Rust's native [http](https://crates.io/crates/http) crate (note this will be a git dependency until 0.2 is published) -- a combination [multi function service](https://github.com/softprops/serverless-aws-rust-multi) to demonstrate how to set up a services with multiple independent functions - -Assuming your host machine has a relatively recent version of node, you [won't need to install any host-wide serverless dependencies](https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner). To get started, run the following commands to create a new lambda Rust application -and install project level dependencies. - -```bash -$ npx serverless install \ - --url https://github.com/softprops/serverless-aws-rust \ - --name my-new-app \ - && cd my-new-app \ - && npm install --silent -``` - -Deploy it using the standard serverless workflow: - -```bash -# build, package, and deploy service to aws lambda -$ npx serverless deploy -``` - -Invoke it using serverless framework or a configured AWS integrated trigger source: - -```bash -npx serverless invoke -f hello -d '{"foo":"bar"}' -``` - -#### 2.5. Docker - -Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rust-serverless/lambda-rust). - -Running the following command will start an ephemeral docker container, which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`. Typically, this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`): - -```bash -# build and package deploy-ready artifact -$ docker run --rm \ - -v ${PWD}:/code \ - -v ${HOME}/.cargo/registry:/root/.cargo/registry \ - -v ${HOME}/.cargo/git:/root/.cargo/git \ - rustserverless/lambda-rust -``` - -With your application built and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: - -```bash -# start a docker container replicating the "provided" lambda runtime -# awaiting an event to be provided via stdin -$ unzip -o \ - target/lambda/release/{your-binary-name}.zip \ - -d /tmp/lambda && \ - docker run \ - -i -e DOCKER_LAMBDA_USE_STDIN=1 \ - --rm \ - -v /tmp/lambda:/var/task \ - lambci/lambda:provided - -# provide an event payload via stdin (typically a json blob) - -# Ctrl-D to yield control back to your function -``` - ## Local development and testing ### Testing your code with unit and integration tests @@ -332,7 +250,7 @@ fn test_my_lambda_handler() { } ``` -### Cargo Lambda +### Local dev server with Cargo Lambda [Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project. You can run the following subcommand to compile your function(s) and start the server. @@ -432,30 +350,6 @@ fn main() -> Result<(), Box> { } ``` -## Feature flags in lambda_http - -`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions. - -By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags. - -The available features flags for `lambda_http` are the following: - -- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). -- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). -- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). -- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). - -If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable: - -```toml -[dependencies.lambda_http] -version = "0.5.3" -default-features = false -features = ["apigw_rest"] -``` - -This will make your function compile much faster. - ## Supported Rust Versions (MSRV) The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. diff --git a/lambda-http/README.md b/lambda-http/README.md index 79c3410d..6b394964 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -235,3 +235,25 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is present, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. + +## Feature flags + +`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions. + +By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags. + +The available features flags for `lambda_http` are the following: + +- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). +- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). +- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). +- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). + +If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable: + +```toml +[dependencies.lambda_http] +version = "0.5.3" +default-features = false +features = ["apigw_rest"] +``` From 00d822eacbad4e4794576bcfb4155971215a85d6 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:54:44 -0700 Subject: [PATCH 310/394] Fix GLIBC typo (#885) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32763e3e..b4a5387f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ By default, Cargo Lambda builds your functions to run on x86_64 architectures. I __Amazon Linux 2023__ -We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: +We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIBC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: ```bash cargo lambda build --release --arm64 From 31250f3ff0ffbee83f2d907d707208e7e107dc02 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:57:07 -0700 Subject: [PATCH 311/394] Add Cognito Post Confirmation example (#884) - Show how to work with Cognito's Post Confirmation events. - Make response generic so customers can return a different object as reponse. Signed-off-by: David Calavera --- .../.gitignore | 1 + .../Cargo.toml | 24 ++++++++ .../basic-cognito-post-confirmation/README.md | 15 +++++ .../src/main.rs | 60 +++++++++++++++++++ lambda-events/src/event/cognito/mod.rs | 10 +++- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 examples/basic-cognito-post-confirmation/.gitignore create mode 100644 examples/basic-cognito-post-confirmation/Cargo.toml create mode 100644 examples/basic-cognito-post-confirmation/README.md create mode 100644 examples/basic-cognito-post-confirmation/src/main.rs diff --git a/examples/basic-cognito-post-confirmation/.gitignore b/examples/basic-cognito-post-confirmation/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/basic-cognito-post-confirmation/Cargo.toml b/examples/basic-cognito-post-confirmation/Cargo.toml new file mode 100644 index 00000000..7d2e7ab4 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "basic-cognito-post-confirmation" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws-config = "1.5.0" +aws-sdk-ses = "1.28.0" +aws_lambda_events = { path = "../../lambda-events", default-features = false, features = ["cognito"] } + +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } + diff --git a/examples/basic-cognito-post-confirmation/README.md b/examples/basic-cognito-post-confirmation/README.md new file mode 100644 index 00000000..4391e16c --- /dev/null +++ b/examples/basic-cognito-post-confirmation/README.md @@ -0,0 +1,15 @@ +# Cognito Post Confirmation Request example + +This example shows how to write a Lambda function in Rust to process Cognito's Post Confirmation requests. + +This is a translation of the example in the AWS Docs to Rust: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html#aws-lambda-triggers-post-confirmation-example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-cognito-post-confirmation/src/main.rs b/examples/basic-cognito-post-confirmation/src/main.rs new file mode 100644 index 00000000..178bd3a9 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/src/main.rs @@ -0,0 +1,60 @@ +use aws_config::BehaviorVersion; +use aws_lambda_events::event::cognito::CognitoEventUserPoolsPostConfirmation; +use aws_sdk_ses::{ + types::{Body, Content, Destination, Message}, + Client, +}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; + +const SOURCE_EMAIL: &str = ""; + +async fn function_handler( + client: &aws_sdk_ses::Client, + event: LambdaEvent, +) -> Result { + let payload = event.payload; + + if let Some(email) = payload.request.user_attributes.get("email") { + let body = if let Some(name) = payload.request.user_attributes.get("name") { + format!("Welcome {name}, you have been confirmed.") + } else { + "Welcome, you have been confirmed.".to_string() + }; + send_post_confirmation_email(client, email, "Cognito Identity Provider registration completed", &body).await?; + } + + // Cognito always expect a response with the same shape as + // the event when it handles Post Confirmation triggers. + Ok(payload) +} + +async fn send_post_confirmation_email(client: &Client, email: &str, subject: &str, body: &str) -> Result<(), Error> { + let destination = Destination::builder().to_addresses(email).build(); + let subject = Content::builder().data(subject).build()?; + let body = Content::builder().data(body).build()?; + + let message = Message::builder() + .body(Body::builder().text(body).build()) + .subject(subject) + .build(); + + client + .send_email() + .source(SOURCE_EMAIL) + .destination(destination) + .message(message) + .send() + .await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = Client::new(&config); + + run(service_fn(|event| function_handler(&client, event))).await +} diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 6eb1c001..1b31abcc 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -84,13 +84,18 @@ pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { /// allowing the Lambda to send custom messages or add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CognitoEventUserPoolsPostConfirmation { +pub struct CognitoEventUserPoolsPostConfirmation +where + T: DeserializeOwned, + T: Serialize, +{ #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostConfirmationRequest, - pub response: CognitoEventUserPoolsPostConfirmationResponse, + #[serde(bound = "")] + pub response: T, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -254,6 +259,7 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostConfirmationResponse {} + /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] From 0fcba1641aca40d1951033efcf710fc05f6f8e5b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jun 2024 10:08:23 -0700 Subject: [PATCH 312/394] Improve Kinesis event recort type (#886) Make the encryption type an enum. Make sequence number and partition key non-optional. Signed-off-by: David Calavera --- lambda-events/src/event/kinesis/event.rs | 29 +++++++++++++-- .../example-kinesis-event-encrypted.json | 37 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 lambda-events/src/fixtures/example-kinesis-event-encrypted.json diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 2b14cbed..94ebbbf9 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -62,15 +62,24 @@ pub struct KinesisEventRecord { pub struct KinesisRecord { pub approximate_arrival_timestamp: SecondTimestamp, pub data: Base64Data, - pub encryption_type: Option, #[serde(default)] - pub partition_key: Option, + pub encryption_type: KinesisEncryptionType, #[serde(default)] - pub sequence_number: Option, + pub partition_key: String, + #[serde(default)] + pub sequence_number: String, #[serde(default)] pub kinesis_schema_version: Option, } +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum KinesisEncryptionType { + #[default] + None, + Kms, +} + #[cfg(test)] mod test { use super::*; @@ -80,6 +89,20 @@ mod test { fn example_kinesis_event() { let data = include_bytes!("../../fixtures/example-kinesis-event.json"); let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(KinesisEncryptionType::None, parsed.records[0].kinesis.encryption_type); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "kinesis")] + fn example_kinesis_event_encrypted() { + let data = include_bytes!("../../fixtures/example-kinesis-event-encrypted.json"); + let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(KinesisEncryptionType::Kms, parsed.records[0].kinesis.encryption_type); + let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); diff --git a/lambda-events/src/fixtures/example-kinesis-event-encrypted.json b/lambda-events/src/fixtures/example-kinesis-event-encrypted.json new file mode 100644 index 00000000..7d1697b1 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-event-encrypted.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333333333333333333333333333333333333333333", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480641523.477, + "encryptionType": "KMS" + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333333333333333333333333333333333333333333", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + }, + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333334444444444444444444444444444444444444", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480841523.477 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333334444444444444444444444444444444444444", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + } + ] +} \ No newline at end of file From 1cf868c7a874be371d0bf5a18a30996c638942fc Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jun 2024 10:08:58 -0700 Subject: [PATCH 313/394] Add support for Lambda-Extesion-Accept-Feature header (#887) - Use this header to read the account id that the extesion is installed in. - Keep the account id as optional just in case we make this feature optional in the future. - Users should not rely on it being always present. - Extract all the information that the register call provides. --- lambda-extension/src/extension.rs | 60 ++++++++++++++++++++++++++----- lambda-extension/src/requests.rs | 6 ++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index e57940ae..3800f7ab 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -6,6 +6,7 @@ use hyper::service::service_fn; use hyper_util::rt::tokio::TokioIo; use lambda_runtime_api_client::Client; +use serde::Deserialize; use std::{ convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, }; @@ -230,8 +231,7 @@ where pub async fn register(self) -> Result, Error> { let client = &Client::builder().build()?; - let extension_id = register(client, self.extension_name, self.events).await?; - let extension_id = extension_id.to_str()?; + let register_res = register(client, self.extension_name, self.events).await?; // Logs API subscriptions must be requested during the Lambda init phase (see // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-subscribing). @@ -266,7 +266,7 @@ where // Call Logs API to start receiving events let req = requests::subscribe_request( Api::LogsApi, - extension_id, + ®ister_res.extension_id, self.log_types, self.log_buffering, self.log_port_number, @@ -312,7 +312,7 @@ where // Call Telemetry API to start receiving events let req = requests::subscribe_request( Api::TelemetryApi, - extension_id, + ®ister_res.extension_id, self.telemetry_types, self.telemetry_buffering, self.telemetry_port_number, @@ -326,7 +326,11 @@ where } Ok(RegisteredExtension { - extension_id: extension_id.to_string(), + extension_id: register_res.extension_id, + function_name: register_res.function_name, + function_version: register_res.function_version, + handler: register_res.handler, + account_id: register_res.account_id, events_processor: self.events_processor, }) } @@ -339,7 +343,17 @@ where /// An extension registered by calling [`Extension::register`]. pub struct RegisteredExtension { - extension_id: String, + /// The ID of the registered extension. This ID is unique per extension and remains constant + pub extension_id: String, + /// The ID of the account the extension was registered to. + /// This will be `None` if the register request doesn't send the Lambda-Extension-Accept-Feature header + pub account_id: Option, + /// The name of the Lambda function that the extension is registered with + pub function_name: String, + /// The version of the Lambda function that the extension is registered with + pub function_version: String, + /// The Lambda function handler that AWS Lambda invokes + pub handler: String, events_processor: E, } @@ -468,12 +482,30 @@ where } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct RegisterResponseBody { + function_name: String, + function_version: String, + handler: String, + account_id: Option, +} + +#[derive(Debug)] +struct RegisterResponse { + extension_id: String, + function_name: String, + function_version: String, + handler: String, + account_id: Option, +} + /// Initialize and register the extension in the Extensions API async fn register<'a>( client: &'a Client, extension_name: Option<&'a str>, events: Option<&'a [&'a str]>, -) -> Result { +) -> Result { let name = match extension_name { Some(name) => name.into(), None => { @@ -501,5 +533,17 @@ async fn register<'a>( .get(requests::EXTENSION_ID_HEADER) .ok_or_else(|| ExtensionError::boxed("missing extension id header")) .map_err(|e| ExtensionError::boxed(e.to_string()))?; - Ok(header.clone()) + let extension_id = header.to_str()?.to_string(); + + let (_, body) = res.into_parts(); + let body = body.collect().await?.to_bytes(); + let response: RegisterResponseBody = serde_json::from_slice(&body)?; + + Ok(RegisterResponse { + extension_id, + function_name: response.function_name, + function_version: response.function_version, + handler: response.handler, + account_id: response.account_id, + }) } diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 4d5f1527..522b8402 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -9,6 +9,11 @@ const EXTENSION_ERROR_TYPE_HEADER: &str = "Lambda-Extension-Function-Error-Type" const CONTENT_TYPE_HEADER_NAME: &str = "Content-Type"; const CONTENT_TYPE_HEADER_VALUE: &str = "application/json"; +// Comma separated list of features the extension supports. +// `accountId` is currently the only supported feature. +const EXTENSION_ACCEPT_FEATURE: &str = "Lambda-Extension-Accept-Feature"; +const EXTENSION_ACCEPT_FEATURE_VALUE: &str = "accountId"; + pub(crate) fn next_event_request(extension_id: &str) -> Result, Error> { let req = build_request() .method(Method::GET) @@ -25,6 +30,7 @@ pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result< .method(Method::POST) .uri("/2020-01-01/extension/register") .header(EXTENSION_NAME_HEADER, extension_name) + .header(EXTENSION_ACCEPT_FEATURE, EXTENSION_ACCEPT_FEATURE_VALUE) .header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE) .body(Body::from(serde_json::to_string(&events)?))?; From d03bbc4caafc3f58abe84bd0c941c3f65b91f68b Mon Sep 17 00:00:00 2001 From: stelo <42366677+jfkisafk@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:07:34 -0700 Subject: [PATCH 314/394] fix(events): makes ApproximateCreationDateTime optional in dynamo event stream record (#891) closes https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 --- lambda-events/src/event/dynamodb/mod.rs | 6 +++++- .../example-dynamodb-event-record-with-optional-fields.json | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 33b977bd..77cc77d2 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -213,9 +213,11 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX - /// epoch time (http://www.epochconverter.com/) format. + /// epoch time (http://www.epochconverter.com/) format. Might not be present in + /// the record: https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 #[serde(rename = "ApproximateCreationDateTime")] #[serde(with = "float_unix_epoch")] + #[serde(default)] pub approximate_creation_date_time: DateTime, /// The primary key attribute(s) for the DynamoDB item that was modified. #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] @@ -274,5 +276,7 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: EventRecord = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + let date = Utc.timestamp_micros(0).unwrap(); // 1970-01-01T00:00:00Z + assert_eq!(date, reparsed.change.approximate_creation_date_time); } } diff --git a/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json index 873cccce..4f468c70 100644 --- a/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json +++ b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json @@ -6,7 +6,6 @@ "recordFormat":"application/json", "tableName":"examples", "dynamodb":{ - "ApproximateCreationDateTime":1649809356015, "Keys":{ "id":{ "S":"00000000-0000-0000-0000-000000000000" From 85d1cb8b196262213f3bbc53b0e0daebf471b2bb Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 8 Jun 2024 11:36:43 -0700 Subject: [PATCH 315/394] Log function error (#892) Bring back this log that was lost between version 0.10 and 0.11. --- lambda-runtime/src/layers/panic.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 26ceeecc..6600b743 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -58,9 +58,9 @@ where let fut = AssertUnwindSafe(task).catch_unwind(); CatchPanicFuture::Future(fut, PhantomData) } - Err(err) => { - error!(error = ?err, "user handler panicked"); - CatchPanicFuture::Error(err) + Err(error) => { + error!(?error, "user handler panicked"); + CatchPanicFuture::Error(error) } } } @@ -85,15 +85,19 @@ where match self.project() { CatchPanicFutureProj::Future(fut, _) => match fut.poll(cx) { Poll::Ready(ready) => match ready { - Ok(inner_result) => Poll::Ready(inner_result.map_err(|err| err.into())), - Err(err) => { - error!(error = ?err, "user handler panicked"); - Poll::Ready(Err(Self::build_panic_diagnostic(&err))) + Ok(Ok(success)) => Poll::Ready(Ok(success)), + Ok(Err(error)) => { + error!("{error:?}"); + Poll::Ready(Err(error.into())) + } + Err(error) => { + error!(?error, "user handler panicked"); + Poll::Ready(Err(Self::build_panic_diagnostic(&error))) } }, Poll::Pending => Poll::Pending, }, - CatchPanicFutureProj::Error(err) => Poll::Ready(Err(Self::build_panic_diagnostic(err))), + CatchPanicFutureProj::Error(error) => Poll::Ready(Err(Self::build_panic_diagnostic(error))), } } } From 6382a85137b033019c78f963b77d1b75399b7f34 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 13 Jun 2024 21:05:48 -0700 Subject: [PATCH 316/394] Release new patch versions for 0.11 (#893) * Release new patch versions for 0.11 Signed-off-by: David Calavera * Fix clippy errors from Rust 1.79.0 Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime-api-client/src/body/channel.rs | 4 ++-- lambda-runtime/Cargo.toml | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9bd5923e..28b0f7e0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.3" +version = "0.11.4" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.3", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index 815de5f2..f5fcb77b 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -20,8 +20,8 @@ pub use sender::Sender; pub(crate) struct DecodedLength(u64); impl DecodedLength { - pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX); - pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1); + pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(u64::MAX); + pub(crate) const CHUNKED: DecodedLength = DecodedLength(u64::MAX - 1); pub(crate) const ZERO: DecodedLength = DecodedLength(0); pub(crate) fn sub_if(&mut self, amt: u64) { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1883a18d..652381eb 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.2" +version = "0.11.3" authors = [ "David Calavera ", "Harold Sun ", @@ -26,10 +26,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } -hyper = { workspace = true, features = [ - "http1", - "client", -] } +hyper = { workspace = true, features = ["http1", "client"] } hyper-util = { workspace = true, features = [ "client", "client-legacy", From d38b1a4d2b96de67923d35e1bb4d3293d6b270f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:14:53 -0700 Subject: [PATCH 317/394] Bump braces from 3.0.2 to 3.0.3 in /examples/http-axum/cdk (#894) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index c35c0bf4..9a7e3026 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -1778,12 +1778,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2346,9 +2346,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5739,12 +5739,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -6138,9 +6138,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From 175acb64d5fcd498785fd1798f4a103a43dcfca7 Mon Sep 17 00:00:00 2001 From: Jon Smith <63128850+jonjsmith@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:51:57 -0500 Subject: [PATCH 318/394] Update README.md (#895) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4a5387f..0a19e467 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ There are other ways of building your function: manually with the AWS CLI, with By default, Cargo Lambda builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. -#### 1.2. Build your Lambda functions +#### 1.1. Build your Lambda functions __Amazon Linux 2023__ @@ -101,7 +101,7 @@ For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-cus You can find the `bootstrap` binary for your function under the `target/lambda` directory. -#### 2.2. Deploying with Cargo Lambda +#### 2.1. Deploying with Cargo Lambda Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: From 92cdd74b2aa4b5397f7ff4f1800b54c9b949d96a Mon Sep 17 00:00:00 2001 From: Tethys Svensson Date: Wed, 19 Jun 2024 18:32:44 +0200 Subject: [PATCH 319/394] Make child-spans work as expected when using the lambda-runtime (#896) --- lambda-runtime/src/layers/trace.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 7a9f8370..35c74c12 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -43,7 +43,11 @@ where fn call(&mut self, req: LambdaInvocation) -> Self::Future { let span = request_span(&req.context); - self.inner.call(req).instrument(span) + let future = { + let _guard = span.enter(); + self.inner.call(req) + }; + future.instrument(span) } } From cc239ea4aeffc51cfe63fbbb151c33ec49734645 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 24 Jun 2024 19:57:50 -0700 Subject: [PATCH 320/394] Relax blanket implementation of Diagnostic (#897) Instead of implementing Diagnostic for everything that implements Display, implement the trait only for a few well known types. This gives people more flexibility to implement Diagnostic. Signed-off-by: David Calavera --- Cargo.toml | 6 +- .../Cargo.toml | 1 + examples/basic-error-diagnostic/.gitignore | 1 + examples/basic-error-diagnostic/Cargo.toml | 22 +++ examples/basic-error-diagnostic/README.md | 13 ++ examples/basic-error-diagnostic/src/main.rs | 37 +++++ examples/basic-error-handling/README.md | 4 +- examples/basic-error-handling/src/main.rs | 8 +- lambda-events/Cargo.toml | 6 +- lambda-http/src/lib.rs | 3 +- lambda-http/src/streaming.rs | 5 +- lambda-runtime/src/diagnostic.rs | 141 ++++++++++++++++++ lambda-runtime/src/layers/api_response.rs | 11 +- lambda-runtime/src/lib.rs | 10 +- lambda-runtime/src/requests.rs | 3 +- lambda-runtime/src/runtime.rs | 9 +- lambda-runtime/src/types.rs | 81 +--------- 17 files changed, 251 insertions(+), 110 deletions(-) create mode 100644 examples/basic-error-diagnostic/.gitignore create mode 100644 examples/basic-error-diagnostic/Cargo.toml create mode 100644 examples/basic-error-diagnostic/README.md create mode 100644 examples/basic-error-diagnostic/src/main.rs create mode 100644 lambda-runtime/src/diagnostic.rs diff --git a/Cargo.toml b/Cargo.toml index 0b01ee05..c55f0e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,11 @@ exclude = ["examples"] [workspace.dependencies] base64 = "0.22" bytes = "1" -chrono = "0.4.35" +chrono = { version = "0.4.35", default-features = false, features = [ + "clock", + "serde", + "std", +] } futures = "0.3" futures-channel = "0.3" futures-util = "0.3" diff --git a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml index 116ab8ef..bd41a01a 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "producer", diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-diagnostic/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/basic-error-diagnostic/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-diagnostic/Cargo.toml new file mode 100644 index 00000000..b81ef730 --- /dev/null +++ b/examples/basic-error-diagnostic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "basic-error-diagnostic" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] + +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +thiserror = "1.0.61" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-diagnostic/README.md new file mode 100644 index 00000000..b9bf1827 --- /dev/null +++ b/examples/basic-error-diagnostic/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function Error handling example + +This example shows how to implement the `Diagnostic` trait to return a specific `error_type` in the Lambda error response. If you don't use the `error_type` field, you don't need to implement `Diagnostic`, the type will be generated based on the error type name. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-diagnostic/src/main.rs new file mode 100644 index 00000000..11f68d4b --- /dev/null +++ b/examples/basic-error-diagnostic/src/main.rs @@ -0,0 +1,37 @@ +use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent}; +use serde::Deserialize; +use thiserror; + +#[derive(Deserialize)] +struct Request {} + +#[derive(Debug, thiserror::Error)] +pub enum ExecutionError { + #[error("transient database error: {0}")] + DatabaseError(String), + #[error("unexpected error: {0}")] + Unexpected(String), +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: ExecutionError) -> Diagnostic<'a> { + let (error_type, error_message) = match value { + ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()), + ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()), + }; + Diagnostic { + error_type: error_type.into(), + error_message: error_message.into(), + } + } +} + +/// This is the main body for the Lambda function +async fn function_handler(_event: LambdaEvent) -> Result<(), ExecutionError> { + Err(ExecutionError::Unexpected("ooops".to_string())) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_runtime::run(service_fn(function_handler)).await +} diff --git a/examples/basic-error-handling/README.md b/examples/basic-error-handling/README.md index 498f8a50..5eef4207 100644 --- a/examples/basic-error-handling/README.md +++ b/examples/basic-error-handling/README.md @@ -1,4 +1,6 @@ -# AWS Lambda Function example +# AWS Lambda Function Error handling example + +This example shows how to return a custom error type for unexpected failures. ## Build & Deploy diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 528d6f02..3bc76936 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,7 +1,7 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::json; use std::fs::File; /// A simple Lambda request structure with just one field @@ -59,11 +59,11 @@ async fn main() -> Result<(), Error> { } /// The actual handler of the Lambda request. -pub(crate) async fn func(event: LambdaEvent) -> Result { +pub(crate) async fn func(event: LambdaEvent) -> Result { let (event, ctx) = event.into_parts(); // check what action was requested - match serde_json::from_value::(event)?.event_type { + match event.event_type { EventType::SimpleError => { // generate a simple text message error using `simple_error` crate return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))); @@ -94,7 +94,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result { msg: "OK".into(), }; - return Ok(json!(resp)); + return Ok(resp); } } } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 6d496b43..d9774104 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -18,11 +18,7 @@ edition = "2021" [dependencies] base64 = { workspace = true } bytes = { workspace = true, features = ["serde"], optional = true } -chrono = { workspace = true, default-features = false, features = [ - "clock", - "serde", - "std", -], optional = true } +chrono = { workspace = true, optional = true } flate2 = { version = "1.0.24", optional = true } http = { workspace = true, optional = true } http-body = { workspace = true, optional = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 265f5ef0..90e59867 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -68,6 +68,7 @@ pub use http::{self, Response}; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[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; @@ -193,7 +194,7 @@ where S: Service, S::Future: Send + 'a, R: IntoResponse, - E: std::fmt::Debug + std::fmt::Display, + E: std::fmt::Debug + for<'b> Into>, { lambda_runtime::run(Adapter::from(handler)).await } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 217c4564..df569129 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -5,8 +5,9 @@ use crate::{request::LambdaRequest, RequestExt}; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; +use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::pin::Pin; use std::task::{Context, Poll}; use tokio_stream::Stream; @@ -20,7 +21,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), where S: Service, Error = E>, S::Future: Send + 'a, - E: Debug + Display, + E: Debug + for<'b> Into>, B: Body + Unpin + Send + 'static, B::Data: Into + Send, B::Error: Into + Send + Debug, diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs new file mode 100644 index 00000000..bc9ba623 --- /dev/null +++ b/lambda-runtime/src/diagnostic.rs @@ -0,0 +1,141 @@ +use serde::{Deserialize, Serialize}; +use std::{any::type_name, borrow::Cow}; + +use crate::{deserializer::DeserializeError, Error}; + +/// Diagnostic information about an error. +/// +/// `Diagnostic` is automatically derived for some common types, +/// like boxed types that implement [`Error`][std::error::Error]. +/// +/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of +/// the original error with [`std::any::type_name`] as a fallback, which may +/// not be reliable for conditional error handling. +/// You can define your own error container that implements `Into` +/// if you need to handle errors based on error types. +/// +/// Example: +/// ``` +/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; +/// use std::borrow::Cow; +/// +/// #[derive(Debug)] +/// struct ErrorResponse(Error); +/// +/// impl<'a> Into> for ErrorResponse { +/// fn into(self) -> Diagnostic<'a> { +/// Diagnostic { +/// error_type: "MyError".into(), +/// error_message: self.0.to_string().into(), +/// } +/// } +/// } +/// +/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { +/// // ... do something +/// Ok(()) +/// } +/// ``` +#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Diagnostic<'a> { + /// Error type. + /// + /// `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`] as a fallback. + /// Please implement your own `Into` if you need more reliable + /// error types. + pub error_type: Cow<'a, str>, + /// Error message. + /// + /// `error_message` is the output from the [`Display`][std::fmt::Display] + /// implementation of the original error as a fallback. + pub error_message: Cow<'a, str>, +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: DeserializeError) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: Error) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a, T> From> for Diagnostic<'a> +where + T: std::error::Error, +{ + fn from(value: Box) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From> for Diagnostic<'a> { + fn from(value: Box) -> Self { + Diagnostic { + error_type: type_name::>().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: std::convert::Infallible) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: String) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.into(), + } + } +} + +impl<'a> From<&'static str> for Diagnostic<'a> { + fn from(value: &'static str) -> Self { + Diagnostic { + error_type: type_name::<&'static str>().into(), + error_message: value.into(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn round_trip_lambda_error() { + use serde_json::{json, Value}; + let expected = json!({ + "errorType": "InvalidEventDataError", + "errorMessage": "Error parsing event data.", + }); + + let actual = Diagnostic { + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data.".into(), + }; + let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); + assert_eq!(expected, actual); + } +} diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 266402cf..55942d0b 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -1,8 +1,9 @@ -use crate::requests::{EventCompletionRequest, IntoRequest}; -use crate::runtime::LambdaInvocation; -use crate::types::Diagnostic; -use crate::{deserializer, IntoFunctionResponse}; -use crate::{EventErrorRequest, LambdaEvent}; +use crate::{ + deserializer, + requests::{EventCompletionRequest, IntoRequest}, + runtime::LambdaInvocation, + Diagnostic, EventErrorRequest, IntoFunctionResponse, LambdaEvent, +}; use futures::ready; use futures::Stream; use lambda_runtime_api_client::{body::Body, BoxError}; diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 9638df64..c3e68cc4 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -18,8 +18,12 @@ use tokio_stream::Stream; use tower::util::ServiceFn; pub use tower::{self, service_fn, Service}; +/// Diagnostic utilities to convert Rust types into Lambda Error types. +pub mod diagnostic; +pub use diagnostic::Diagnostic; + mod deserializer; -/// Tower middleware to be applied to runtime invocatinos. +/// Tower middleware to be applied to runtime invocations. pub mod layers; mod requests; mod runtime; @@ -35,9 +39,7 @@ mod types; use requests::EventErrorRequest; pub use runtime::{LambdaInvocation, Runtime}; -pub use types::{ - Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, -}; +pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::BoxError; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index ec893710..de0835c7 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,5 +1,4 @@ -use crate::types::ToStreamErrorTrailer; -use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; +use crate::{types::ToStreamErrorTrailer, Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; use http::{Method, Request, Uri}; diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 5ded610c..92ba5f47 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -1,7 +1,7 @@ -use super::requests::{IntoRequest, NextEventRequest}; -use super::types::{invoke_request_id, Diagnostic, IntoFunctionResponse, LambdaEvent}; use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; -use crate::{Config, Context}; +use crate::requests::{IntoRequest, NextEventRequest}; +use crate::types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}; +use crate::{Config, Context, Diagnostic}; use http_body_util::BodyExt; use lambda_runtime_api_client::BoxError; use lambda_runtime_api_client::Client as ApiClient; @@ -252,8 +252,7 @@ mod endpoint_tests { use super::{incoming, wrap_handler}; use crate::{ requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, - types::Diagnostic, - Config, Error, Runtime, + Config, Diagnostic, Error, Runtime, }; use futures::future::BoxFuture; use http::{HeaderValue, StatusCode}; diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index b4f10f71..ee09978f 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -5,75 +5,12 @@ use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ - borrow::Cow, collections::HashMap, - fmt::{Debug, Display}, + fmt::Debug, time::{Duration, SystemTime}, }; use tokio_stream::Stream; -/// Diagnostic information about an error. -/// -/// `Diagnostic` is automatically derived for types that implement -/// [`Display`][std::fmt::Display]; e.g., [`Error`][std::error::Error]. -/// -/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of -/// the original error with [`std::any::type_name`] as a fallback, which may -/// not be reliable for conditional error handling. -/// You can define your own error container that implements `Into` -/// if you need to handle errors based on error types. -/// -/// Example: -/// ``` -/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; -/// use std::borrow::Cow; -/// -/// #[derive(Debug)] -/// struct ErrorResponse(Error); -/// -/// impl<'a> Into> for ErrorResponse { -/// fn into(self) -> Diagnostic<'a> { -/// Diagnostic { -/// error_type: Cow::Borrowed("MyError"), -/// error_message: Cow::Owned(self.0.to_string()), -/// } -/// } -/// } -/// -/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { -/// // ... do something -/// Ok(()) -/// } -/// ``` -#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Diagnostic<'a> { - /// Error type. - /// - /// `error_type` is derived from the type name of the original error with - /// [`std::any::type_name`] as a fallback. - /// Please implement your own `Into` if you need more reliable - /// error types. - pub error_type: Cow<'a, str>, - /// Error message. - /// - /// `error_message` is the output from the [`Display`][std::fmt::Display] - /// implementation of the original error as a fallback. - pub error_message: Cow<'a, str>, -} - -impl<'a, T> From for Diagnostic<'a> -where - T: Display, -{ - fn from(value: T) -> Self { - Diagnostic { - error_type: Cow::Borrowed(std::any::type_name::()), - error_message: Cow::Owned(format!("{value}")), - } - } -} - /// Client context sent by the AWS Mobile SDK. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ClientContext { @@ -342,22 +279,6 @@ mod test { use crate::Config; use std::sync::Arc; - #[test] - fn round_trip_lambda_error() { - use serde_json::{json, Value}; - let expected = json!({ - "errorType": "InvalidEventDataError", - "errorMessage": "Error parsing event data.", - }); - - let actual = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data."), - }; - let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); - assert_eq!(expected, actual); - } - #[test] fn context_with_expected_values_and_types_resolves() { let config = Arc::new(Config::default()); From f484569adcea8701a7f7e358ce1fc08b3b67059e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:07:46 -0700 Subject: [PATCH 321/394] Add OpenTelemetry layer as optional (#898) Signed-off-by: David Calavera --- examples/opentelemetry-tracing/Cargo.toml | 36 +++++-------------- examples/opentelemetry-tracing/src/main.rs | 5 ++- lambda-runtime/Cargo.toml | 2 ++ lambda-runtime/src/layers/mod.rs | 5 +++ .../src/layers/otel.rs | 12 +++++-- lambda-runtime/src/layers/trace.rs | 2 ++ 6 files changed, 29 insertions(+), 33 deletions(-) rename examples/opentelemetry-tracing/src/lib.rs => lambda-runtime/src/layers/otel.rs (88%) diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index 27a778b5..c80b997d 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -4,33 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -# Library dependencies -lambda_runtime = { path = "../../lambda-runtime" } -pin-project = "1" +lambda_runtime = { path = "../../lambda-runtime", features = ["opentelemetry"] } opentelemetry-semantic-conventions = "0.14" +opentelemetry = "0.22" +opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.3", features = ["trace"] } +pin-project = "1" +serde_json = "1.0" +tokio = "1" tower = "0.4" tracing = "0.1" - -# Binary dependencies -opentelemetry = { version = "0.22", optional = true } -opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"], optional = true } -opentelemetry-stdout = { version = "0.3", features = ["trace"], optional = true } -serde_json = { version = "1.0", optional = true } -tokio = { version = "1", optional = true } -tracing-opentelemetry = { version = "0.23", optional = true } -tracing-subscriber = { version = "0.3", optional = true } - -[features] -build-binary = [ - "opentelemetry", - "opentelemetry_sdk", - "opentelemetry-stdout", - "serde_json", - "tokio", - "tracing-opentelemetry", - "tracing-subscriber", -] - -[[bin]] -name = "opentelemetry-tracing" -required-features = ["build-binary"] +tracing-opentelemetry = "0.23" +tracing-subscriber = "0.3" diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 68038366..4020d87c 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,7 +1,6 @@ -use lambda_runtime::{LambdaEvent, Runtime}; +use lambda_runtime::{layers::OpenTelemetryLayer as OtelLayer, LambdaEvent, Runtime}; use opentelemetry::trace::TracerProvider; use opentelemetry_sdk::{runtime, trace}; -use opentelemetry_tracing::OpenTelemetryLayer; use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; @@ -25,7 +24,7 @@ async fn main() -> Result<(), BoxError> { .init(); // Initialize the Lambda runtime and add OpenTelemetry tracing - let runtime = Runtime::new(service_fn(echo)).layer(OpenTelemetryLayer::new(|| { + let runtime = Runtime::new(service_fn(echo)).layer(OtelLayer::new(|| { // Make sure that the trace is exported before the Lambda runtime is frozen tracer_provider.force_flush(); })); diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 652381eb..58ecc6b6 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -16,6 +16,7 @@ readme = "../README.md" [features] default = ["tracing"] tracing = ["lambda_runtime_api_client/tracing"] +opentelemetry = ["opentelemetry-semantic-conventions"] [dependencies] async-stream = "0.3" @@ -34,6 +35,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +opentelemetry-semantic-conventions = { version = "0.14", optional = true } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 27ce0d68..55fdccd3 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -10,3 +10,8 @@ pub(crate) use api_client::RuntimeApiClientService; pub(crate) use api_response::RuntimeApiResponseService; pub(crate) use panic::CatchPanicService; pub use trace::TracingLayer; + +#[cfg(feature = "opentelemetry")] +mod otel; +#[cfg(feature = "opentelemetry")] +pub use otel::OpenTelemetryLayer; diff --git a/examples/opentelemetry-tracing/src/lib.rs b/lambda-runtime/src/layers/otel.rs similarity index 88% rename from examples/opentelemetry-tracing/src/lib.rs rename to lambda-runtime/src/layers/otel.rs index 82f12a16..1a483a39 100644 --- a/examples/opentelemetry-tracing/src/lib.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::pin::Pin; use std::task; -use lambda_runtime::LambdaInvocation; +use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; @@ -19,6 +19,7 @@ impl OpenTelemetryLayer where F: Fn() + Clone, { + /// Create a new [OpenTelemetryLayer] with the provided flush function. pub fn new(flush_fn: F) -> Self { Self { flush_fn } } @@ -71,9 +72,14 @@ where // After the first execution, we can set 'coldstart' to false self.coldstart = false; - let fut = self.inner.call(req).instrument(span); + let future = { + // Enter the span before calling the inner service + // to ensure that it's assigned as parent of the inner spans. + let _guard = span.enter(); + self.inner.call(req) + }; OpenTelemetryFuture { - future: Some(fut), + future: Some(future.instrument(span)), flush_fn: self.flush_fn.clone(), } } diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 35c74c12..e93927b1 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -44,6 +44,8 @@ where fn call(&mut self, req: LambdaInvocation) -> Self::Future { let span = request_span(&req.context); let future = { + // Enter the span before calling the inner service + // to ensure that it's assigned as parent of the inner spans. let _guard = span.enter(); self.inner.call(req) }; From 3a049fe60222371dea2cef0519216319f6da8848 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:42:26 -0700 Subject: [PATCH 322/394] Organize imports (#899) Group imports by crate. Add instructions to the PR template about clippy and fmt. Remove PR comments because they don't work on forks. Signed-off-by: David Calavera --- .github/PULL_REQUEST_TEMPLATE.md | 10 ++-- .github/workflows/format.yml | 50 +++++-------------- .rustfmt.toml | 14 ++++-- .../src/custom_serde/codebuild_time.rs | 2 +- .../src/custom_serde/float_unix_epoch.rs | 3 +- lambda-events/src/custom_serde/headers.rs | 9 ++-- lambda-events/src/custom_serde/http_method.rs | 6 ++- lambda-events/src/custom_serde/mod.rs | 6 ++- lambda-events/src/encodings/http.rs | 6 ++- lambda-events/src/encodings/mod.rs | 2 +- lambda-events/src/encodings/time.rs | 2 +- lambda-events/src/event/alb/mod.rs | 8 +-- lambda-events/src/event/apigw/mod.rs | 12 +++-- lambda-events/src/event/appsync/mod.rs | 3 +- lambda-events/src/event/autoscaling/mod.rs | 3 +- lambda-events/src/event/cloudformation/mod.rs | 3 +- .../src/event/cloudformation/provider.rs | 3 +- .../src/event/cloudwatch_events/mod.rs | 3 +- lambda-events/src/event/codebuild/mod.rs | 9 ++-- lambda-events/src/event/cognito/mod.rs | 3 +- lambda-events/src/event/dynamodb/mod.rs | 8 +-- lambda-events/src/event/eventbridge/mod.rs | 3 +- lambda-events/src/event/iot/mod.rs | 4 +- lambda-events/src/event/kinesis/event.rs | 6 ++- lambda-events/src/event/rabbitmq/mod.rs | 3 +- lambda-events/src/event/s3/object_lambda.rs | 3 +- lambda-events/src/event/sns/mod.rs | 3 +- lambda-events/src/event/sqs/mod.rs | 6 +-- lambda-extension/src/extension.rs | 12 +++-- lambda-http/src/request.rs | 7 +-- lambda-http/src/response.rs | 16 +++--- lambda-http/src/streaming.rs | 13 +++-- lambda-runtime-api-client/src/body/channel.rs | 14 +++--- lambda-runtime-api-client/src/body/mod.rs | 6 ++- lambda-runtime-api-client/src/body/watch.rs | 10 ++-- lambda-runtime-api-client/src/lib.rs | 5 +- lambda-runtime/src/layers/api_client.rs | 5 +- lambda-runtime/src/layers/api_response.rs | 9 +--- lambda-runtime/src/layers/otel.rs | 7 +-- lambda-runtime/src/layers/panic.rs | 11 ++-- lambda-runtime/src/requests.rs | 7 +-- lambda-runtime/src/runtime.rs | 21 ++++---- 42 files changed, 155 insertions(+), 181 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31b151e5..f3010c68 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,10 @@ -*Issue #, if available:* +📬 *Issue #, if available:* -*Description of changes:* +✍️ *Description of changes:* -By submitting this pull request +🔏 *By submitting this pull request* -- [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. +- [ ] I confirm that I've ran `cargo +nightly fmt`. +- [ ] I confirm that I've ran `cargo clippy --fix`. - [ ] I confirm that I've made a best effort attempt to update all relevant documentation. +- [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index f18d1f83..10f8c75f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -3,53 +3,29 @@ name: Formatting and Linting on: [push, pull_request] jobs: - check: + fmt: + name: Cargo fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt - uses: Swatinem/rust-cache@v2 - - name: Run fmt check id: cargoFmt shell: bash - run: cargo fmt --all -- --check - - name: Notify fmt check - if: failure() && steps.cargoFmt.outcome == 'failure' - uses: actions/github-script@v6 - with: - script: | - const message = `👋 It looks like your code is not formatted like we expect. - - Please run \`cargo fmt\` and push the code again.`; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }); - core.setFailed('It looks like there are formatting errors'); - + run: cargo +nightly fmt --all -- --check + clippy: + name: Cargo clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - name: Run clippy check id: cargoClippy shell: bash run: cargo clippy --workspace --all-features -- -D warnings - - name: Notify fmt check - if: failure() && steps.cargoClippy.outcome == 'failure' - uses: actions/github-script@v6 - with: - script: | - const message = `👋 It looks like your code has some linting issues. - - Please run \`cargo clippy --fix\` and push the code again.`; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }); - core.setFailed('It looks like there are linting errors'); \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml index d8e9ca33..d2f48233 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,6 +1,14 @@ edition = "2021" -# imports_granularity is unstable -# # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports -# imports_granularity = "Crate" + # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width max_width = 120 + +#https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#reorder_imports +reorder_imports = true + +#https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#unstable_features +unstable_features = true + +# imports_granularity is unstable +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports +imports_granularity = "Crate" diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index bd132b23..07bd0a5c 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, NaiveDateTime, Utc}; -use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError, Visitor}, + ser::Serializer, Deserialize, }; use std::fmt; diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 82fd51df..437e2b1c 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -1,8 +1,7 @@ use serde::{de, ser}; use std::fmt; -use chrono::offset::TimeZone; -use chrono::{DateTime, LocalResult, Utc}; +use chrono::{offset::TimeZone, DateTime, LocalResult, Utc}; enum SerdeError { NonExistent { timestamp: V }, diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 2ef3050e..44884649 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -1,7 +1,8 @@ -use http::header::HeaderName; -use http::{HeaderMap, HeaderValue}; -use serde::de::{self, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor}; -use serde::ser::{Error as SerError, SerializeMap, Serializer}; +use http::{header::HeaderName, HeaderMap, HeaderValue}; +use serde::{ + de::{self, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor}, + ser::{Error as SerError, SerializeMap, Serializer}, +}; use std::{borrow::Cow, fmt}; /// Serialize a http::HeaderMap into a serde str => Vec map diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 63a98eb4..42a94dd7 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -1,6 +1,8 @@ use http::Method; -use serde::de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}; -use serde::ser::Serializer; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}, + ser::Serializer, +}; use std::fmt; pub fn serialize(method: &Method, ser: S) -> Result { diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 030cb5b3..729dee3d 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -1,6 +1,8 @@ use base64::Engine; -use serde::de::{Deserialize, Deserializer, Error as DeError}; -use serde::ser::Serializer; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError}, + ser::Serializer, +}; use std::collections::HashMap; #[cfg(feature = "codebuild")] diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index e013a698..56cce76a 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -1,8 +1,10 @@ use base64::display::Base64Display; use bytes::Bytes; use http_body::{Body as HttpBody, SizeHint}; -use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; -use serde::ser::{Error as SerError, Serialize, Serializer}; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError, Visitor}, + ser::{Error as SerError, Serialize, Serializer}, +}; use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// Representation of http request and response bodies as supported diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index ccc92684..23399664 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{ops::Deref, ops::DerefMut}; +use std::ops::{Deref, DerefMut}; #[cfg(feature = "chrono")] mod time; diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 6d77b5cf..d4903360 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, TimeDelta, TimeZone, Utc}; -use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError}, + ser::Serializer, Deserialize, Serialize, }; use std::ops::{Deref, DerefMut}; diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index a3f96d88..3b4ce9d5 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::{ - deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, +use crate::{ + custom_serde::{ + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, + }, + encodings::Body, }; -use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{Deserialize, Serialize}; diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index d8310755..e0aa1e8c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,9 +1,11 @@ -use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, - serialize_multi_value_headers, +use crate::{ + custom_serde::{ + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, + }, + encodings::Body, + iam::IamPolicyStatement, }; -use crate::encodings::Body; -use crate::iam::IamPolicyStatement; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 4035f181..63f9ac74 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 707b828a..601e8774 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 8dbf8b5e..44156b97 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -102,8 +102,7 @@ pub enum CloudFormationCustomResourceResponseStatus { mod test { use std::collections::HashMap; - use super::CloudFormationCustomResourceRequest::*; - use super::*; + use super::{CloudFormationCustomResourceRequest::*, *}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index e3ba5bea..a1594eb4 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -90,8 +90,7 @@ where mod test { use std::collections::HashMap; - use super::CloudFormationCustomResourceRequest::*; - use super::*; + use super::{CloudFormationCustomResourceRequest::*, *}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 425de865..6384406e 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; pub mod cloudtrail; diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index fdabcb6f..d4970f5a 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -1,8 +1,9 @@ -use crate::custom_serde::{codebuild_time, CodeBuildNumber}; -use crate::encodings::{MinuteDuration, SecondDuration}; +use crate::{ + custom_serde::{codebuild_time, CodeBuildNumber}, + encodings::{MinuteDuration, SecondDuration}, +}; use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; pub type CodeBuildPhaseStatus = String; diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 1b31abcc..a0ebd8d9 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 77cc77d2..2a3d7558 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::deserialize_lambda_dynamodb_item; -use crate::time_window::*; -use crate::{custom_serde::float_unix_epoch, streams::DynamoDbBatchItemFailure}; +use crate::{ + custom_serde::{deserialize_lambda_dynamodb_item, float_unix_epoch}, + streams::DynamoDbBatchItemFailure, + time_window::*, +}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 990a853d..7756e0e4 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; /// Parse EventBridge events. diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index cf0ca246..3835b515 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,6 +1,4 @@ -use crate::custom_serde::serialize_headers; -use crate::encodings::Base64Data; -use crate::iam::IamPolicyDocument; +use crate::{custom_serde::serialize_headers, encodings::Base64Data, iam::IamPolicyDocument}; use http::HeaderMap; use serde::{Deserialize, Serialize}; diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 94ebbbf9..fac80e07 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -1,5 +1,7 @@ -use crate::encodings::{Base64Data, SecondTimestamp}; -use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; +use crate::{ + encodings::{Base64Data, SecondTimestamp}, + time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}, +}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 47f3c004..23327276 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index fe52b8a6..2abcc797 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -1,6 +1,5 @@ use http::HeaderMap; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index d72b926a..0fda569d 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 21359f3a..9dd69f66 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,7 +1,5 @@ -use crate::custom_serde::deserialize_lambda_map; -use crate::encodings::Base64Data; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use crate::{custom_serde::deserialize_lambda_map, encodings::Base64Data}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 3800f7ab..d9717243 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,14 +1,18 @@ use http::Request; use http_body_util::BodyExt; -use hyper::body::Incoming; -use hyper::server::conn::http1; -use hyper::service::service_fn; +use hyper::{body::Incoming, server::conn::http1, service::service_fn}; use hyper_util::rt::tokio::TokioIo; use lambda_runtime_api_client::Client; use serde::Deserialize; use std::{ - convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, + convert::Infallible, + fmt, + future::{ready, Future}, + net::SocketAddr, + path::PathBuf, + pin::Pin, + sync::Arc, }; use tokio::{net::TcpListener, sync::Mutex}; use tokio_stream::StreamExt; diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9476da3b..afe233eb 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -26,15 +26,12 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC #[cfg(feature = "apigw_websockets")] use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; use aws_lambda_events::{encodings::Body, query_map::QueryMap}; -use http::header::HeaderName; -use http::{HeaderMap, HeaderValue}; +use http::{header::HeaderName, HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; use serde_json::error::Error as JsonError; -use std::future::Future; -use std::pin::Pin; -use std::{env, io::Read}; +use std::{env, future::Future, io::Read, pin::Pin}; use url::Url; /// Internal representation of an Lambda http event from diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 6a31cb79..e8528fdf 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -9,16 +9,20 @@ use aws_lambda_events::apigw::ApiGatewayProxyResponse; use aws_lambda_events::apigw::ApiGatewayV2httpResponse; use aws_lambda_events::encodings::Body; use encoding_rs::Encoding; -use http::header::CONTENT_ENCODING; -use http::HeaderMap; -use http::{header::CONTENT_TYPE, Response, StatusCode}; +use http::{ + header::{CONTENT_ENCODING, CONTENT_TYPE}, + HeaderMap, Response, StatusCode, +}; use http_body::Body as HttpBody; use http_body_util::BodyExt; use mime::{Mime, CHARSET}; use serde::Serialize; -use std::borrow::Cow; -use std::future::ready; -use std::{fmt, future::Future, pin::Pin}; +use std::{ + borrow::Cow, + fmt, + future::{ready, Future}, + pin::Pin, +}; const X_LAMBDA_HTTP_CONTENT_ENCODING: &str = "x-lambda-http-content-encoding"; diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index df569129..ad3471d3 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,15 +1,14 @@ -use crate::http::header::SET_COOKIE; -use crate::tower::ServiceBuilder; -use crate::Request; -use crate::{request::LambdaRequest, RequestExt}; +use crate::{http::header::SET_COOKIE, request::LambdaRequest, tower::ServiceBuilder, Request, RequestExt}; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::fmt::Debug; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + fmt::Debug, + pin::Pin, + task::{Context, Poll}, +}; use tokio_stream::Stream; /// Starts the Lambda Rust runtime and stream response back [Configure Lambda diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index f5fcb77b..27574655 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -1,19 +1,17 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. //! https://github.com/hyperium/hyper/blob/master/LICENSE -use std::pin::Pin; -use std::task::Context; -use std::task::Poll; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use crate::body::{sender, watch}; use bytes::Bytes; -use futures_channel::mpsc; -use futures_channel::oneshot; +use futures_channel::{mpsc, oneshot}; use futures_util::{stream::FusedStream, Future, Stream}; use http::HeaderMap; -use http_body::Body; -use http_body::Frame; -use http_body::SizeHint; +use http_body::{Body, Frame, SizeHint}; pub use sender::Sender; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs index 7e2d597c..46735682 100644 --- a/lambda-runtime-api-client/src/body/mod.rs +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -6,8 +6,10 @@ use bytes::Bytes; use futures_util::stream::Stream; use http_body::{Body as _, Frame}; use http_body_util::{BodyExt, Collected}; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use self::channel::Sender; diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs index ac0bd4ee..a5f8ae41 100644 --- a/lambda-runtime-api-client/src/body/watch.rs +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -8,11 +8,13 @@ //! - The value `0` is reserved for closed. use futures_util::task::AtomicWaker; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + task, }; -use std::task; type Value = usize; diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 4315dfd7..00c00e4c 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -5,7 +5,10 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. use futures_util::{future::BoxFuture, FutureExt, TryFutureExt}; -use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; +use http::{ + uri::{PathAndQuery, Scheme}, + Request, Response, Uri, +}; use hyper::body::Incoming; use hyper_util::client::legacy::connect::HttpConnector; use std::{convert::TryInto, fmt::Debug, future}; diff --git a/lambda-runtime/src/layers/api_client.rs b/lambda-runtime/src/layers/api_client.rs index b6d9acf8..d44a84f2 100644 --- a/lambda-runtime/src/layers/api_client.rs +++ b/lambda-runtime/src/layers/api_client.rs @@ -3,10 +3,7 @@ use futures::{future::BoxFuture, ready, FutureExt, TryFutureExt}; use hyper::body::Incoming; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use pin_project::pin_project; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task; +use std::{future::Future, pin::Pin, sync::Arc, task}; use tower::Service; use tracing::error; diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 55942d0b..5994012c 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -4,16 +4,11 @@ use crate::{ runtime::LambdaInvocation, Diagnostic, EventErrorRequest, IntoFunctionResponse, LambdaEvent, }; -use futures::ready; -use futures::Stream; +use futures::{ready, Stream}; use lambda_runtime_api_client::{body::Body, BoxError}; use pin_project::pin_project; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task; +use std::{fmt::Debug, future::Future, marker::PhantomData, pin::Pin, task}; use tower::Service; use tracing::{error, trace}; diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index 1a483a39..bcba399a 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,13 +1,10 @@ -use std::future::Future; -use std::pin::Pin; -use std::task; +use std::{future::Future, pin::Pin, task}; use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; -use tracing::instrument::Instrumented; -use tracing::Instrument; +use tracing::{instrument::Instrumented, Instrument}; /// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts /// a function to flush OpenTelemetry after the end of the invocation. diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 6600b743..036b747b 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -1,14 +1,9 @@ use crate::{Diagnostic, LambdaEvent}; use futures::{future::CatchUnwind, FutureExt}; use pin_project::pin_project; -use std::any::Any; -use std::borrow::Cow; -use std::fmt::Debug; -use std::future::Future; -use std::marker::PhantomData; -use std::panic::AssertUnwindSafe; -use std::pin::Pin; -use std::task; +use std::{ + any::Any, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task, +}; use tower::Service; use tracing::error; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index de0835c7..0e2eb59a 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,12 +1,9 @@ use crate::{types::ToStreamErrorTrailer, Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; -use http::header::CONTENT_TYPE; -use http::{Method, Request, Uri}; +use http::{header::CONTENT_TYPE, Method, Request, Uri}; use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::str::FromStr; +use std::{fmt::Debug, marker::PhantomData, str::FromStr}; use tokio_stream::{Stream, StreamExt}; pub(crate) trait IntoRequest { diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 92ba5f47..56c8efad 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -1,18 +1,15 @@ -use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; -use crate::requests::{IntoRequest, NextEventRequest}; -use crate::types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}; -use crate::{Config, Context, Diagnostic}; +use crate::{ + layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}, + requests::{IntoRequest, NextEventRequest}, + types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}, + Config, Context, Diagnostic, +}; use http_body_util::BodyExt; -use lambda_runtime_api_client::BoxError; -use lambda_runtime_api_client::Client as ApiClient; +use lambda_runtime_api_client::{BoxError, Client as ApiClient}; use serde::{Deserialize, Serialize}; -use std::env; -use std::fmt::Debug; -use std::future::Future; -use std::sync::Arc; +use std::{env, fmt::Debug, future::Future, sync::Arc}; use tokio_stream::{Stream, StreamExt}; -use tower::Layer; -use tower::{Service, ServiceExt}; +use tower::{Layer, Service, ServiceExt}; use tracing::trace; /* ----------------------------------------- INVOCATION ---------------------------------------- */ From f8cc32de3166f277375dbf1203aa3936509c1cf0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:42:41 -0700 Subject: [PATCH 323/394] Release runtime 0.12 and extension 0.11 (#900) --- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 706dd4db..c1afd732 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 28b0f7e0..1165084b 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.4" +version = "0.12.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.3", path = "../lambda-runtime" } +lambda_runtime = { version = "0.12.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 58ecc6b6..9e56d05b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.3" +version = "0.12.0" authors = [ "David Calavera ", "Harold Sun ", From 9b88cea689bc30e829994e16db4c5740d58f20e1 Mon Sep 17 00:00:00 2001 From: Oliver THEBAULT Date: Sun, 30 Jun 2024 20:13:06 +0200 Subject: [PATCH 324/394] feat(otel): allow to configure the faas.trigger attribute of the span (#903) * feat(otel): allow to configure the faas.trigger attribute of the span * fix: update opentelemetry-tracing example * fix: use datasource as default faas.trigger + introduce enum OpenTelemetryFaasTrigger --- examples/opentelemetry-tracing/src/main.rs | 18 +++++--- lambda-runtime/src/layers/mod.rs | 2 +- lambda-runtime/src/layers/otel.rs | 50 ++++++++++++++++++++-- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 4020d87c..85c3791c 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,4 +1,7 @@ -use lambda_runtime::{layers::OpenTelemetryLayer as OtelLayer, LambdaEvent, Runtime}; +use lambda_runtime::{ + layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + LambdaEvent, Runtime, +}; use opentelemetry::trace::TracerProvider; use opentelemetry_sdk::{runtime, trace}; use tower::{service_fn, BoxError}; @@ -24,10 +27,15 @@ async fn main() -> Result<(), BoxError> { .init(); // Initialize the Lambda runtime and add OpenTelemetry tracing - let runtime = Runtime::new(service_fn(echo)).layer(OtelLayer::new(|| { - // Make sure that the trace is exported before the Lambda runtime is frozen - tracer_provider.force_flush(); - })); + let runtime = Runtime::new(service_fn(echo)).layer( + // Create a tracing span for each Lambda invocation + OtelLayer::new(|| { + // Make sure that the trace is exported before the Lambda runtime is frozen + tracer_provider.force_flush(); + }) + // Set the "faas.trigger" attribute of the span to "pubsub" + .with_trigger(OpenTelemetryFaasTrigger::PubSub), + ); runtime.run().await?; Ok(()) } diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 55fdccd3..1f07f199 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -14,4 +14,4 @@ pub use trace::TracingLayer; #[cfg(feature = "opentelemetry")] mod otel; #[cfg(feature = "opentelemetry")] -pub use otel::OpenTelemetryLayer; +pub use otel::{OpenTelemetryFaasTrigger, OpenTelemetryLayer}; diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index bcba399a..e6b7cfff 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,4 +1,4 @@ -use std::{future::Future, pin::Pin, task}; +use std::{fmt::Display, future::Future, pin::Pin, task}; use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; @@ -10,6 +10,7 @@ use tracing::{instrument::Instrumented, Instrument}; /// a function to flush OpenTelemetry after the end of the invocation. pub struct OpenTelemetryLayer { flush_fn: F, + otel_attribute_trigger: OpenTelemetryFaasTrigger, } impl OpenTelemetryLayer @@ -18,7 +19,18 @@ where { /// Create a new [OpenTelemetryLayer] with the provided flush function. pub fn new(flush_fn: F) -> Self { - Self { flush_fn } + Self { + flush_fn, + otel_attribute_trigger: Default::default(), + } + } + + /// Configure the `faas.trigger` attribute of the OpenTelemetry span. + pub fn with_trigger(self, trigger: OpenTelemetryFaasTrigger) -> Self { + Self { + otel_attribute_trigger: trigger, + ..self + } } } @@ -33,6 +45,7 @@ where inner, flush_fn: self.flush_fn.clone(), coldstart: true, + otel_attribute_trigger: self.otel_attribute_trigger.to_string(), } } } @@ -42,6 +55,7 @@ pub struct OpenTelemetryService { inner: S, flush_fn: F, coldstart: bool, + otel_attribute_trigger: String, } impl Service for OpenTelemetryService @@ -61,7 +75,7 @@ where let span = tracing::info_span!( "Lambda function invocation", "otel.name" = req.context.env_config.function_name, - { traceconv::FAAS_TRIGGER } = "http", + { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, { traceconv::FAAS_COLDSTART } = self.coldstart ); @@ -114,3 +128,33 @@ where task::Poll::Ready(ready) } } + +/// Represent the possible values for the OpenTelemetry `faas.trigger` attribute. +/// See https://opentelemetry.io/docs/specs/semconv/attributes-registry/faas/ for more details. +#[derive(Default, Clone, Copy)] +#[non_exhaustive] +pub enum OpenTelemetryFaasTrigger { + /// A response to some data source operation such as a database or filesystem read/write + #[default] + Datasource, + /// To provide an answer to an inbound HTTP request + Http, + /// A function is set to be executed when messages are sent to a messaging system + PubSub, + /// A function is scheduled to be executed regularly + Timer, + /// If none of the others apply + Other, +} + +impl Display for OpenTelemetryFaasTrigger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OpenTelemetryFaasTrigger::Datasource => write!(f, "datasource"), + OpenTelemetryFaasTrigger::Http => write!(f, "http"), + OpenTelemetryFaasTrigger::PubSub => write!(f, "pubsub"), + OpenTelemetryFaasTrigger::Timer => write!(f, "timer"), + OpenTelemetryFaasTrigger::Other => write!(f, "other"), + } + } +} From c4594f797c9c3a133845298eaa0a2f896ebe4220 Mon Sep 17 00:00:00 2001 From: Taiki Ono Date: Tue, 2 Jul 2024 00:14:34 +0900 Subject: [PATCH 325/394] doc: Add an example for using the anyhow crate (#904) * Add "error handling with anyhow" example Signed-off-by: Taiki Ono * More doc for users who use external error types Signed-off-by: Taiki Ono --------- Signed-off-by: Taiki Ono --- examples/basic-error-anyhow/.gitignore | 1 + examples/basic-error-anyhow/Cargo.toml | 10 ++++++++++ examples/basic-error-anyhow/README.md | 13 +++++++++++++ examples/basic-error-anyhow/src/main.rs | 21 +++++++++++++++++++++ lambda-runtime/src/diagnostic.rs | 3 +++ 5 files changed, 48 insertions(+) create mode 100644 examples/basic-error-anyhow/.gitignore create mode 100644 examples/basic-error-anyhow/Cargo.toml create mode 100644 examples/basic-error-anyhow/README.md create mode 100644 examples/basic-error-anyhow/src/main.rs diff --git a/examples/basic-error-anyhow/.gitignore b/examples/basic-error-anyhow/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/examples/basic-error-anyhow/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/basic-error-anyhow/Cargo.toml b/examples/basic-error-anyhow/Cargo.toml new file mode 100644 index 00000000..a0ff62db --- /dev/null +++ b/examples/basic-error-anyhow/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "basic-error-anyhow" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/README.md b/examples/basic-error-anyhow/README.md new file mode 100644 index 00000000..b659c283 --- /dev/null +++ b/examples/basic-error-anyhow/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function Error Handling With `anyhow` Crate Example + +This example shows how to use external error types like `anyhow::Error`. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-error-anyhow/src/main.rs b/examples/basic-error-anyhow/src/main.rs new file mode 100644 index 00000000..cbca84fd --- /dev/null +++ b/examples/basic-error-anyhow/src/main.rs @@ -0,0 +1,21 @@ +use anyhow::bail; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Request {} + +/// Return anyhow::Result in the main body for the Lambda function. +async fn function_handler(_event: LambdaEvent) -> anyhow::Result<()> { + bail!("This is an error message"); +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_runtime::run(service_fn(|event: LambdaEvent| async move { + function_handler(event) + .await + .map_err(Into::>::into) + })) + .await +} diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index bc9ba623..9a7230a7 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -7,6 +7,9 @@ use crate::{deserializer::DeserializeError, Error}; /// /// `Diagnostic` is automatically derived for some common types, /// like boxed types that implement [`Error`][std::error::Error]. +/// If you use an error type which comes from a external crate like anyhow, +/// you need convert it to common types like `Box`. +/// See the examples for more details. /// /// [`error_type`][`Diagnostic::error_type`] is derived from the type name of /// the original error with [`std::any::type_name`] as a fallback, which may From 4ee10b0906728d34926b1eba2e90702e782472ec Mon Sep 17 00:00:00 2001 From: mx Date: Thu, 4 Jul 2024 03:07:58 +1200 Subject: [PATCH 326/394] Improved payload Deser error messages (#905) * made error messages more informative * added a suggestion to log at TRACE level to README --- README.md | 4 +++- lambda-runtime/src/layers/api_response.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0a19e467..44ec1983 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,9 @@ async fn main() -> Result<(), Error> { } ``` -The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) if they're configured for your function. By default, the log level to emit events is `INFO`. +The subscriber uses `RUST_LOG` environment variable to determine the log level for your function. It also uses [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/), if configured. + +By default, the log level to emit events is `INFO`. Log at `TRACE` level for more detail, including a dump of the raw payload. ## AWS event objects diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 5994012c..7c963f68 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -98,7 +98,7 @@ where Ok(()) }; if let Err(err) = trace_fn() { - error!(error = ?err, "failed to parse raw JSON event received from Lambda"); + error!(error = ?err, "Failed to parse raw JSON event received from Lambda. The handler will not be called. Log at TRACE level to see the payload."); return RuntimeApiResponseFuture::Ready(Some(Err(err))); }; @@ -124,7 +124,7 @@ fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result> + Debug, { - error!(error = ?err, "building error response for Lambda Runtime API"); + error!(error = ?err, "Request payload deserialization into LambdaEvent failed. The handler will not be called. Log at TRACE level to see the payload."); EventErrorRequest::new(request_id, err).into_req() } From 4b5ffc8c435809ed471a530a6b613bd81f8fa2ed Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Tue, 9 Jul 2024 10:33:21 +1000 Subject: [PATCH 327/394] added RDS IAM Auth example (#908) * added RDS IAM Auth example * restructuring project for testing * removing CDK temp files * removing package-lock.json --- examples/lambda-rds-iam-auth/.gitignore | 10 + examples/lambda-rds-iam-auth/Cargo.toml | 14 + examples/lambda-rds-iam-auth/cdk/.gitignore | 134 + examples/lambda-rds-iam-auth/cdk/README.md | 8 + examples/lambda-rds-iam-auth/cdk/app.ts | 105 + examples/lambda-rds-iam-auth/cdk/cdk.json | 21 + examples/lambda-rds-iam-auth/cdk/package.json | 13 + .../lambda-rds-iam-auth/cdk/tsconfig.json | 31 + .../lambda-rds-iam-auth/src/global-bundle.pem | 3028 +++++++++++++++++ examples/lambda-rds-iam-auth/src/main.rs | 109 + 10 files changed, 3473 insertions(+) create mode 100644 examples/lambda-rds-iam-auth/.gitignore create mode 100644 examples/lambda-rds-iam-auth/Cargo.toml create mode 100644 examples/lambda-rds-iam-auth/cdk/.gitignore create mode 100644 examples/lambda-rds-iam-auth/cdk/README.md create mode 100644 examples/lambda-rds-iam-auth/cdk/app.ts create mode 100644 examples/lambda-rds-iam-auth/cdk/cdk.json create mode 100644 examples/lambda-rds-iam-auth/cdk/package.json create mode 100644 examples/lambda-rds-iam-auth/cdk/tsconfig.json create mode 100644 examples/lambda-rds-iam-auth/src/global-bundle.pem create mode 100644 examples/lambda-rds-iam-auth/src/main.rs diff --git a/examples/lambda-rds-iam-auth/.gitignore b/examples/lambda-rds-iam-auth/.gitignore new file mode 100644 index 00000000..fbbbb6eb --- /dev/null +++ b/examples/lambda-rds-iam-auth/.gitignore @@ -0,0 +1,10 @@ +# Rust +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/examples/lambda-rds-iam-auth/Cargo.toml b/examples/lambda-rds-iam-auth/Cargo.toml new file mode 100644 index 00000000..a1e212ae --- /dev/null +++ b/examples/lambda-rds-iam-auth/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rds-iam-rust-lambda" +version = "0.1.0" +edition = "2021" + +[dependencies] +lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0.120" +aws-config = "1.0.1" +aws-credential-types = "1.0.1" +aws-sigv4 = "1.0.1" +url = "2.5.0" +tokio = { version = "1.25.0", features = ["full"] } +sqlx = { version = "0.7.4", features = ["tls-rustls", "postgres", "runtime-tokio"] } diff --git a/examples/lambda-rds-iam-auth/cdk/.gitignore b/examples/lambda-rds-iam-auth/cdk/.gitignore new file mode 100644 index 00000000..b2e6f363 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/.gitignore @@ -0,0 +1,134 @@ +# CDK +node_modules +cdk.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/examples/lambda-rds-iam-auth/cdk/README.md b/examples/lambda-rds-iam-auth/cdk/README.md new file mode 100644 index 00000000..4378fb42 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/README.md @@ -0,0 +1,8 @@ +# AWS Lambda Function that uses RDS's IAM Authnetication +This example shows how to build and deploy Rust Lambda Function and an RDS instance using AWS CDK and + +Build & Deploy +1. `npm install` +1. `npx cdk deploy` +1. Using the dev instance or using a local Postgres client: connect into the RDS instance as root and create the required Users with permissions `CREATE USER lambda; GRANT rds_iam TO lambda;` +1. Go to the Lambda Function in the AWS console and invoke the lambda function \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/cdk/app.ts b/examples/lambda-rds-iam-auth/cdk/app.ts new file mode 100644 index 00000000..127afad9 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/app.ts @@ -0,0 +1,105 @@ +import { join } from 'path'; +import * as cdk from 'aws-cdk-lib'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { RustFunction } from '@cdklabs/aws-lambda-rust' + +class LambdaRDSStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create a VPC + const vpc = new ec2.Vpc(this, 'VPC'); + + // Admin DB user + const DB_ADMIN_USERNAME = 'root'; + const DB_USERNAME = 'lambda'; + + // Lambda DB user + const DB_NAME = 'foo'; + + // Create an RDS instance + const db = new rds.DatabaseInstance(this, 'Postgres', { + engine: rds.DatabaseInstanceEngine.POSTGRES, + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + credentials: rds.Credentials.fromGeneratedSecret(DB_ADMIN_USERNAME), + iamAuthentication: true, + publiclyAccessible: true, + databaseName: DB_NAME, + deleteAutomatedBackups: true, + removalPolicy: cdk.RemovalPolicy.DESTROY + }) + + db.connections.allowFromAnyIpv4(ec2.Port.allTcp()) + + // RDS SSL Cert Lambda Layer alternative to loading the certificates at compile time + /* + const certLayer = new lambda.LayerVersion(this, 'CertLayer', { + description: 'SSL Certificate Layer', + code: lambda.Code.fromAsset('certs'), + compatibleArchitectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64] + }); + */ + + const lambdaSG = new ec2.SecurityGroup(this, 'LambdaSG', { + securityGroupName: 'LambdaSG', + allowAllOutbound: true, + vpc: vpc, + }) + // create a rust lambda function + const rustLambdaFunction = new RustFunction(this, "lambda", { + entry: join(__dirname, '..', 'lambda'), + vpc: vpc, + securityGroups: [lambdaSG], + environment: { + DB_HOSTNAME: db.dbInstanceEndpointAddress, + DB_PORT: db.dbInstanceEndpointPort, + DB_NAME: DB_NAME, + DB_USERNAME: DB_USERNAME, + }, + bundling: { + forceDockerBundling: true, + }, + runtime: lambda.Runtime.PROVIDED_AL2023, + timeout: cdk.Duration.seconds(60), + }); + + // MySQL + /* + CREATE USER 'lambda' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; + GRANT ALL PRIVILEGES ON foo.* TO 'lambda'; + ALTER USER 'lambda' REQUIRE SSL; + */ + + // Postgres + /* + CREATE USER db_userx; + GRANT rds_iam TO db_userx; + */ + db.grantConnect(rustLambdaFunction, DB_USERNAME); + db.connections.allowDefaultPortFrom(rustLambdaFunction); + + /* + Dev Instance for initialising the datbase with the above commands + */ + const devInstance = new ec2.Instance(this, 'dev', { + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + machineImage: ec2.MachineImage.latestAmazonLinux2023(), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM) + }) + db.grantConnect(devInstance, DB_ADMIN_USERNAME); + db.grantConnect(devInstance, DB_USERNAME); + db.connections.allowDefaultPortFrom(devInstance); + + // Output the Lambda function ARN + new cdk.CfnOutput(this, 'LambdaFunctionConsole', { + value: `https://${this.region}.console.aws.amazon.com/lambda/home?region=${this.region}#/functions/${rustLambdaFunction.functionName}?tab=testing` + }); + } +} + +const app = new cdk.App(); +new LambdaRDSStack(app, 'LambdaRDSStack'); diff --git a/examples/lambda-rds-iam-auth/cdk/cdk.json b/examples/lambda-rds-iam-auth/cdk/cdk.json new file mode 100644 index 00000000..8d92ca81 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/cdk.json @@ -0,0 +1,21 @@ +{ + "app": "npx ts-node --prefer-ts-exts app.ts", + "watch": { + "include": [ + "**.js", + "**.rs", + "**.ts" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } +} \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/cdk/package.json b/examples/lambda-rds-iam-auth/cdk/package.json new file mode 100644 index 00000000..ebaf98c5 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "@cdklabs/aws-lambda-rust": "0.0.4", + "aws-cdk-lib": "^2.147.0", + "path": "^0.12.7", + "prettier": "^3.3.2", + "rust.aws-cdk-lambda": "^1.2.1", + "ts-node": "^10.9.2" + }, + "devDependencies": { + "@types/node": "^20.14.10" + } +} diff --git a/examples/lambda-rds-iam-auth/cdk/tsconfig.json b/examples/lambda-rds-iam-auth/cdk/tsconfig.json new file mode 100644 index 00000000..72067eca --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/src/global-bundle.pem b/examples/lambda-rds-iam-auth/src/global-bundle.pem new file mode 100644 index 00000000..de68d41a --- /dev/null +++ b/examples/lambda-rds-iam-auth/src/global-bundle.pem @@ -0,0 +1,3028 @@ +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAM2ZN/+nPi27MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDI4MTgwNTU4WhcNMjQxMDI2MTgwNTU4WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgYWYtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR2351uPMZaJk2gMGT+1sk8HE9MQh2rc +/sCnbxGn2p1c7Oi9aBbd/GiFijeJb2BXvHU+TOq3d3Jjqepq8tapXVt4ojbTJNyC +J5E7r7KjTktKdLxtBE1MK25aY+IRJjtdU6vG3KiPKUT1naO3xs3yt0F76WVuFivd +9OHv2a+KHvPkRUWIxpmAHuMY9SIIMmEZtVE7YZGx5ah0iO4JzItHcbVR0y0PBH55 +arpFBddpIVHCacp1FUPxSEWkOpI7q0AaU4xfX0fe1BV5HZYRKpBOIp1TtZWvJD+X +jGUtL1BEsT5vN5g9MkqdtYrC+3SNpAk4VtpvJrdjraI/hhvfeXNnAwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUEEi/ +WWMcBJsoGXg+EZwkQ0MscZQwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0Ms +cZQwDQYJKoZIhvcNAQELBQADggEBAGDZ5js5Pc/gC58LJrwMPXFhJDBS8QuDm23C +FFUdlqucskwOS3907ErK1ZkmVJCIqFLArHqskFXMAkRZ2PNR7RjWLqBs+0znG5yH +hRKb4DXzhUFQ18UBRcvT6V6zN97HTRsEEaNhM/7k8YLe7P8vfNZ28VIoJIGGgv9D +wQBBvkxQ71oOmAG0AwaGD0ORGUfbYry9Dz4a4IcUsZyRWRMADixgrFv6VuETp26s +/+z+iqNaGWlELBKh3iQCT6Y/1UnkPLO42bxrCSyOvshdkYN58Q2gMTE1SVTqyo8G +Lw8lLAz9bnvUSgHzB3jRrSx6ggF/WRMRYlR++y6LXP4SAsSAaC0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAJYM4LxvTZA6MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDMwMjAyMDM2WhcNMjQxMDI4MjAyMDM2WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgZXUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqM921jXCXeqpRNCS9CBPOe5N7gMaEt+D +s5uR3riZbqzRlHGiF1jZihkXfHAIQewDwy+Yz+Oec1aEZCQMhUHxZJPusuX0cJfj +b+UluFqHIijL2TfXJ3D0PVLLoNTQJZ8+GAPECyojAaNuoHbdVqxhOcznMsXIXVFq +yVLKDGvyKkJjai/iSPDrQMXufg3kWt0ISjNLvsG5IFXgP4gttsM8i0yvRd4QcHoo +DjvH7V3cS+CQqW5SnDrGnHToB0RLskE1ET+oNOfeN9PWOxQprMOX/zmJhnJQlTqD +QP7jcf7SddxrKFjuziFiouskJJyNDsMjt1Lf60+oHZhed2ogTeifGwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUFBAF +cgJe/BBuZiGeZ8STfpkgRYQwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkg +RYQwDQYJKoZIhvcNAQELBQADggEBAKAYUtlvDuX2UpZW9i1QgsjFuy/ErbW0dLHU +e/IcFtju2z6RLZ+uF+5A8Kme7IKG1hgt8s+w9TRVQS/7ukQzoK3TaN6XKXRosjtc +o9Rm4gYWM8bmglzY1TPNaiI4HC7546hSwJhubjN0bXCuj/0sHD6w2DkiGuwKNAef +yTu5vZhPkeNyXLykxkzz7bNp2/PtMBnzIp+WpS7uUDmWyScGPohKMq5PqvL59z+L +ZI3CYeMZrJ5VpXUg3fNNIz/83N3G0sk7wr0ohs/kHTP7xPOYB0zD7Ku4HA0Q9Swf +WX0qr6UQgTPMjfYDLffI7aEId0gxKw1eGYc6Cq5JAZ3ipi/cBFc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJANew34ehz5l8MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkwNTEwMjE0ODI3WhcNMjQwNTA4MjE0ODI3WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgbWUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp7BYV88MukcY+rq0r79+C8UzkT30fEfT +aPXbx1d6M7uheGN4FMaoYmL+JE1NZPaMRIPTHhFtLSdPccInvenRDIatcXX+jgOk +UA6lnHQ98pwN0pfDUyz/Vph4jBR9LcVkBbe0zdoKKp+HGbMPRU0N2yNrog9gM5O8 +gkU/3O2csJ/OFQNnj4c2NQloGMUpEmedwJMOyQQfcUyt9CvZDfIPNnheUS29jGSw +ERpJe/AENu8Pxyc72jaXQuD+FEi2Ck6lBkSlWYQFhTottAeGvVFNCzKszCntrtqd +rdYUwurYsLTXDHv9nW2hfDUQa0mhXf9gNDOBIVAZugR9NqNRNyYLHQIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU54cf +DjgwBx4ycBH8+/r8WXdaiqYwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXda +iqYwDQYJKoZIhvcNAQELBQADggEBAIIMTSPx/dR7jlcxggr+O6OyY49Rlap2laKA +eC/XI4ySP3vQkIFlP822U9Kh8a9s46eR0uiwV4AGLabcu0iKYfXjPkIprVCqeXV7 +ny9oDtrbflyj7NcGdZLvuzSwgl9SYTJp7PVCZtZutsPYlbJrBPHwFABvAkMvRtDB +hitIg4AESDGPoCl94sYHpfDfjpUDMSrAMDUyO6DyBdZH5ryRMAs3lGtsmkkNUrso +aTW6R05681Z0mvkRdb+cdXtKOSuDZPoe2wJJIaz3IlNQNSrB5TImMYgmt6iAsFhv +3vfTSTKrZDNTJn4ybG6pq1zWExoXsktZPylJly6R3RBwV6nwqBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBjCCAu6gAwIBAgIJAMc0ZzaSUK51MA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkw +ODIyMTcwODUwWhcNMjQwODIyMTcwODUwWjCBjzELMAkGA1UEBhMCVVMxEDAOBgNV +BAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoMGUFtYXpv +biBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxIDAeBgNV +BAMMF0FtYXpvbiBSRFMgUm9vdCAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEArXnF/E6/Qh+ku3hQTSKPMhQQlCpoWvnIthzX6MK3p5a0eXKZ +oWIjYcNNG6UwJjp4fUXl6glp53Jobn+tWNX88dNH2n8DVbppSwScVE2LpuL+94vY +0EYE/XxN7svKea8YvlrqkUBKyxLxTjh+U/KrGOaHxz9v0l6ZNlDbuaZw3qIWdD/I +6aNbGeRUVtpM6P+bWIoxVl/caQylQS6CEYUk+CpVyJSkopwJlzXT07tMoDL5WgX9 +O08KVgDNz9qP/IGtAcRduRcNioH3E9v981QO1zt/Gpb2f8NqAjUUCUZzOnij6mx9 +McZ+9cWX88CRzR0vQODWuZscgI08NvM69Fn2SQIDAQABo2MwYTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUc19g2LzLA5j0Kxc0LjZa +pmD/vB8wHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJKoZIhvcN +AQELBQADggEBAHAG7WTmyjzPRIM85rVj+fWHsLIvqpw6DObIjMWokpliCeMINZFV +ynfgBKsf1ExwbvJNzYFXW6dihnguDG9VMPpi2up/ctQTN8tm9nDKOy08uNZoofMc +NUZxKCEkVKZv+IL4oHoeayt8egtv3ujJM6V14AstMQ6SwvwvA93EP/Ug2e4WAXHu +cbI1NAbUgVDqp+DRdfvZkgYKryjTWd/0+1fS8X1bBZVWzl7eirNVnHbSH2ZDpNuY +0SBd8dj5F6ld3t58ydZbrTHze7JJOd8ijySAp4/kiu9UfZWuTPABzDa/DSdz9Dk/ +zPW4CXXvhLmE02TA9/HeCw3KEHIwicNuEfw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEDCCAvigAwIBAgIJAKFMXyltvuRdMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzElMCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTAe +Fw0xOTA4MTkxNzM4MjZaFw0yNDA4MTkxNzM4MjZaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UECgwZ +QW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEl +MCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMkZdnIH9ndatGAcFo+DppGJ1HUt4x+zeO+0 +ZZ29m0sfGetVulmTlv2d5b66e+QXZFWpcPQMouSxxYTW08TbrQiZngKr40JNXftA +atvzBqIImD4II0ZX5UEVj2h98qe/ypW5xaDN7fEa5e8FkYB1TEemPaWIbNXqchcL +tV7IJPr3Cd7Z5gZJlmujIVDPpMuSiNaal9/6nT9oqN+JSM1fx5SzrU5ssg1Vp1vv +5Xab64uOg7wCJRB9R2GC9XD04odX6VcxUAGrZo6LR64ZSifupo3l+R5sVOc5i8NH +skdboTzU9H7+oSdqoAyhIU717PcqeDum23DYlPE2nGBWckE+eT8CAwEAAaNjMGEw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK2hDBWl +sbHzt/EHd0QYOooqcFPhMB8GA1UdIwQYMBaAFK2hDBWlsbHzt/EHd0QYOooqcFPh +MA0GCSqGSIb3DQEBCwUAA4IBAQAO/718k8EnOqJDx6wweUscGTGL/QdKXUzTVRAx +JUsjNUv49mH2HQVEW7oxszfH6cPCaupNAddMhQc4C/af6GHX8HnqfPDk27/yBQI+ +yBBvIanGgxv9c9wBbmcIaCEWJcsLp3HzXSYHmjiqkViXwCpYfkoV3Ns2m8bp+KCO +y9XmcCKRaXkt237qmoxoh2sGmBHk2UlQtOsMC0aUQ4d7teAJG0q6pbyZEiPyKZY1 +XR/UVxMJL0Q4iVpcRS1kaNCMfqS2smbLJeNdsan8pkw1dvPhcaVTb7CvjhJtjztF +YfDzAI5794qMlWxwilKMmUvDlPPOTen8NNHkLwWvyFCH7Doh +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFjCCAv6gAwIBAgIJAMzYZJ+R9NBVMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBD +QTAeFw0xOTA4MjEyMjI5NDlaFw0yNDA4MjEyMjI5NDlaMIGXMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBDQTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7kkS6vjgKKQTPynC2NjdN5aPPV +O71G0JJS/2ARVBVJd93JLiGovVJilfWYfwZCs4gTRSSjrUD4D4HyqCd6A+eEEtJq +M0DEC7i0dC+9WNTsPszuB206Jy2IUmxZMIKJAA1NHSbIMjB+b6/JhbSUi7nKdbR/ +brj83bF+RoSA+ogrgX7mQbxhmFcoZN9OGaJgYKsKWUt5Wqv627KkGodUK8mDepgD +S3ZfoRQRx3iceETpcmHJvaIge6+vyDX3d9Z22jmvQ4AKv3py2CmU2UwuhOltFDwB +0ddtb39vgwrJxaGfiMRHpEP1DfNLWHAnA69/pgZPwIggidS+iBPUhgucMp8CAwEA +AaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FGnTGpQuQ2H/DZlXMQijZEhjs7TdMB8GA1UdIwQYMBaAFGnTGpQuQ2H/DZlXMQij +ZEhjs7TdMA0GCSqGSIb3DQEBCwUAA4IBAQC3xz1vQvcXAfpcZlngiRWeqU8zQAMQ +LZPCFNv7PVk4pmqX+ZiIRo4f9Zy7TrOVcboCnqmP/b/mNq0gVF4O+88jwXJZD+f8 +/RnABMZcnGU+vK0YmxsAtYU6TIb1uhRFmbF8K80HHbj9vSjBGIQdPCbvmR2zY6VJ +BYM+w9U9hp6H4DVMLKXPc1bFlKA5OBTgUtgkDibWJKFOEPW3UOYwp9uq6pFoN0AO +xMTldqWFsOF3bJIlvOY0c/1EFZXu3Ns6/oCP//Ap9vumldYMUZWmbK+gK33FPOXV +8BQ6jNC29icv7lLDpRPwjibJBXX+peDR5UK4FdYcswWEB1Tix5X8dYu6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MjgxODA2NTNaFw0yNDEwMjgxODA2NTNaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBhZi1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvtV1OqmFa8zCVQSKOvPUJERLVFtd4rZmDpImc5rIoeBk7w/P +9lcKUJjO8R/w1a2lJXx3oQ81tiY0Piw6TpT62YWVRMWrOw8+Vxq1dNaDSFp9I8d0 +UHillSSbOk6FOrPDp+R6AwbGFqUDebbN5LFFoDKbhNmH1BVS0a6YNKpGigLRqhka +cClPslWtPqtjbaP3Jbxl26zWzLo7OtZl98dR225pq8aApNBwmtgA7Gh60HK/cX0t +32W94n8D+GKSg6R4MKredVFqRTi9hCCNUu0sxYPoELuM+mHiqB5NPjtm92EzCWs+ ++vgWhMc6GxG+82QSWx1Vj8sgLqtE/vLrWddf5QIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuLB4gYVJrSKJj/Gz +pqc6yeA+RcAwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0MscZQwDQYJKoZI +hvcNAQELBQADggEBABauYOZxUhe9/RhzGJ8MsWCz8eKcyDVd4FCnY6Qh+9wcmYNT +LtnD88LACtJKb/b81qYzcB0Em6+zVJ3Z9jznfr6buItE6es9wAoja22Xgv44BTHL +rimbgMwpTt3uEMXDffaS0Ww6YWb3pSE0XYI2ISMWz+xRERRf+QqktSaL39zuiaW5 +tfZMre+YhohRa/F0ZQl3RCd6yFcLx4UoSPqQsUl97WhYzwAxZZfwvLJXOc4ATt3u +VlCUylNDkaZztDJc/yN5XQoK9W5nOt2cLu513MGYKbuarQr8f+gYU8S+qOyuSRSP +NRITzwCRVnsJE+2JmcRInn/NcanB7uOGqTvJ9+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MzAyMDIxMzBaFw0yNDEwMzAyMDIxMzBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBldS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAtEyjYcajx6xImJn8Vz1zjdmL4ANPgQXwF7+tF7xccmNAZETb +bzb3I9i5fZlmrRaVznX+9biXVaGxYzIUIR3huQ3Q283KsDYnVuGa3mk690vhvJbB +QIPgKa5mVwJppnuJm78KqaSpi0vxyCPe3h8h6LLFawVyWrYNZ4okli1/U582eef8 +RzJp/Ear3KgHOLIiCdPDF0rjOdCG1MOlDLixVnPn9IYOciqO+VivXBg+jtfc5J+L +AaPm0/Yx4uELt1tkbWkm4BvTU/gBOODnYziITZM0l6Fgwvbwgq5duAtKW+h031lC +37rEvrclqcp4wrsUYcLAWX79ZyKIlRxcAdvEhQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU7zPyc0azQxnBCe7D +b9KAadH1QSEwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkgRYQwDQYJKoZI +hvcNAQELBQADggEBAFGaNiYxg7yC/xauXPlaqLCtwbm2dKyK9nIFbF/7be8mk7Q3 +MOA0of1vGHPLVQLr6bJJpD9MAbUcm4cPAwWaxwcNpxOjYOFDaq10PCK4eRAxZWwF +NJRIRmGsl8NEsMNTMCy8X+Kyw5EzH4vWFl5Uf2bGKOeFg0zt43jWQVOX6C+aL3Cd +pRS5MhmYpxMG8irrNOxf4NVFE2zpJOCm3bn0STLhkDcV/ww4zMzObTJhiIb5wSWn +EXKKWhUXuRt7A2y1KJtXpTbSRHQxE++69Go1tWhXtRiULCJtf7wF2Ksm0RR/AdXT +1uR1vKyH5KBJPX3ppYkQDukoHTFR0CpB+G84NLo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTA1 +MTAyMTU4NDNaFw0yNTA2MDExMjAwMDBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBtZS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAudOYPZH+ihJAo6hNYMB5izPVBe3TYhnZm8+X3IoaaYiKtsp1 +JJhkTT0CEejYIQ58Fh4QrMUyWvU8qsdK3diNyQRoYLbctsBPgxBR1u07eUJDv38/ +C1JlqgHmMnMi4y68Iy7ymv50QgAMuaBqgEBRI1R6Lfbyrb2YvH5txjJyTVMwuCfd +YPAtZVouRz0JxmnfsHyxjE+So56uOKTDuw++Ho4HhZ7Qveej7XB8b+PIPuroknd3 +FQB5RVbXRvt5ZcVD4F2fbEdBniF7FAF4dEiofVCQGQ2nynT7dZdEIPfPdH3n7ZmE +lAOmwHQ6G83OsiHRBLnbp+QZRgOsjkHJxT20bQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUOEVDM7VomRH4HVdA +QvIMNq2tXOcwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXdaiqYwDQYJKoZI +hvcNAQELBQADggEBAHhvMssj+Th8IpNePU6RH0BiL6o9c437R3Q4IEJeFdYL+nZz +PW/rELDPvLRUNMfKM+KzduLZ+l29HahxefejYPXtvXBlq/E/9czFDD4fWXg+zVou +uDXhyrV4kNmP4S0eqsAP/jQHPOZAMFA4yVwO9hlqmePhyDnszCh9c1PfJSBh49+b +4w7i/L3VBOMt8j3EKYvqz0gVfpeqhJwL4Hey8UbVfJRFJMJzfNHpePqtDRAY7yjV +PYquRaV2ab/E+/7VFkWMM4tazYz/qsYA2jSH+4xDHvYk8LnsbcrF9iuidQmEc5sb +FgcWaSKG4DJjcI5k7AJLWcXyTDt21Ci43LE+I9Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICVIYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDQxNzEz +MDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBhcC1zb3V0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDUYOz1hGL42yUCrcsMSOoU8AeD/3KgZ4q7gP+vAz1WnY9K/kim +eWN/2Qqzlo3+mxSFQFyD4MyV3+CnCPnBl9Sh1G/F6kThNiJ7dEWSWBQGAB6HMDbC +BaAsmUc1UIz8sLTL3fO+S9wYhA63Wun0Fbm/Rn2yk/4WnJAaMZcEtYf6e0KNa0LM +p/kN/70/8cD3iz3dDR8zOZFpHoCtf0ek80QqTich0A9n3JLxR6g6tpwoYviVg89e +qCjQ4axxOkWWeusLeTJCcY6CkVyFvDAKvcUl1ytM5AiaUkXblE7zDFXRM4qMMRdt +lPm8d3pFxh0fRYk8bIKnpmtOpz3RIctDrZZxAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT99wKJftD3jb4sHoHG +i3uGlH6W6TAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAZ17hhr3dII3hUfuHQ1hPWGrpJOX/G9dLzkprEIcCidkmRYl+ +hu1Pe3caRMh/17+qsoEErmnVq5jNY9X1GZL04IZH8YbHc7iRHw3HcWAdhN8633+K +jYEB2LbJ3vluCGnCejq9djDb6alOugdLMJzxOkHDhMZ6/gYbECOot+ph1tQuZXzD +tZ7prRsrcuPBChHlPjmGy8M9z8u+kF196iNSUGC4lM8vLkHM7ycc1/ZOwRq9aaTe +iOghbQQyAEe03MWCyDGtSmDfr0qEk+CHN+6hPiaL8qKt4s+V9P7DeK4iW08ny8Ox +AVS7u0OK/5+jKMAMrKwpYrBydOjTUTHScocyNw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICQ2QwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDUxODQ2 +MjlaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBzYS1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMMvR+ReRnOzqJzoaPipNTt1Z2VA968jlN1+SYKUrYM3No+Vpz0H +M6Tn0oYB66ByVsXiGc28ulsqX1HbHsxqDPwvQTKvO7SrmDokoAkjJgLocOLUAeld +5AwvUjxGRP6yY90NV7X786MpnYb2Il9DIIaV9HjCmPt+rjy2CZjS0UjPjCKNfB8J +bFjgW6GGscjeyGb/zFwcom5p4j0rLydbNaOr9wOyQrtt3ZQWLYGY9Zees/b8pmcc +Jt+7jstZ2UMV32OO/kIsJ4rMUn2r/uxccPwAc1IDeRSSxOrnFKhW3Cu69iB3bHp7 +JbawY12g7zshE4I14sHjv3QoXASoXjx4xgMCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI1Fc/Ql2jx+oJPgBVYq +ccgP0pQ8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB4VVVabVp70myuYuZ3vltQIWqSUMhkaTzehMgGcHjMf9iLoZ/I +93KiFUSGnek5cRePyS9wcpp0fcBT3FvkjpUdCjVtdttJgZFhBxgTd8y26ImdDDMR +4+BUuhI5msvjL08f+Vkkpu1GQcGmyFVPFOy/UY8iefu+QyUuiBUnUuEDd49Hw0Fn +/kIPII6Vj82a2mWV/Q8e+rgN8dIRksRjKI03DEoP8lhPlsOkhdwU6Uz9Vu6NOB2Q +Ls1kbcxAc7cFSyRVJEhh12Sz9d0q/CQSTFsVJKOjSNQBQfVnLz1GwO/IieUEAr4C +jkTntH0r1LX5b/GwN4R887LvjAEdTbg1his7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIkHMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTA2MTc0 +MDIxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtd2VzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDD2yzbbAl77OofTghDMEf624OvU0eS9O+lsdO0QlbfUfWa1Kd6 +0WkgjkLZGfSRxEHMCnrv4UPBSK/Qwn6FTjkDLgemhqBtAnplN4VsoDL+BkRX4Wwq +/dSQJE2b+0hm9w9UMVGFDEq1TMotGGTD2B71eh9HEKzKhGzqiNeGsiX4VV+LJzdH +uM23eGisNqmd4iJV0zcAZ+Gbh2zK6fqTOCvXtm7Idccv8vZZnyk1FiWl3NR4WAgK +AkvWTIoFU3Mt7dIXKKClVmvssG8WHCkd3Xcb4FHy/G756UZcq67gMMTX/9fOFM/v +l5C0+CHl33Yig1vIDZd+fXV1KZD84dEJfEvHAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR+ap20kO/6A7pPxo3+ +T3CfqZpQWjAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAHCJky2tPjPttlDM/RIqExupBkNrnSYnOK4kr9xJ3sl8UF2DA +PAnYsjXp3rfcjN/k/FVOhxwzi3cXJF/2Tjj39Bm/OEfYTOJDNYtBwB0VVH4ffa/6 +tZl87jaIkrxJcreeeHqYMnIxeN0b/kliyA+a5L2Yb0VPjt9INq34QDc1v74FNZ17 +4z8nr1nzg4xsOWu0Dbjo966lm4nOYIGBRGOKEkHZRZ4mEiMgr3YLkv8gSmeitx57 +Z6dVemNtUic/LVo5Iqw4n3TBS0iF2C1Q1xT/s3h+0SXZlfOWttzSluDvoMv5PvCd +pFjNn+aXLAALoihL1MJSsxydtsLjOBro5eK0Vw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOFAwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAxNzQ2 +MjFaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzU72e6XbaJbi4HjJoRNjKxzUEuChKQIt7k3CWzNnmjc5 +8I1MjCpa2W1iw1BYVysXSNSsLOtUsfvBZxi/1uyMn5ZCaf9aeoA9UsSkFSZBjOCN +DpKPCmfV1zcEOvJz26+1m8WDg+8Oa60QV0ou2AU1tYcw98fOQjcAES0JXXB80P2s +3UfkNcnDz+l4k7j4SllhFPhH6BQ4lD2NiFAP4HwoG6FeJUn45EPjzrydxjq6v5Fc +cQ8rGuHADVXotDbEhaYhNjIrsPL+puhjWfhJjheEw8c4whRZNp6gJ/b6WEes/ZhZ +h32DwsDsZw0BfRDUMgUn8TdecNexHUw8vQWeC181hwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwW9bWgkWkr0U +lrOsq2kvIdrECDgwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAEugF0Gj7HVhX0ehPZoGRYRt3PBuI2YjfrrJRTZ9X5wc +9T8oHmw07mHmNy1qqWvooNJg09bDGfB0k5goC2emDiIiGfc/kvMLI7u+eQOoMKj6 +mkfCncyRN3ty08Po45vTLBFZGUvtQmjM6yKewc4sXiASSBmQUpsMbiHRCL72M5qV +obcJOjGcIdDTmV1BHdWT+XcjynsGjUqOvQWWhhLPrn4jWe6Xuxll75qlrpn3IrIx +CRBv/5r7qbcQJPOgwQsyK4kv9Ly8g7YT1/vYBlR3cRsYQjccw5ceWUj2DrMVWhJ4 +prf+E3Aa4vYmLLOUUvKnDQ1k3RGNu56V0tonsQbfsaM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICEzUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAyMDUy +MjVaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOxHqdcPSA2uBjsCP4DLSlqSoPuQ/X1kkJLusVRKiQE2zayB +viuCBt4VB9Qsh2rW3iYGM+usDjltGnI1iUWA5KHcvHszSMkWAOYWLiMNKTlg6LCp +XnE89tvj5dIH6U8WlDvXLdjB/h30gW9JEX7S8supsBSci2GxEzb5mRdKaDuuF/0O +qvz4YE04pua3iZ9QwmMFuTAOYzD1M72aOpj+7Ac+YLMM61qOtU+AU6MndnQkKoQi +qmUN2A9IFaqHFzRlSdXwKCKUA4otzmz+/N3vFwjb5F4DSsbsrMfjeHMo6o/nb6Nh +YDb0VJxxPee6TxSuN7CQJ2FxMlFUezcoXqwqXD0CAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFDGGpon9WfIpsggE +CxHq8hZ7E2ESMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQAvpeQYEGZvoTVLgV9rd2+StPYykMsmFjWQcyn3dBTZRXC2 +lKq7QhQczMAOhEaaN29ZprjQzsA2X/UauKzLR2Uyqc2qOeO9/YOl0H3qauo8C/W9 +r8xqPbOCDLEXlOQ19fidXyyEPHEq5WFp8j+fTh+s8WOx2M7IuC0ANEetIZURYhSp +xl9XOPRCJxOhj7JdelhpweX0BJDNHeUFi0ClnFOws8oKQ7sQEv66d5ddxqqZ3NVv +RbCvCtEutQMOUMIuaygDlMn1anSM8N7Wndx8G6+Uy67AnhjGx7jw/0YPPxopEj6x +JXP8j0sJbcT9K/9/fPVLNT25RvQ/93T2+IQL4Ca2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICYpgwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExNzMx +NDhaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMk3YdSZ64iAYp6MyyKtYJtNzv7zFSnnNf6vv0FB4VnfITTMmOyZ +LXqKAT2ahZ00hXi34ewqJElgU6eUZT/QlzdIu359TEZyLVPwURflL6SWgdG01Q5X +O++7fSGcBRyIeuQWs9FJNIIqK8daF6qw0Rl5TXfu7P9dBc3zkgDXZm2DHmxGDD69 +7liQUiXzoE1q2Z9cA8+jirDioJxN9av8hQt12pskLQumhlArsMIhjhHRgF03HOh5 +tvi+RCfihVOxELyIRTRpTNiIwAqfZxxTWFTgfn+gijTmd0/1DseAe82aYic8JbuS +EMbrDduAWsqrnJ4GPzxHKLXX0JasCUcWyMECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPLtsq1NrwJXO13C9eHt +sLY11AGwMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAnWBKj5xV1A1mYd0kIgDdkjCwQkiKF5bjIbGkT3YEFFbXoJlSP +0lZZ/hDaOHI8wbLT44SzOvPEEmWF9EE7SJzkvSdQrUAWR9FwDLaU427ALI3ngNHy +lGJ2hse1fvSRNbmg8Sc9GBv8oqNIBPVuw+AJzHTacZ1OkyLZrz1c1QvwvwN2a+Jd +vH0V0YIhv66llKcYDMUQJAQi4+8nbRxXWv6Gq3pvrFoorzsnkr42V3JpbhnYiK+9 +nRKd4uWl62KRZjGkfMbmsqZpj2fdSWMY1UGyN1k+kDmCSWYdrTRDP0xjtIocwg+A +J116n4hV/5mbA0BaPiS2krtv17YAeHABZcvz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICV2YwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExOTM2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBldS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMEx54X2pHVv86APA0RWqxxRNmdkhAyp2R1cFWumKQRofoFv +n+SPXdkpIINpMuEIGJANozdiEz7SPsrAf8WHyD93j/ZxrdQftRcIGH41xasetKGl +I67uans8d+pgJgBKGb/Z+B5m+UsIuEVekpvgpwKtmmaLFC/NCGuSsJoFsRqoa6Gh +m34W6yJoY87UatddCqLY4IIXaBFsgK9Q/wYzYLbnWM6ZZvhJ52VMtdhcdzeTHNW0 +5LGuXJOF7Ahb4JkEhoo6TS2c0NxB4l4MBfBPgti+O7WjR3FfZHpt18A6Zkq6A2u6 +D/oTSL6c9/3sAaFTFgMyL3wHb2YlW0BPiljZIqECAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFOcAToAc6skWffJa +TnreaswAfrbcMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQA1d0Whc1QtspK496mFWfFEQNegLh0a9GWYlJm+Htcj5Nxt +DAIGXb+8xrtOZFHmYP7VLCT5Zd2C+XytqseK/+s07iAr0/EPF+O2qcyQWMN5KhgE +cXw2SwuP9FPV3i+YAm11PBVeenrmzuk9NrdHQ7TxU4v7VGhcsd2C++0EisrmquWH +mgIfmVDGxphwoES52cY6t3fbnXmTkvENvR+h3rj+fUiSz0aSo+XZUGHPgvuEKM/W +CBD9Smc9CBoBgvy7BgHRgRUmwtABZHFUIEjHI5rIr7ZvYn+6A0O6sogRfvVYtWFc +qpyrW1YX8mD0VlJ8fGKM3G+aCOsiiPKDV/Uafrm+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICGAcwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIxODE5 +NDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBldS1ub3J0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCiIYnhe4UNBbdBb/nQxl5giM0XoVHWNrYV5nB0YukA98+TPn9v +Aoj1RGYmtryjhrf01Kuv8SWO+Eom95L3zquoTFcE2gmxCfk7bp6qJJ3eHOJB+QUO +XsNRh76fwDzEF1yTeZWH49oeL2xO13EAx4PbZuZpZBttBM5zAxgZkqu4uWQczFEs +JXfla7z2fvWmGcTagX10O5C18XaFroV0ubvSyIi75ue9ykg/nlFAeB7O0Wxae88e +uhiBEFAuLYdqWnsg3459NfV8Yi1GnaitTym6VI3tHKIFiUvkSiy0DAlAGV2iiyJE +q+DsVEO4/hSINJEtII4TMtysOsYPpINqeEzRAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRR0UpnbQyjnHChgmOc +hnlc0PogzTAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAKJD4xVzSf4zSGTBJrmamo86jl1NHQxXUApAZuBZEc8tqC6TI +T5CeoSr9CMuVC8grYyBjXblC4OsM5NMvmsrXl/u5C9dEwtBFjo8mm53rOOIm1fxl +I1oYB/9mtO9ANWjkykuLzWeBlqDT/i7ckaKwalhLODsRDO73vRhYNjsIUGloNsKe +pxw3dzHwAZx4upSdEVG4RGCZ1D0LJ4Gw40OfD69hfkDfRVVxKGrbEzqxXRvovmDc +tKLdYZO/6REoca36v4BlgIs1CbUXJGLSXUwtg7YXGLSVBJ/U0+22iGJmBSNcoyUN +cjPFD9JQEhDDIYYKSGzIYpvslvGc4T5ISXFiuQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICZIEwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIyMTMy +MzJaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBALGiwqjiF7xIjT0Sx7zB3764K2T2a1DHnAxEOr+/EIftWKxWzT3u +PFwS2eEZcnKqSdRQ+vRzonLBeNLO4z8aLjQnNbkizZMBuXGm4BqRm1Kgq3nlLDQn +7YqdijOq54SpShvR/8zsO4sgMDMmHIYAJJOJqBdaus2smRt0NobIKc0liy7759KB +6kmQ47Gg+kfIwxrQA5zlvPLeQImxSoPi9LdbRoKvu7Iot7SOa+jGhVBh3VdqndJX +7tm/saj4NE375csmMETFLAOXjat7zViMRwVorX4V6AzEg1vkzxXpA9N7qywWIT5Y +fYaq5M8i6vvLg0CzrH9fHORtnkdjdu1y+0MCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFOhOx1yt3Z7mvGB9jBv +2ymdZwiOMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBehqY36UGDvPVU9+vtaYGr38dBbp+LzkjZzHwKT1XJSSUc2wqM +hnCIQKilonrTIvP1vmkQi8qHPvDRtBZKqvz/AErW/ZwQdZzqYNFd+BmOXaeZWV0Q +oHtDzXmcwtP8aUQpxN0e1xkWb1E80qoy+0uuRqb/50b/R4Q5qqSfJhkn6z8nwB10 +7RjLtJPrK8igxdpr3tGUzfAOyiPrIDncY7UJaL84GFp7WWAkH0WG3H8Y8DRcRXOU +mqDxDLUP3rNuow3jnGxiUY+gGX5OqaZg4f4P6QzOSmeQYs6nLpH0PiN00+oS1BbD +bpWdZEttILPI+vAYkU4QuBKKDjJL6HbSd+cn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIVCMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTEzMTcw +NjQxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDE+T2xYjUbxOp+pv+gRA3FO24+1zCWgXTDF1DHrh1lsPg5k7ht +2KPYzNc+Vg4E+jgPiW0BQnA6jStX5EqVh8BU60zELlxMNvpg4KumniMCZ3krtMUC +au1NF9rM7HBh+O+DYMBLK5eSIVt6lZosOb7bCi3V6wMLA8YqWSWqabkxwN4w0vXI +8lu5uXXFRemHnlNf+yA/4YtN4uaAyd0ami9+klwdkZfkrDOaiy59haOeBGL8EB/c +dbJJlguHH5CpCscs3RKtOOjEonXnKXldxarFdkMzi+aIIjQ8GyUOSAXHtQHb3gZ4 +nS6Ey0CMlwkB8vUObZU9fnjKJcL5QCQqOfwvAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQUPuRHohPxx4VjykmH +6usGrLL1ETAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAUdR9Vb3y33Yj6X6KGtuthZ08SwjImVQPtknzpajNE5jOJAh8 +quvQnU9nlnMO85fVDU1Dz3lLHGJ/YG1pt1Cqq2QQ200JcWCvBRgdvH6MjHoDQpqZ +HvQ3vLgOGqCLNQKFuet9BdpsHzsctKvCVaeBqbGpeCtt3Hh/26tgx0rorPLw90A2 +V8QSkZJjlcKkLa58N5CMM8Xz8KLWg3MZeT4DmlUXVCukqK2RGuP2L+aME8dOxqNv +OnOz1zrL5mR2iJoDpk8+VE/eBDmJX40IJk6jBjWoxAO/RXq+vBozuF5YHN1ujE92 +tO8HItgTp37XT8bJBAiAnt5mxw+NLSqtxk2QdQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICY4kwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTMyMDEx +NDJaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAr5u9OuLL/OF/fBNUX2kINJLzFl4DnmrhnLuSeSnBPgbb +qddjf5EFFJBfv7IYiIWEFPDbDG5hoBwgMup5bZDbas+ZTJTotnnxVJTQ6wlhTmns +eHECcg2pqGIKGrxZfbQhlj08/4nNAPvyYCTS0bEcmQ1emuDPyvJBYDDLDU6AbCB5 +6Z7YKFQPTiCBblvvNzchjLWF9IpkqiTsPHiEt21sAdABxj9ityStV3ja/W9BfgxH +wzABSTAQT6FbDwmQMo7dcFOPRX+hewQSic2Rn1XYjmNYzgEHisdUsH7eeXREAcTw +61TRvaLH8AiOWBnTEJXPAe6wYfrcSd1pD0MXpoB62wIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUytwMiomQOgX5 +Ichd+2lDWRUhkikwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBACf6lRDpfCD7BFRqiWM45hqIzffIaysmVfr+Jr+fBTjP +uYe/ba1omSrNGG23bOcT9LJ8hkQJ9d+FxUwYyICQNWOy6ejicm4z0C3VhphbTPqj +yjpt9nG56IAcV8BcRJh4o/2IfLNzC/dVuYJV8wj7XzwlvjysenwdrJCoLadkTr1h +eIdG6Le07sB9IxrGJL9e04afk37h7c8ESGSE4E+oS4JQEi3ATq8ne1B9DQ9SasXi +IRmhNAaISDzOPdyLXi9N9V9Lwe/DHcja7hgLGYx3UqfjhLhOKwp8HtoZORixAmOI +HfILgNmwyugAbuZoCazSKKBhQ0wgO0WZ66ZKTMG8Oho= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICUYkwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxODIx +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANCEZBZyu6yJQFZBJmSUZfSZd3Ui2gitczMKC4FLr0QzkbxY+cLa +uVONIOrPt4Rwi+3h/UdnUg917xao3S53XDf1TDMFEYp4U8EFPXqCn/GXBIWlU86P +PvBN+gzw3nS+aco7WXb+woTouvFVkk8FGU7J532llW8o/9ydQyDIMtdIkKTuMfho +OiNHSaNc+QXQ32TgvM9A/6q7ksUoNXGCP8hDOkSZ/YOLiI5TcdLh/aWj00ziL5bj +pvytiMZkilnc9dLY9QhRNr0vGqL0xjmWdoEXz9/OwjmCihHqJq+20MJPsvFm7D6a +2NKybR9U+ddrjb8/iyLOjURUZnj5O+2+OPcCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEBxMBdv81xuzqcK5TVu +pHj+Aor8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBZkfiVqGoJjBI37aTlLOSjLcjI75L5wBrwO39q+B4cwcmpj58P +3sivv+jhYfAGEbQnGRzjuFoyPzWnZ1DesRExX+wrmHsLLQbF2kVjLZhEJMHF9eB7 +GZlTPdTzHErcnuXkwA/OqyXMpj9aghcQFuhCNguEfnROY9sAoK2PTfnTz9NJHL+Q +UpDLEJEUfc0GZMVWYhahc0x38ZnSY2SKacIPECQrTI0KpqZv/P+ijCEcMD9xmYEb +jL4en+XKS1uJpw5fIU5Sj0MxhdGstH6S84iAE5J3GM3XHklGSFwwqPYvuTXvANH6 +uboynxRgSae59jIlAK6Jrr6GWMwQRbgcaAlW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICEkYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxOTUz +NDdaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAufodI2Flker8q7PXZG0P0vmFSlhQDw907A6eJuF/WeMo +GHnll3b4S6nC3oRS3nGeRMHbyU2KKXDwXNb3Mheu+ox+n5eb/BJ17eoj9HbQR1cd +gEkIciiAltf8gpMMQH4anP7TD+HNFlZnP7ii3geEJB2GGXSxgSWvUzH4etL67Zmn +TpGDWQMB0T8lK2ziLCMF4XAC/8xDELN/buHCNuhDpxpPebhct0T+f6Arzsiswt2j +7OeNeLLZwIZvVwAKF7zUFjC6m7/VmTQC8nidVY559D6l0UhhU0Co/txgq3HVsMOH +PbxmQUwJEKAzQXoIi+4uZzHFZrvov/nDTNJUhC6DqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwaZpaCme+EiV +M5gcjeHZSTgOn4owHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAR6a2meCZuXO2TF9bGqKGtZmaah4pH2ETcEVUjkvXVz +sl+ZKbYjrun+VkcMGGKLUjS812e7eDF726ptoku9/PZZIxlJB0isC/0OyixI8N4M +NsEyvp52XN9QundTjkl362bomPnHAApeU0mRbMDRR2JdT70u6yAzGLGsUwMkoNnw +1VR4XKhXHYGWo7KMvFrZ1KcjWhubxLHxZWXRulPVtGmyWg/MvE6KF+2XMLhojhUL ++9jB3Fpn53s6KMx5tVq1x8PukHmowcZuAF8k+W4gk8Y68wIwynrdZrKRyRv6CVtR +FZ8DeJgoNZT3y/GT254VqMxxfuy2Ccb/RInd16tEvVk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOYIwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTcyMDA1 +MjlaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA4dMak8W+XW8y/2F6nRiytFiA4XLwePadqWebGtlIgyCS +kbug8Jv5w7nlMkuxOxoUeD4WhI6A9EkAn3r0REM/2f0aYnd2KPxeqS2MrtdxxHw1 +xoOxk2x0piNSlOz6yog1idsKR5Wurf94fvM9FdTrMYPPrDabbGqiBMsZZmoHLvA3 +Z+57HEV2tU0Ei3vWeGIqnNjIekS+E06KhASxrkNU5vi611UsnYZlSi0VtJsH4UGV +LhnHl53aZL0YFO5mn/fzuNG/51qgk/6EFMMhaWInXX49Dia9FnnuWXwVwi6uX1Wn +7kjoHi5VtmC8ZlGEHroxX2DxEr6bhJTEpcLMnoQMqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUsUI5Cb3SWB8+ +gv1YLN/ABPMdxSAwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAJAF3E9PM1uzVL8YNdzb6fwJrxxqI2shvaMVmC1mXS+w +G0zh4v2hBZOf91l1EO0rwFD7+fxoI6hzQfMxIczh875T6vUXePKVOCOKI5wCrDad +zQbVqbFbdhsBjF4aUilOdtw2qjjs9JwPuB0VXN4/jY7m21oKEOcnpe36+7OiSPjN +xngYewCXKrSRqoj3mw+0w/+exYj3Wsush7uFssX18av78G+ehKPIVDXptOCP/N7W +8iKVNeQ2QGTnu2fzWsGUSvMGyM7yqT+h1ILaT//yQS8er511aHMLc142bD4D9VSy +DgactwPDTShK/PXqhvNey9v/sKXm4XatZvwcc8KYlW4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICcEUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNjU2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAndtkldmHtk4TVQAyqhAvtEHSMb6pLhyKrIFved1WO3S7 ++I+bWwv9b2W/ljJxLq9kdT43bhvzonNtI4a1LAohS6bqyirmk8sFfsWT3akb+4Sx +1sjc8Ovc9eqIWJCrUiSvv7+cS7ZTA9AgM1PxvHcsqrcUXiK3Jd/Dax9jdZE1e15s +BEhb2OEPE+tClFZ+soj8h8Pl2Clo5OAppEzYI4LmFKtp1X/BOf62k4jviXuCSst3 +UnRJzE/CXtjmN6oZySVWSe0rQYuyqRl6//9nK40cfGKyxVnimB8XrrcxUN743Vud +QQVU0Esm8OVTX013mXWQXJHP2c0aKkog8LOga0vobQIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQULmoOS1mFSjj+ +snUPx4DgS3SkLFYwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAkVL2P1M2/G9GM3DANVAqYOwmX0Xk58YBHQu6iiQg4j +b4Ky/qsZIsgT7YBsZA4AOcPKQFgGTWhe9pvhmXqoN3RYltN8Vn7TbUm/ZVDoMsrM +gwv0+TKxW1/u7s8cXYfHPiTzVSJuOogHx99kBW6b2f99GbP7O1Sv3sLq4j6lVvBX +Fiacf5LAWC925nvlTzLlBgIc3O9xDtFeAGtZcEtxZJ4fnGXiqEnN4539+nqzIyYq +nvlgCzyvcfRAxwltrJHuuRu6Maw5AGcd2Y0saMhqOVq9KYKFKuD/927BTrbd2JVf +2sGWyuPZPCk3gq+5pCjbD0c6DkhcMGI6WwxvM5V/zSM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJDQwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNzAz +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTMgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL9bL7KE0n02DLVtlZ2PL+g/BuHpMYFq2JnE2RgompGurDIZdjmh +1pxfL3nT+QIVMubuAOy8InRfkRxfpxyjKYdfLJTPJG+jDVL+wDcPpACFVqoV7Prg +pVYEV0lc5aoYw4bSeYFhdzgim6F8iyjoPnObjll9mo4XsHzSoqJLCd0QC+VG9Fw2 +q+GDRZrLRmVM2oNGDRbGpGIFg77aRxRapFZa8SnUgs2AqzuzKiprVH5i0S0M6dWr +i+kk5epmTtkiDHceX+dP/0R1NcnkCPoQ9TglyXyPdUdTPPRfKCq12dftqll+u4mV +ARdN6WFjovxax8EAP2OAUTi1afY+1JFMj+sCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLfhrbrO5exkCVgxW0x3 +Y2mAi8lNMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAigQ5VBNGyw+OZFXwxeJEAUYaXVoP/qrhTOJ6mCE2DXUVEoJeV +SxScy/TlFA9tJXqmit8JH8VQ/xDL4ubBfeMFAIAo4WzNWDVoeVMqphVEcDWBHsI1 +AETWzfsapRS9yQekOMmxg63d/nV8xewIl8aNVTHdHYXMqhhik47VrmaVEok1UQb3 +O971RadLXIEbVd9tjY5bMEHm89JsZDnDEw1hQXBb67Elu64OOxoKaHBgUH8AZn/2 +zFsL1ynNUjOhCSAA15pgd1vjwc0YsBbAEBPcHBWYBEyME6NLNarjOzBl4FMtATSF +wWCKRGkvqN8oxYhwR2jf2rR5Mu4DWkK5Q8Ep +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJVUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTkxODE2 +NTNaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAM3i/k2u6cqbMdcISGRvh+m+L0yaSIoOXjtpNEoIftAipTUYoMhL +InXGlQBVA4shkekxp1N7HXe1Y/iMaPEyb3n+16pf3vdjKl7kaSkIhjdUz3oVUEYt +i8Z/XeJJ9H2aEGuiZh3kHixQcZczn8cg3dA9aeeyLSEnTkl/npzLf//669Ammyhs +XcAo58yvT0D4E0D/EEHf2N7HRX7j/TlyWvw/39SW0usiCrHPKDLxByLojxLdHzso +QIp/S04m+eWn6rmD+uUiRteN1hI5ncQiA3wo4G37mHnUEKo6TtTUh+sd/ku6a8HK +glMBcgqudDI90s1OpuIAWmuWpY//8xEG2YECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPqhoWZcrVY9mU7tuemR +RBnQIj1jMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB6zOLZ+YINEs72heHIWlPZ8c6WY8MDU+Be5w1M+BK2kpcVhCUK +PJO4nMXpgamEX8DIiaO7emsunwJzMSvavSPRnxXXTKIc0i/g1EbiDjnYX9d85DkC +E1LaAUCmCZBVi9fIe0H2r9whIh4uLWZA41oMnJx/MOmo3XyMfQoWcqaSFlMqfZM4 +0rNoB/tdHLNuV4eIdaw2mlHxdWDtF4oH+HFm+2cVBUVC1jXKrFv/euRVtsTT+A6i +h2XBHKxQ1Y4HgAn0jACP2QSPEmuoQEIa57bEKEcZsBR8SDY6ZdTd2HLRIApcCOSF +MRM8CKLeF658I0XgF8D5EsYoKPsA+74Z+jDH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEETCCAvmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSUwIwYDVQQDDBxBbWF6b24gUkRTIEJldGEgUm9vdCAyMDE5IENBMB4XDTE5MDgy +MDE3MTAwN1oXDTI0MDgxOTE3MzgyNlowgZkxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6b24g +V2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMSowKAYDVQQD +DCFBbWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDTNCOlotQcLP8TP82U2+nk0bExVuuMVOgFeVMx +vbUHZQeIj9ikjk+jm6eTDnnkhoZcmJiJgRy+5Jt69QcRbb3y3SAU7VoHgtraVbxF +QDh7JEHI9tqEEVOA5OvRrDRcyeEYBoTDgh76ROco2lR+/9uCvGtHVrMCtG7BP7ZB +sSVNAr1IIRZZqKLv2skKT/7mzZR2ivcw9UeBBTUf8xsfiYVBvMGoEsXEycjYdf6w +WV+7XS7teNOc9UgsFNN+9AhIBc1jvee5E//72/4F8pAttAg/+mmPUyIKtekNJ4gj +OAR2VAzGx1ybzWPwIgOudZFHXFduxvq4f1hIRPH0KbQ/gkRrAgMBAAGjZjBkMA4G +A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTkvpCD +6C43rar9TtJoXr7q8dkrrjAfBgNVHSMEGDAWgBStoQwVpbGx87fxB3dEGDqKKnBT +4TANBgkqhkiG9w0BAQsFAAOCAQEAJd9fOSkwB3uVdsS+puj6gCER8jqmhd3g/J5V +Zjk9cKS8H0e8pq/tMxeJ8kpurPAzUk5RkCspGt2l0BSwmf3ahr8aJRviMX6AuW3/ +g8aKplTvq/WMNGKLXONa3Sq8591J+ce8gtOX/1rDKmFI4wQ/gUzOSYiT991m7QKS +Fr6HMgFuz7RNJbb3Fy5cnurh8eYWA7mMv7laiLwTNsaro5qsqErD5uXuot6o9beT +a+GiKinEur35tNxAr47ax4IRubuIzyfCrezjfKc5raVV2NURJDyKP0m0CCaffAxE +qn2dNfYc3v1D8ypg3XjHlOzRo32RB04o8ALHMD9LSwsYDLpMag== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFzCCAv+gAwIBAgICFSUwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSgwJgYDVQQDDB9BbWF6b24gUkRTIFByZXZpZXcgUm9vdCAyMDE5IENBMB4XDTE5 +MDgyMTIyMzk0N1oXDTI0MDgyMTIyMjk0OVowgZwxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6 +b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMS0wKwYD +VQQDDCRBbWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0dB/U7qRnSf05wOi7m10Pa2uPMTJv +r6U/3Y17a5prq5Zr4++CnSUYarG51YuIf355dKs+7Lpzs782PIwCmLpzAHKWzix6 +pOaTQ+WZ0+vUMTxyqgqWbsBgSCyP7pVBiyqnmLC/L4az9XnscrbAX4pNaoJxsuQe +mzBo6yofjQaAzCX69DuqxFkVTRQnVy7LCFkVaZtjNAftnAHJjVgQw7lIhdGZp9q9 +IafRt2gteihYfpn+EAQ/t/E4MnhrYs4CPLfS7BaYXBycEKC5Muj1l4GijNNQ0Efo +xG8LSZz7SNgUvfVwiNTaqfLP3AtEAWiqxyMyh3VO+1HpCjT7uNBFtmF3AgMBAAGj +ZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW +BBQtinkdrj+0B2+qdXngV2tgHnPIujAfBgNVHSMEGDAWgBRp0xqULkNh/w2ZVzEI +o2RIY7O03TANBgkqhkiG9w0BAQsFAAOCAQEAtJdqbCxDeMc8VN1/RzCabw9BIL/z +73Auh8eFTww/sup26yn8NWUkfbckeDYr1BrXa+rPyLfHpg06kwR8rBKyrs5mHwJx +bvOzXD/5WTdgreB+2Fb7mXNvWhenYuji1MF+q1R2DXV3I05zWHteKX6Dajmx+Uuq +Yq78oaCBSV48hMxWlp8fm40ANCL1+gzQ122xweMFN09FmNYFhwuW+Ao+Vv90ZfQG +PYwTvN4n/gegw2TYcifGZC2PNX74q3DH03DXe5fvNgRW5plgz/7f+9mS+YHd5qa9 +tYTPUvoRbi169ou6jicsMKUKPORHWhiTpSCWR1FMMIbsAcsyrvtIsuaGCQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQdOCSuA9psBpQd8EI368/0DANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTgwNjI2WhgPMjA2MTA1MTkxOTA2MjZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgc2EtZWFzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6ftL6w8v3dB2yW +LjCxSP1D7ZsOTeLZOSCz1Zv0Gkd0XLhil5MdHOHBvwH/DrXqFU2oGzCRuAy+aZis +DardJU6ChyIQIciXCO37f0K23edhtpXuruTLLwUwzeEPdcnLPCX+sWEn9Y5FPnVm +pCd6J8edH2IfSGoa9LdErkpuESXdidLym/w0tWG/O2By4TabkNSmpdrCL00cqI+c +prA8Bx1jX8/9sY0gpAovtuFaRN+Ivg3PAnWuhqiSYyQ5nC2qDparOWuDiOhpY56E +EgmTvjwqMMjNtExfYx6Rv2Ndu50TriiNKEZBzEtkekwXInTupmYTvc7U83P/959V +UiQ+WSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4uYHdH0+ +bUeh81Eq2l5/RJbW+vswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBhxcExJ+w74bvDknrPZDRgTeMLYgbVJjx2ExH7/Ac5FZZWcpUpFwWMIJJxtewI +AnhryzM3tQYYd4CG9O+Iu0+h/VVfW7e4O3joWVkxNMb820kQSEwvZfA78aItGwOY +WSaFNVRyloVicZRNJSyb1UL9EiJ9ldhxm4LTT0ax+4ontI7zTx6n6h8Sr6r/UOvX +d9T5aUUENWeo6M9jGupHNn3BobtL7BZm2oS8wX8IVYj4tl0q5T89zDi2x0MxbsIV +5ZjwqBQ5JWKv7ASGPb+z286RjPA9R2knF4lJVZrYuNV90rHvI/ECyt/JrDqeljGL +BLl1W/UsvZo6ldLIpoMbbrb5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQUfVbqapkLYpUqcLajpTJWzANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA2MjMyMDA5WhgPMjA2MjA1MDcwMDIwMDlaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJIeovu3 +ewI9FVitXMQzvkh34aQ6WyI4NO3YepfJaePiv3cnyFGYHN2S1cR3UQcLWgypP5va +j6bfroqwGbCbZZcb+6cyOB4ceKO9Ws1UkcaGHnNDcy5gXR7aCW2OGTUfinUuhd2d +5bOGgV7JsPbpw0bwJ156+MwfOK40OLCWVbzy8B1kITs4RUPNa/ZJnvIbiMu9rdj4 +8y7GSFJLnKCjlOFUkNI5LcaYvI1+ybuNgphT3nuu5ZirvTswGakGUT/Q0J3dxP0J +pDfg5Sj/2G4gXiaM0LppVOoU5yEwVewhQ250l0eQAqSrwPqAkdTg9ng360zqCFPE +JPPcgI1tdGUgneECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +/2AJVxWdZxc8eJgdpbwpW7b0f7IwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQBYm63jTu2qYKJ94gKnqc+oUgqmb1mTXmgmp/lXDbxonjszJDOXFbri +3CCO7xB2sg9bd5YWY8sGKHaWmENj3FZpCmoefbUx++8D7Mny95Cz8R32rNcwsPTl +ebpd9A/Oaw5ug6M0x/cNr0qzF8Wk9Dx+nFEimp8RYQdKvLDfNFZHjPa1itnTiD8M +TorAqj+VwnUGHOYBsT/0NY12tnwXdD+ATWfpEHdOXV+kTMqFFwDyhfgRVNpTc+os +ygr8SwhnSCpJPB/EYl2S7r+tgAbJOkuwUvGT4pTqrzDQEhwE7swgepnHC87zhf6l +qN6mVpSnQKQLm6Ob5TeCEFgcyElsF5bH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAOxu0I1QuMAhIeszB3fJIlkwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjIwNjU5WhgPMjEyMTA1MjQyMzA2NTlaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEz4bylRcGqqDWdP7gQIIoTHdBK6FNtKH1 +4SkEIXRXkYDmRvL9Bci1MuGrwuvrka5TDj4b7e+csY0llEzHpKfq6nJPFljoYYP9 +uqHFkv77nOpJJ633KOr8IxmeHW5RXgrZo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQQikVz8wmjd9eDFRXzBIU8OseiGzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwf06Mcrpw1O0EBLBBrp84m37NYtOkE/0Z0O+C7D41wnXi +EQdn6PXUVgdD23Gj82SrAjEAklhKs+liO1PtN15yeZR1Io98nFve+lLptaLakZcH ++hfFuUtCqMbaI8CdvJlKnPqT +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALyWMTyCebLZOGcZZQmkmfcwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyODAzWhgPMjEyMTA1MjQyMTI4MDNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +wGFiyDyCrGqgdn4fXG12cxKAAfVvhMea1mw5h9CVRoavkPqhzQpAitSOuMB9DeiP +wQyqcsiGl/cTEau4L+AUBG8b9v26RlY48exUYBXj8CieYntOT9iNw5WtdYJa3kF/ +JxgI+HDMzE9cmHDs5DOO3S0uwZVyra/xE1ymfSlpOeUIOTpHRJv97CBUEpaZMUW5 +Sr6GruuOwFVpO5FX3A/jQlcS+UN4GjSRgDUJuqg6RRQldEZGCVCCmodbByvI2fGm +reGpsPJD54KkmAX08nOR8e5hkGoHxq0m2DLD4SrOFmt65vG47qnuwplWJjtk9B3Z +9wDoopwZLBOtlkPIkUllWm1P8EuHC1IKOA+wSP6XdT7cy8S77wgyHzR0ynxv7q/l +vlZtH30wnNqFI0y9FeogD0TGMCHcnGqfBSicJXPy9T4fU6f0r1HwqKwPp2GArwe7 +dnqLTj2D7M9MyVtFjEs6gfGWXmu1y5uDrf+CszurE8Cycoma+OfjjuVQgWOCy7Nd +jJswPxAroTzVfpgoxXza4ShUY10woZu0/J+HmNmqK7lh4NS75q1tz75in8uTZDkV +be7GK+SEusTrRgcf3tlgPjSTWG3veNzFDF2Vn1GLJXmuZfhdlVQDBNXW4MNREExS +dG57kJjICpT+r8X+si+5j51gRzkSnMYs7VHulpxfcwECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU4JWOpDBmUBuWKvGPZelw87ezhL8wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBRNLMql7itvXSEFQRAnyOjivHz +l5IlWVQjAbOUr6ogZcwvK6YpxNAFW5zQr8F+fdkiypLz1kk5irx9TIpff0BWC9hQ +/odMPO8Gxn8+COlSvc+dLsF2Dax3Hvz0zLeKMo+cYisJOzpdR/eKd0/AmFdkvQoM +AOK9n0yYvVJU2IrSgeJBiiCarpKSeAktEVQ4rvyacQGr+QAPkkjRwm+5LHZKK43W +nNnggRli9N/27qYtc5bgr3AaQEhEXMI4RxPRXCLsod0ehMGWyRRK728a+6PMMJAJ +WHOU0x7LCEMPP/bvpLj3BdvSGqNor4ZtyXEbwREry1uzsgODeRRns5acPwTM6ff+ +CmxO2NZ0OktIUSYRmf6H/ZFlZrIhV8uWaIwEJDz71qvj7buhQ+RFDZ9CNL64C0X6 +mf0zJGEpddjANHaaVky+F4gYMtEy2K2Lcm4JGTdyIzUoIe+atzCnRp0QeIcuWtF+ +s8AjDYCVFNypcMmqbRmNpITSnOoCHSRuVkY3gutVoYyMLbp8Jm9SJnCIlEWTA6Rm +wADOMGZJVn5/XRTRuetVOB3KlQDjs9OO01XN5NzGSZO2KT9ngAUfh9Eqhf1iRWSP +nZlRbQ2NRCuY/oJ5N59mLGxnNJSE7giEKEBRhTQ/XEPIUYAUPD5fca0arKRJwbol +l9Se1Hsq0ZU5f+OZKQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAK7vlRrGVEePJpW1VHMXdlIwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MTkxOTI4NDNaGA8yMTIxMDUxOTIwMjg0M1owgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMZiHOQC6x4o +eC7vVOMCGiN5EuLqPYHdceFPm4h5k/ZejXTf7kryk6aoKZKsDIYihkaZwXVS7Y/y +7Ig1F1ABi2jD+CYprj7WxXbhpysmN+CKG7YC3uE4jSvfvUnpzionkQbjJsRJcrPO +cZJM4FVaVp3mlHHtvnM+K3T+ni4a38nAd8xrv1na4+B8ZzZwWZXarfg8lJoGskSn +ou+3rbGQ0r+XlUP03zWujHoNlVK85qUIQvDfTB7n3O4s1XNGvkfv3GNBhYRWJYlB +4p8T+PFN8wG+UOByp1gV7BD64RnpuZ8V3dRAlO6YVAmINyG5UGrPzkIbLtErUNHO +4iSp4UqYvztDqJWWHR/rA84ef+I9RVwwZ8FQbjKq96OTnPrsr63A5mXTC9dXKtbw +XNJPQY//FEdyM3K8sqM0IdCzxCA1MXZ8+QapWVjwyTjUwFvL69HYky9H8eAER59K +5I7u/CWWeCy2R1SYUBINc3xxLr0CGGukcWPEZW2aPo5ibW5kepU1P/pzdMTaTfao +F42jSFXbc7gplLcSqUgWwzBnn35HLTbiZOFBPKf6vRRu8aRX9atgHw/EjCebi2xP +xIYr5Ub8u0QVHIqcnF1/hVzO/Xz0chj3E6VF/yTXnsakm+W1aM2QkZbFGpga+LMy +mFCtdPrELjea2CfxgibaJX1Q4rdEpc8DAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDSaycEyuspo/NOuzlzblui8KotFMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAbosemjeTRsL9o4v0KadBUNS3V7gdAH+X4vH2 +Ee1Jc91VOGLdd/s1L9UX6bhe37b9WjUD69ur657wDW0RzxMYgQdZ27SUl0tEgGGp +cCmVs1ky3zEN+Hwnhkz+OTmIg1ufq0W2hJgJiluAx2r1ib1GB+YI3Mo3rXSaBYUk +bgQuujYPctf0PA153RkeICE5GI3OaJ7u6j0caYEixBS3PDHt2MJWexITvXGwHWwc +CcrC05RIrTUNOJaetQw8smVKYOfRImEzLLPZ5kf/H3Cbj8BNAFNsa10wgvlPuGOW +XLXqzNXzrG4V3sjQU5YtisDMagwYaN3a6bBf1wFwFIHQoAPIgt8q5zaQ9WI+SBns +Il6rd4zfvjq/BPmt0uI7rVg/cgbaEg/JDL2neuM9CJAzmKxYxLQuHSX2i3Fy4Y1B +cnxnRQETCRZNPGd00ADyxPKVoYBC45/t+yVusArFt+2SVLEGiFBr23eG2CEZu+HS +nDEgIfQ4V3YOTUNa86wvbAss1gbbnT/v1XCnNGClEWCWNCSRjwV2ZmQ/IVTmNHPo +7axTTBBJbKJbKzFndCnuxnDXyytdYRgFU7Ly3sa27WS2KFyFEDebLFRHQEfoYqCu +IupSqBSbXsR3U10OTjc9z6EPo1nuV6bdz+gEDthmxKa1NI+Qb1kvyliXQHL2lfhr +5zT5+Bs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAOLV6zZcL4IV2xmEneN1GwswDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MDg1OFoYDzIxMjEwNTE5MjAwODU4WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7koAKGXXlLixN +fVjhuqvz0WxDeTQfhthPK60ekRpftkfE5QtnYGzeovaUAiS58MYVzqnnTACDwcJs +IGTFE6Wd7sB6r8eI/3CwI1pyJfxepubiQNVAQG0zJETOVkoYKe/5KnteKtnEER3X +tCBRdV/rfbxEDG9ZAsYfMl6zzhEWKF88G6xhs2+VZpDqwJNNALvQuzmTx8BNbl5W +RUWGq9CQ9GK9GPF570YPCuURW7kl35skofudE9bhURNz51pNoNtk2Z3aEeRx3ouT +ifFJlzh+xGJRHqBG7nt5NhX8xbg+vw4xHCeq1aAe6aVFJ3Uf9E2HzLB4SfIT9bRp +P7c9c0ySGt+3n+KLSHFf/iQ3E4nft75JdPjeSt0dnyChi1sEKDi0tnWGiXaIg+J+ +r1ZtcHiyYpCB7l29QYMAdD0TjfDwwPayLmq//c20cPmnSzw271VwqjUT0jYdrNAm +gV+JfW9t4ixtE3xF2jaUh/NzL3bAmN5v8+9k/aqPXlU1BgE3uPwMCjrfn7V0I7I1 +WLpHyd9jF3U/Ysci6H6i8YKgaPiOfySimQiDu1idmPld659qerutUSemQWmPD3bE +dcjZolmzS9U0Ujq/jDF1YayN3G3xvry1qWkTci0qMRMu2dZu30Herugh9vsdTYkf +00EqngPbqtIVLDrDjEQLqPcb8QvWFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBQBqg8Za/L0YMHURGExHfvPyfLbOTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBACAGPMa1QL7P/FIO7jEtMelJ0hQlQepKnGtbKz4r +Xq1bUX1jnLvnAieR9KZmeQVuKi3g3CDU6b0mDgygS+FL1KDDcGRCSPh238Ou8KcG +HIxtt3CMwMHMa9gmdcMlR5fJF9vhR0C56KM2zvyelUY51B/HJqHwGvWuexryXUKa +wq1/iK2/d9mNeOcjDvEIj0RCMI8dFQCJv3PRCTC36XS36Tzr6F47TcTw1c3mgKcs +xpcwt7ezrXMUunzHS4qWAA5OGdzhYlcv+P5GW7iAA7TDNrBF+3W4a/6s9v2nQAnX +UvXd9ul0ob71377UhZbJ6SOMY56+I9cJOOfF5QvaL83Sz29Ij1EKYw/s8TYdVqAq ++dCyQZBkMSnDFLVe3J1KH2SUSfm3O98jdPORQrUlORQVYCHPls19l2F6lCmU7ICK +hRt8EVSpXm4sAIA7zcnR2nU00UH8YmMQLnx5ok9YGhuh3Ehk6QlTQLJux6LYLskd +9YHOLGW/t6knVtV78DgPqDeEx/Wu/5A8R0q7HunpWxr8LCPBK6hksZnOoUhhb8IP +vl46Ve5Tv/FlkyYr1RTVjETmg7lb16a8J0At14iLtpZWmwmuv4agss/1iBVMXfFk ++ZGtx5vytWU5XJmsfKA51KLsMQnhrLxb3X3zC+JRCyJoyc8++F3YEcRi2pkRYE3q +Hing +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAI+asxQA/MB1cGyyrC0MPpkwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBjYS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIzMDkxMzIwMjEzNFoYDzIwNjMwOTEzMjEyMTMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMHvQITTZcfl2O +yfzRIAPKwzzlc8eXWdXef7VUsbezg3lm9RC+vArO4JuAzta/aLw1D94wPSRm9JXX +NkP3obO6Ql80/0doooU6BAPceD0xmEWC4aCFT/5KWsD6Sy2/Rjwq3NKBTwzxLwYK +GqVsBp8AdrzDTmdRETC+Dg2czEo32mTDAA1uMgqrz6xxeTYroj8NTSTp6jfE6C0n +YgzYmVQCEIjHqI49j7k3jfT3P2skCVKGJwQzoZnerFacKzXsDB18uIqU7NaMc2cX +kOd0gRqpyKOzAHU2m5/S4jw4UHdkoI3E7nkayuen8ZPKH2YqWtTXUrXGhSTT34nX +yiFgu+vTAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHzz1NTd +TOm9zAv4d8l6XCFKSdJfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAodBvd0cvXQYhFBef2evnuI9XA+AC/Q9P1nYtbp5MPA4aFhy5v9rjW8wwJX14 +l+ltd2o3tz8PFDBZ1NX2ooiWVlZthQxKn1/xDVKsTXHbYUXItPQ3jI5IscB5IML8 +oCzAbkoLXsSPNOVFP5P4l4cZEMqHGRnBag7hLJZvmvzZSBnz+ioC2jpjVluF8kDX +fQGNjqPECik68CqbSV0SaQ0cgEoYTDjwON5ZLBeS8sxR2abE/gsj4VFYl5w/uEBd +w3Tt9uGfIy+wd2tNj6isGC6PcbPMjA31jd+ifs2yNzigqkcYTTWFtnvh4a8xiecm +GHu2EgH0Jqzz500N7L3uQdPkdg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRANxgyBbnxgTEOpDul2ZnC0UwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNjEwMTgxOTA3WhgPMjA2MTA2MTAxOTE5MDda +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xnwSDAChrMkfk5TA4Dk8hKzStDlSlONzmd3fTG0Wqr5+x3EmFT6Ksiu/WIwEl9J2 +K98UI7vYyuZfCxUKb1iMPeBdVGqk0zb92GpURd+Iz/+K1ps9ZLeGBkzR8mBmAi1S +OfpwKiTBzIv6E8twhEn4IUpHsdcuX/2Y78uESpJyM8O5CpkG0JaV9FNEbDkJeBUQ +Ao2qqNcH4R0Qcr5pyeqA9Zto1RswgL06BQMI9dTpfwSP5VvkvcNUaLl7Zv5WzLQE +JzORWePvdPzzvWEkY/3FPjxBypuYwssKaERW0fkPDmPtykktP9W/oJolKUFI6pXp +y+Y6p6/AVdnQD2zZjW5FhQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBT+jEKs96LC+/X4BZkUYUkzPfXdqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIGQqgqcQ6XSGkmNebzR6DhadTbfDmbYeN5N0Vuzv+Tdmufb +tMGjdjnYMg4B+IVnTKQb+Ox3pL9gbX6KglGK8HupobmIRtwKVth+gYYz3m0SL/Nk +haWPYzOm0x3tJm8jSdufJcEob4/ATce9JwseLl76pSWdl5A4lLjnhPPKudUDfH+1 +BLNUi3lxpp6GkC8aWUPtupnhZuXddolTLOuA3GwTZySI44NfaFRm+o83N1jp+EwD +6e94M4cTRzjUv6J3MZmSbdtQP/Tk1uz2K4bQZGP0PZC3bVpqiesdE/xr+wbu8uHr +cM1JXH0AmXf1yIkTgyWzmvt0k1/vgcw5ixAqvvE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIRAMhw98EQU18mIji+unM2YH8wDQYJKoZIhvcNAQELBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQyMjJaGA8yMDYyMDYwNjIyNDIyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeeRoLfTm+7 +vqm7ZlFSx+1/CGYHyYrOOryM4/Z3dqYVHFMgWTR7V3ziO8RZ6yUanrRcWVX3PZbF +AfX0KFE8OgLsXEZIX8odSrq86+/Th5eZOchB2fDBsUB7GuN2rvFBbM8lTI9ivVOU +lbuTnYyb55nOXN7TpmH2bK+z5c1y9RVC5iQsNAl6IJNvSN8VCqXh31eK5MlKB4DT ++Y3OivCrSGsjM+UR59uZmwuFB1h+icE+U0p9Ct3Mjq3MzSX5tQb6ElTNGlfmyGpW +Kh7GQ5XU1KaKNZXoJ37H53woNSlq56bpVrKI4uv7ATpdpFubOnSLtpsKlpLdR3sy +Ws245200pC8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUp0ki +6+eWvsnBjQhMxwMW5pwn7DgwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQB2V8lv0aqbYQpj/bmVv/83QfE4vOxKCJAHv7DQ35cJsTyBdF+8pBczzi3t +3VNL5IUgW6WkyuUOWnE0eqAFOUVj0yTS1jSAtfl3vOOzGJZmWBbqm9BKEdu1D8O6 +sB8bnomwiab2tNDHPmUslpdDqdabbkWwNWzLJ97oGFZ7KNODMEPXWKWNxg33iHfS +/nlmnrTVI3XgaNK9qLZiUrxu9Yz5gxi/1K+sG9/Dajd32ZxjRwDipOLiZbiXQrsd +qzIMY4GcWf3g1gHL5mCTfk7dG22h/rhPyGV0svaDnsb+hOt6sv1McMN6Y3Ou0mtM +/UaAXojREmJmTSCNvs2aBny3/2sy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMnRxsKLYscJV8Qv5pWbL7swCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTgxNjAxWhgPMjEyMTA1MTkxOTE2MDFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgc2EtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjFOCZgTNVKxLKhUxffiDEvTLFhrmIqdO +dKqVdgDoELEzIHWDdC+19aDPitbCYtBVHl65ITu/9pn6mMUl5hhUNtfZuc6A+Iw1 +sBe0v0qI3y9Q9HdQYrGgeHDh8M5P7E2ho0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBS5L7/8M0TzoBZk39Ps7BkfTB4yJTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwI43O0NtWKTgnVv9z0LO5UMZYgSve7GvGTwqktZYCMObE +rUI4QerXM9D6JwLy09mqAjEAypfkdLyVWtaElVDUyHFkihAS1I1oUxaaDrynLNQK +Ou/Ay+ns+J+GyvyDUjBpVVW1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQR71Z8lTO5Sj+as2jB7IWXzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjIwMzIwWhgPMjEyMTA1MjQyMzAzMjBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM977bHIs1WJijrS +XQMfUOhmlJjr2v0K0UjPl52sE1TJ76H8umo1yR4T7Whkd9IwBHNGKXCJtJmMr9zp +fB38eLTu+5ydUAXdFuZpRMKBWwPVe37AdJRKqn5beS8HQjd3JXAgGKUNNuE92iqF +qi2fIqFMpnJXWo0FIW6s2Dl2zkORd7tH0DygcRi7lgVxCsw1BJQhFJon3y+IV8/F +bnbUXSNSDUnDW2EhvWSD8L+t4eiXYsozhDAzhBvojpxhPH9OB7vqFYw5qxFx+G0t +lSLX5iWi1jzzc3XyGnB6WInZDVbvnvJ4BGZ+dTRpOCvsoMIn9bz4EQTvu243c7aU +HbS/kvnCASNt+zk7C6lbmaq0AGNztwNj85Opn2enFciWZVnnJ/4OeefUWQxD0EPp +SjEd9Cn2IHzkBZrHCg+lWZJQBKbUVS0lLIMSsLQQ6WvR38jY7D2nxM1A93xWxwpt +ZtQnYRCVXH6zt2OwDAFePInWwxUjR5t/wu3XxPgpSfrmTi3WYtr1wFypAJ811e/P +yBtswWUQ6BNJQvy+KnOEeGfOwmtdDFYR+GOCfvCihzrKJrxOtHIieehR5Iw3cbXG +sm4pDzfMUVvDDz6C2M6PRlJhhClbatHCjik9hxFYEsAlqtVVK9pxaz9i8hOqSFQq +kJSQsgWw+oM/B2CyjcSqkSQEu8RLAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFPmrdxpRRgu3IcaB5BTqlprcKdTsMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAVdlxWjPvVKky3kn8ZizeM4D+EsLw9dWLau2UD/ls +zwDCFoT6euagVeCknrn+YEl7g20CRYT9iaonGoMUPuMR/cdtPL1W/Rf40PSrGf9q +QuxavWiHLEXOQTCtCaVZMokkvjuuLNDXyZnstgECuiZECTwhexUF4oiuhyGk9o01 +QMaiz4HX4lgk0ozALUvEzaNd9gWEwD2qe+rq9cQMTVq3IArUkvTIftZUaVUMzr0O +ed1+zAsNa9nJhURJ/6anJPJjbQgb5qA1asFcp9UaMT1ku36U3gnR1T/BdgG2jX3X +Um0UcaGNVPrH1ukInWW743pxWQb7/2sumEEMVh+jWbB18SAyLI4WIh4lkurdifzS +IuTFp8TEx+MouISFhz/vJDWZ84tqoLVjkEcP6oDypq9lFoEzHDJv3V1CYcIgOusT +k1jm9P7BXdTG7TYzUaTb9USb6bkqkD9EwJAOSs7DI94aE6rsSws2yAHavjAMfuMZ +sDAZvkqS2Qg2Z2+CI6wUZn7mzkJXbZoqRjDvChDXEB1mIhzVXhiNW/CR5WKVDvlj +9v1sdGByh2pbxcLQtVaq/5coM4ANgphoNz3pOYUPWHS+JUrIivBZ+JobjXcxr3SN +9iDzcu5/FVVNbq7+KN/nvPMngT+gduEN5m+EBjm8GukJymFG0m6BENRA0QSDqZ7k +zDY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAK5EYG3iHserxMqgg+0EFjgwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyMzE2WhgPMjA2MTA1MjQyMTIzMTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +s1L6TtB84LGraLHVC+rGPhLBW2P0oN/91Rq3AnYwqDOuTom7agANwEjvLq7dSRG/ +sIfZsSV/ABTgArZ5sCmLjHFZAo8Kd45yA9byx20RcYtAG8IZl+q1Cri+s0XefzyO +U6mlfXZkVe6lzjlfXBkrlE/+5ifVbJK4dqOS1t9cWIpgKqv5fbE6Qbq4LVT+5/WM +Vd2BOljuBMGMzdZubqFKFq4mzTuIYfnBm7SmHlZfTdfBYPP1ScNuhpjuzw4n3NCR +EdU6dQv04Q6th4r7eiOCwbWI9LkmVbvBe3ylhH63lApC7MiiPYLlB13xBubVHVhV +q1NHoNTi+zA3MN9HWicRxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSuxoqm0/wjNiZLvqv+JlQwsDvTPDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAFfTK/j5kv90uIbM8VaFdVbr/6weKTwehafT0pAk1bfLVX+7 +uf8oHgYiyKTTl0DFQicXejghXTeyzwoEkWSR8c6XkhD5vYG3oESqmt/RGvvoxz11 +rHHy7yHYu7RIUc3VQG60c4qxXv/1mWySGwVwJrnuyNT9KZXPevu3jVaWOVHEILaK +HvzQ2YEcWBPmde/zEseO2QeeGF8FL45Q1d66wqIP4nNUd2pCjeTS5SpB0MMx7yi9 +ki1OH1pv8tOuIdimtZ7wkdB8+JSZoaJ81b8sRrydRwJyvB88rftuI3YB4WwGuONT +ZezUPsmaoK69B0RChB0ofDpAaviF9V3xOWvVZfo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGDzCCA/egAwIBAgIRAI0sMNG2XhaBMRN3zD7ZyoEwDQYJKoZIhvcNAQEMBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA1NzUwWhgPMjEyMTA1MTgyMTU3 +NTBaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAh/otSiCu4Uw3hu7OJm0PKgLsLRqBmUS6jihcrkxfN2SHmp2zuRflkweU +BhMkebzL+xnNvC8okzbgPWtUxSmDnIRhE8J7bvSKFlqs/tmEdiI/LMqe/YIKcdsI +20UYmvyLIjtDaJIh598SHHlF9P8DB5jD8snJfhxWY+9AZRN+YVTltgQAAgayxkWp +M1BbvxpOnz4CC00rE0eqkguXIUSuobb1vKqdKIenlYBNxm2AmtgvQfpsBIQ0SB+8 +8Zip8Ef5rtjSw5J3s2Rq0aYvZPfCVIsKYepIboVwXtD7E9J31UkB5onLBQlaHaA6 +XlH4srsMmrew5d2XejQGy/lGZ1nVWNsKO0x/Az2QzY5Kjd6AlXZ8kq6H68hscA5i +OMbNlXzeEQsZH0YkId3+UsEns35AAjZv4qfFoLOu8vDotWhgVNT5DfdbIWZW3ZL8 +qbmra3JnCHuaTwXMnc25QeKgVq7/rG00YB69tCIDwcf1P+tFJWxvaGtV0g2NthtB +a+Xo09eC0L53gfZZ3hZw1pa3SIF5dIZ6RFRUQ+lFOux3Q/I3u+rYstYw7Zxc4Zeo +Y8JiedpQXEAnbw2ECHix/L6mVWgiWCiDzBnNLLdbmXjJRnafNSndSfFtHCnY1SiP +aCrNpzwZIJejoV1zDlWAMO+gyS28EqzuIq3WJK/TFE7acHkdKIcCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUrmV1YASnuudfmqAZP4sKGTvScaEw +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBGpEKeQoPvE85tN/25 +qHFkys9oHDl93DZ62EnOqAUKLd6v0JpCyEiop4nlrJe+4KrBYVBPyKOJDcIqE2Sp +3cvgJXLhY4i46VM3Qxe8yuYF1ElqBpg3jJVj/sCQnYz9dwoAMWIJFaDWOvmU2E7M +MRaKx+sPXFkIjiDA6Bv0m+VHef7aedSYIY7IDltEQHuXoqNacGrYo3I50R+fZs88 +/mB3e/V7967e99D6565yf9Lcjw4oQf2Hy7kl/6P9AuMz0LODnGITwh2TKk/Zo3RU +Vgq25RDrT4xJK6nFHyjUF6+4cOBxVpimmFw/VP1zaXT8DN5r4HyJ9p4YuSK8ha5N +2pJc/exvU8Nv2+vS/efcDZWyuEdZ7eh1IJWQZlOZKIAONfRDRTpeQHJ3zzv3QVYy +t78pYp/eWBHyVIfEE8p2lFKD4279WYe+Uvdb8c4Jm4TJwqkSJV8ifID7Ub80Lsir +lPAU3OCVTBeVRFPXT2zpC4PB4W6KBSuj6OOcEu2y/HgWcoi7Cnjvp0vFTUhDFdus +Wz3ucmJjfVsrkEO6avDKu4SwdbVHsk30TVAwPd6srIdi9U6MOeOQSOSE4EsrrS7l +SVmu2QIDUVFpm8QAHYplkyWIyGkupyl3ashH9mokQhixIU/Pzir0byePxHLHrwLu +1axqeKpI0F5SBUPsaVNYY2uNFg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQCREfzzVyDTMcNME+gWnTCTANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQyMzNaGA8yMDYxMDUyNDIxNDIzM1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +1MT6br3L/4Pq87DPXtcjlXN3cnbNk2YqRAZHJayStTz8VtsFcGPJOpk14geRVeVk +e9uKFHRbcyr/RM4owrJTj5X4qcEuATYZbo6ou/rW2kYzuWFZpFp7lqm0vasV4Z9F +fChlhwkNks0UbM3G+psCSMNSoF19ERunj7w2c4E62LwujkeYLvKGNepjnaH10TJL +2krpERd+ZQ4jIpObtRcMH++bTrvklc+ei8W9lqrVOJL+89v2piN3Ecdd389uphst +qQdb1BBVXbhUrtuGHgVf7zKqN1SkCoktoWxVuOprVWhSvr7akaWeq0UmlvbEsujU +vADqxGMcJFyCzxx3CkJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFFk8UJmlhoxFT3PP12PvhvazHjT4MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAfFtr2lGoWVXmWAsIo2NYre7kzL8Xb9Tx7desKxCCz5HOOvIr +8JMB1YK6A7IOvQsLJQ/f1UnKRh3X3mJZjKIywfrMSh0FiDf+rjcEzXxw2dGtUem4 +A+WMvIA3jwxnJ90OQj5rQ8bg3iPtE6eojzo9vWQGw/Vu48Dtw1DJo9210Lq/6hze +hPhNkFh8fMXNT7Q1Wz/TJqJElyAQGNOXhyGpHKeb0jHMMhsy5UNoW5hLeMS5ffao +TBFWEJ1gVfxIU9QRxSh+62m46JIg+dwDlWv8Aww14KgepspRbMqDuaM2cinoejv6 +t3dyOyHHrsOyv3ffZUKtQhQbQr+sUcL89lARsg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAIJLTMpzGNxqHZ4t+c1MlCIwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIxMzAzM1oYDzIwNjEwNTI1MjIzMDMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtdHut0ZhJ9Nn2 +MpVafFcwHdoEzx06okmmhjJsNy4l9QYVeh0UUoek0SufRNMRF4d5ibzpgZol0Y92 +/qKWNe0jNxhEj6sXyHsHPeYtNBPuDMzThfbvsLK8z7pBP7vVyGPGuppqW/6m4ZBB +lcc9fsf7xpZ689iSgoyjiT6J5wlVgmCx8hFYc/uvcRtfd8jAHvheug7QJ3zZmIye +V4htOW+fRVWnBjf40Q+7uTv790UAqs0Zboj4Yil+hER0ibG62y1g71XcCyvcVpto +2/XW7Y9NCgMNqQ7fGN3wR1gjtSYPd7DO32LTzYhutyvfbpAZjsAHnoObmoljcgXI +QjfBcCFpAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJI3aWLg +CS5xqU5WYVaeT5s8lpO0MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAUwATpJOcGVOs3hZAgJwznWOoTzOVJKfrqBum7lvkVH1vBwxBl9CahaKj3ZOt +YYp2qJzhDUWludL164DL4ZjS6eRedLRviyy5cRy0581l1MxPWTThs27z+lCC14RL +PJZNVYYdl7Jy9Q5NsQ0RBINUKYlRY6OqGDySWyuMPgno2GPbE8aynMdKP+f6G/uE +YHOf08gFDqTsbyfa70ztgVEJaRooVf5JJq4UQtpDvVswW2reT96qi6tXPKHN5qp3 +3wI0I1Mp4ePmiBKku2dwYzPfrJK/pQlvu0Gu5lKOQ65QdotwLAAoaFqrf9za1yYs +INUkHLWIxDds+4OHNYcerGp5Dw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIO6ldra1KZvNWJ0TA1ihXEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjE0NTA1WhgPMjEyMTA1MjEyMjQ1MDVa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +sDN52Si9pFSyZ1ruh3xAN0nVqEs960o2IK5CPu/ZfshFmzAwnx/MM8EHt/jMeZtj +SM58LADAsNDL01ELpFZATjgZQ6xNAyXRXE7RiTRUvNkK7O3o2qAGbLnJq/UqF7Sw +LRnB8V6hYOv+2EjVnohtGCn9SUFGZtYDjWXsLd4ML4Zpxv0a5LK7oEC7AHzbUR7R +jsjkrXqSv7GE7bvhSOhMkmgxgj1F3J0b0jdQdtyyj109aO0ATUmIvf+Bzadg5AI2 +A9UA+TUcGeebhpHu8AP1Hf56XIlzPpaQv3ZJ4vzoLaVNUC7XKzAl1dlvCl7Klg/C +84qmbD/tjZ6GHtzpLKgg7kQEV7mRoXq8X4wDX2AFPPQl2fv+Kbe+JODqm5ZjGegm +uskABBi8IFv1hYx9jEulZPxC6uD/09W2+niFm3pirnlWS83BwVDTUBzF+CooUIMT +jhWkIIZGDDgMJTzouBHfoSJtS1KpUZi99m2WyVs21MNKHeWAbs+zmI6TO5iiMC+T +uB8spaOiHFO1573Fmeer4sy3YA6qVoqVl6jjTQqOdy3frAMbCkwH22/crV8YA+08 +hLeHXrMK+6XUvU+EtHAM3VzcrLbuYJUI2XJbzTj5g0Eb8I8JWsHvWHR5K7Z7gceR +78AzxQmoGEfV6KABNWKsgoCQnfb1BidDJIe3BsI0A6UCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUABp0MlB14MSHgAcuNSOhs3MOlUcwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCv4CIOBSQi/QR9NxdRgVAG/pAh +tFJhV7OWb/wqwsNKFDtg6tTxwaahdCfWpGWId15OUe7G9LoPiKiwM9C92n0ZeHRz +4ewbrQVo7Eu1JI1wf0rnZJISL72hVYKmlvaWaacHhWxvsbKLrB7vt6Cknxa+S993 +Kf8i2Psw8j5886gaxhiUtzMTBwoDWak8ZaK7m3Y6C6hXQk08+3pnIornVSFJ9dlS +PAqt5UPwWmrEfF+0uIDORlT+cvrAwgSp7nUF1q8iasledycZ/BxFgQqzNwnkBDwQ +Z/aM52ArGsTzfMhkZRz9HIEhz1/0mJw8gZtDVQroD8778h8zsx2SrIz7eWQ6uWsD +QEeSWXpcheiUtEfzkDImjr2DLbwbA23c9LoexUD10nwohhoiQQg77LmvBVxeu7WU +E63JqaYUlOLOzEmNJp85zekIgR8UTkO7Gc+5BD7P4noYscI7pPOL5rP7YLg15ZFi +ega+G53NTckRXz4metsd8XFWloDjZJJq4FfD60VuxgXzoMNT9wpFTNSH42PR2s9L +I1vcl3w8yNccs9se2utM2nLsItZ3J0m/+QSRiw9hbrTYTcM9sXki0DtH2kyIOwYf +lOrGJDiYOIrXSQK36H0gQ+8omlrUTvUj4msvkXuQjlfgx6sgp2duOAfnGxE7uHnc +UhnJzzoe6M+LfGHkVQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQSAG6j2WHtWUUuLGJTPb1nTAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2MzgyNloYDzIxMjEwNTIwMTczODI2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2eqwU4FOzW8RV1W381Bd +olhDOrqoMqzWli21oDUt7y8OnXM/lmAuOS6sr8Nt61BLVbONdbr+jgCYw75KabrK +ZGg3siqvMOgabIKkKuXO14wtrGyGDt7dnKXg5ERGYOZlo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBS1Acp2WYxOcblv5ikZ3ZIbRCCW+zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAJL84J08PBprxmsAKPTotBuVI3MyW1r8 +xQ0i8lgCQUf8GcmYjQ0jI4oZyv+TuYJAcwIxAP9Xpzq0Docxb+4N1qVhpiOfWt1O +FnemFiy9m1l+wv6p3riQMPV7mBVpklmijkIv3Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRALZLcqCVIJ25maDPE3sbPCIwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjEzOTM5WhgPMjA2MTA1MjEyMjM5Mzla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ypKc+6FfGx6Gl6fQ78WYS29QoKgQiur58oxR3zltWeg5fqh9Z85K5S3UbRSTqWWu +Xcfnkz0/FS07qHX+nWAGU27JiQb4YYqhjZNOAq8q0+ptFHJ6V7lyOqXBq5xOzO8f ++0DlbJSsy7GEtJp7d7QCM3M5KVY9dENVZUKeJwa8PC5StvwPx4jcLeZRJC2rAVDG +SW7NAInbATvr9ssSh03JqjXb+HDyywiqoQ7EVLtmtXWimX+0b3/2vhqcH5jgcKC9 +IGFydrjPbv4kwMrKnm6XlPZ9L0/3FMzanXPGd64LQVy51SI4d5Xymn0Mw2kMX8s6 +Nf05OsWcDzJ1n6/Q1qHSxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRmaIc8eNwGP7i6P7AJrNQuK6OpFzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIBeHfGwz3S2zwIUIpqEEI5/sMySDeS+3nJR+woWAHeO0C8i +BJdDh+kzzkP0JkWpr/4NWz84/IdYo1lqASd1Kopz9aT1+iROXaWr43CtbzjXb7/X +Zv7eZZFC8/lS5SROq42pPWl4ekbR0w8XGQElmHYcWS41LBfKeHCUwv83ATF0XQ6I +4t+9YSqZHzj4vvedrvcRInzmwWJaal9s7Z6GuwTGmnMsN3LkhZ+/GD6oW3pU/Pyh +EtWqffjsLhfcdCs3gG8x9BbkcJPH5aPAVkPn4wc8wuXg6xxb9YGsQuY930GWTYRf +schbgjsuqznW4HHakq4WNhs1UdTSTKkRdZz7FUQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIRAM2zAbhyckaqRim63b+Tib8wDQYJKoZIhvcNAQELBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA0OTQ1WhgPMjA2MTA1MTgyMTQ5 +NDVaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA1ybjQMH1MkbvfKsWJaCTXeCSN1SG5UYid+Twe+TjuSqaXWonyp4WRR5z +tlkqq+L2MWUeQQAX3S17ivo/t84mpZ3Rla0cx39SJtP3BiA2BwfUKRjhPwOjmk7j +3zrcJjV5k1vSeLNOfFFSlwyDiVyLAE61lO6onBx+cRjelu0egMGq6WyFVidTdCmT +Q9Zw3W6LTrnPvPmEyjHy2yCHzH3E50KSd/5k4MliV4QTujnxYexI2eR8F8YQC4m3 +DYjXt/MicbqA366SOoJA50JbgpuVv62+LSBu56FpzY12wubmDZsdn4lsfYKiWxUy +uc83a2fRXsJZ1d3whxrl20VFtLFHFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRC0ytKmDYbfz0Bz0Psd4lRQV3aNTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggEBAGv8qZu4uaeoF6zsbumauz6ea6tdcWt+hGFuwGrb +tRbI85ucAmVSX06x59DJClsb4MPhL1XmqO3RxVMIVVfRwRHWOsZQPnXm8OYQ2sny +rYuFln1COOz1U/KflZjgJmxbn8x4lYiTPZRLarG0V/OsCmnLkQLPtEl/spMu8Un7 +r3K8SkbWN80gg17Q8EV5mnFwycUx9xsTAaFItuG0en9bGsMgMmy+ZsDmTRbL+lcX +Fq8r4LT4QjrFz0shrzCwuuM4GmcYtBSxlacl+HxYEtAs5k10tmzRf6OYlY33tGf6 +1tkYvKryxDPF/EDgGp/LiBwx6ixYMBfISoYASt4V/ylAlHA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtTCCAjqgAwIBAgIRAK9BSZU6nIe6jqfODmuVctYwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjIxMzA5WhgPMjEyMTA1MjEyMzEzMDlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgY2EtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUkEERcgxneT5H+P+fERcbGmf +bVx+M7rNWtgWUr6w+OBENebQA9ozTkeSg4c4M+qdYSObFqjxITdYxT1z/nHz1gyx +OKAhLjWu+nkbRefqy3RwXaWT680uUaAP6ccnkZOMo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSN6fxlg0s5Wny08uRBYZcQ3TUoyzAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaQAwZgIxAORaz+MBVoFBTmZ93j2G2vYTwA6T5hWzBWrx +CrI54pKn5g6At56DBrkjrwZF5T1enAIxAJe/LZ9xpDkAdxDgGJFN8gZYLRWc0NRy +Rb4hihy5vj9L+w9uKc9VfEBIFuhT7Z3ljg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQB/57HSuaqUkLaasdjxUdPjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE3NDAzNFoYDzIwNjEwNTE5MTg0MDM0WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbkaoVsUS76o +TgLFmcnaB8cswBk1M3Bf4IVRcwWT3a1HeJSnaJUqWHCJ+u3ip/zGVOYl0gN1MgBb +MuQRIJiB95zGVcIa6HZtx00VezDTr3jgGWRHmRjNVCCHGmxOZWvJjsIE1xavT/1j +QYV/ph4EZEIZ/qPq7e3rHohJaHDe23Z7QM9kbyqp2hANG2JtU/iUhCxqgqUHNozV +Zd0l5K6KnltZQoBhhekKgyiHqdTrH8fWajYl5seD71bs0Axowb+Oh0rwmrws3Db2 +Dh+oc2PwREnjHeca9/1C6J2vhY+V0LGaJmnnIuOANrslx2+bgMlyhf9j0Bv8AwSi +dSWsobOhNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQb7vJT +VciLN72yJGhaRKLn6Krn2TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAAxEj8N9GslReAQnNOBpGl8SLgCMTejQ6AW/bapQvzxrZrfVOZOYwp/5oV0f +9S1jcGysDM+DrmfUJNzWxq2Y586R94WtpH4UpJDGqZp+FuOVJL313te4609kopzO +lDdmd+8z61+0Au93wB1rMiEfnIMkOEyt7D2eTFJfJRKNmnPrd8RjimRDlFgcLWJA +3E8wca67Lz/G0eAeLhRHIXv429y8RRXDtKNNz0wA2RwURWIxyPjn1fHjA9SPDkeW +E1Bq7gZj+tBnrqz+ra3yjZ2blss6Ds3/uRY6NYqseFTZWmQWT7FolZEnT9vMUitW +I0VynUbShVpGf6946e0vgaaKw20= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQGyUVTaVjYJvWhroVEiHPpDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTkwNDA2WhgPMjA2MTA1MTkyMDA0MDZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhyXpJ0t4nigRDZ +EwNtFOem1rM1k8k5XmziHKDvDk831p7QsX9ZOxl/BT59Pu/P+6W6SvasIyKls1sW +FJIjFF+6xRQcpoE5L5evMgN/JXahpKGeQJPOX9UEXVW5B8yi+/dyUitFT7YK5LZA +MqWBN/LtHVPa8UmE88RCDLiKkqiv229tmwZtWT7nlMTTCqiAHMFcryZHx0pf9VPh +x/iPV8p2gBJnuPwcz7z1kRKNmJ8/cWaY+9w4q7AYlAMaq/rzEqDaN2XXevdpsYAK +TMMj2kji4x1oZO50+VPNfBl5ZgJc92qz1ocF95SAwMfOUsP8AIRZkf0CILJYlgzk +/6u6qZECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm5jfcS9o ++LwL517HpB6hG+PmpBswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAcQ6lsqxi63MtpGk9XK8mCxGRLCad51+MF6gcNz6i6PAqhPOoKCoFqdj4cEQTF +F8dCfa3pvfJhxV6RIh+t5FCk/y6bWT8Ls/fYKVo6FhHj57bcemWsw/Z0XnROdVfK +Yqbc7zvjCPmwPHEqYBhjU34NcY4UF9yPmlLOL8uO1JKXa3CAR0htIoW4Pbmo6sA4 +6P0co/clW+3zzsQ92yUCjYmRNeSbdXbPfz3K/RtFfZ8jMtriRGuO7KNxp8MqrUho +HK8O0mlSUxGXBZMNicfo7qY8FD21GIPH9w5fp5oiAl7lqFzt3E3sCLD3IiVJmxbf +fUwpGd1XZBBSdIxysRLM6j48 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQU+PAILXGkpoTcpF200VD/jAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMTQ1MTFaGA8yMTIxMDUyNTIyNDUxMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3tFKE8Kw1sGQAvNLlLhd8OcGhlc7MiW/s +NXm3pOiCT4vZpawKvHBzD76Kcv+ZZzHRxQEmG1/muDzZGlKR32h8AAj+NNO2Wy3d +CKTtYMiVF6Z2zjtuSkZQdjuQbe4eQ7qjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFAiSQOp16Vv0Ohpvqcbd2j5RmhYNMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjBVsi+5Ape0kOhMt/WFkANkslD4qXA5uqhrfAtH29Xzz2NV +tR7akiA771OaIGB/6xsCMQCZt2egCtbX7J0WkuZ2KivTh66jecJr5DHvAP4X2xtS +F/5pS+AUhcKTEGjI9jDH3ew= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQT5mGlavQzFHsB7hV6Mmy6TAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTAxNVoYDzIxMjEwNTI0MjE1MDE1WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcm4BBBjYK7clwm0HJRWS +flt3iYwoJbIXiXn9c1y3E+Vb7bmuyKhS4eO8mwO4GefUcXObRfoHY2TZLhMJLVBQ +7MN2xDc0RtZNj07BbGD3VAIFRTDX0mH9UNYd0JQM3t/Oo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRrd5ITedfAwrGo4FA9UaDaGFK3rjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPBNqmVv1IIA3EZyQ6XuVf4gj79/DMO8 +bkicNS1EcBpUqbSuU4Zwt2BYc8c/t7KVOQIxAOHoWkoKZPiKyCxfMtJpCZySUG+n +sXgB/LOyWE5BJcXUfm+T1ckeNoWeUUMOLmnJjg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJcDeinvdNrDQBeJ8+t38WQwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY0OTE2WhgPMjA2MjA1MjUxNzQ5MTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +k8DBNkr9tMoIM0NHoFiO7cQfSX0cOMhEuk/CHt0fFx95IBytx7GHCnNzpM27O5z6 +x6iRhfNnx+B6CrGyCzOjxvPizneY+h+9zfvNz9jj7L1I2uYMuiNyOKR6FkHR46CT +1CiArfVLLPaTqgD/rQjS0GL2sLHS/0dmYipzynnZcs613XT0rAWdYDYgxDq7r/Yi +Xge5AkWQFkMUq3nOYDLCyGGfQqWKkwv6lZUHLCDKf+Y0Uvsrj8YGCI1O8mF0qPCQ +lmlfaDvbuBu1AV+aabmkvyFj3b8KRIlNLEtQ4N8KGYR2Jdb82S4YUGIOAt4wuuFt +1B7AUDLk3V/u+HTWiwfoLQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSNpcjz6ArWBtAA+Gz6kyyZxrrgdDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGJEd7UgOzHYIcQRSF7nSYyjLROyalaIV9AX4WXW/Cqlul1c +MblP5etDZm7A/thliZIWAuyqv2bNicmS3xKvNy6/QYi1YgxZyy/qwJ3NdFl067W0 +t8nGo29B+EVK94IPjzFHWShuoktIgp+dmpijB7wkTIk8SmIoe9yuY4+hzgqk+bo4 +ms2SOXSN1DoQ75Xv+YmztbnZM8MuWhL1T7hA4AMorzTQLJ9Pof8SpSdMHeDsHp0R +01jogNFkwy25nw7cL62nufSuH2fPYGWXyNDg+y42wKsKWYXLRgUQuDVEJ2OmTFMB +T0Vf7VuNijfIA9hkN2d3K53m/9z5WjGPSdOjGhg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQRiwspKyrO0xoxDgSkqLZczANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjE1OTAwWhgPMjA2MTA1MjQyMjU5MDBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL53Jk3GsKiu+4bx +jDfsevWbwPCNJ3H08Zp7GWhvI3Tgi39opfHYv2ku2BKFjK8N2L6RvNPSR8yplv5j +Y0tK0U+XVNl8o0ibhqRDhbTuh6KL8CFINWYzAajuxFS+CF0U6c1Q3tXLBdALxA7l +FlXJ71QrP06W31kRe7kvgrvO7qWU3/OzUf9qYw4LSiR1/VkvvRCTqcVNw09clw/M +Jbw6FSgweN65M9j7zPbjGAXSHkXyxH1Erin2fa+B9PE4ZDgX9cp2C1DHewYJQL/g +SepwwcudVNRN1ibKH7kpMrgPnaNIVNx5sXVsTjk6q2ZqYw3SVHegltJpLy/cZReP +mlivF2kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmTcQd6o1 +CuS65MjBrMwQ9JJjmBwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAKSDSIzl956wVddPThf2VAzI8syw9ngSwsEHZvxVGHBvu5gg618rDyguVCYX9L +4Kw/xJrk6S3qxOS2ZDyBcOpsrBskgahDFIunzoRP3a18ARQVq55LVgfwSDQiunch +Bd05cnFGLoiLkR5rrkgYaP2ftn3gRBRaf0y0S3JXZ2XB3sMZxGxavYq9mfiEcwB0 +LMTMQ1NYzahIeG6Jm3LqRqR8HkzP/Ztq4dT2AtSLvFebbNMiWqeqT7OcYp94HTYT +zqrtaVdUg9bwyAUCDgy0GV9RHDIdNAOInU/4LEETovrtuBU7Z1q4tcHXvN6Hd1H8 +gMb0mCG5I393qW5hFsA/diFb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAPQAvihfjBg/JDbj6U64K98wDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYyODQxWhgPMjA2MTA1MjAxNzI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +vJ9lgyksCxkBlY40qOzI1TCj/Q0FVGuPL/Z1Mw2YN0l+41BDv0FHApjTUkIKOeIP +nwDwpXTa3NjYbk3cOZ/fpH2rYJ++Fte6PNDGPgKppVCUh6x3jiVZ1L7wOgnTdK1Q +Trw8440IDS5eLykRHvz8OmwvYDl0iIrt832V0QyOlHTGt6ZJ/aTQKl12Fy3QBLv7 +stClPzvHTrgWqVU6uidSYoDtzHbU7Vda7YH0wD9IUoMBf7Tu0rqcE4uH47s2XYkc +SdLEoOg/Ngs7Y9B1y1GCyj3Ux7hnyvCoRTw014QyNB7dTatFMDvYlrRDGG14KeiU +UL7Vo/+EejWI31eXNLw84wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQkgTWFsNg6wA3HbbihDQ4vpt1E2zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGz1Asiw7hn5WYUj8RpOCzpE0h/oBZcnxP8wulzZ5Xd0YxWO +0jYUcUk3tTQy1QvoY+Q5aCjg6vFv+oFBAxkib/SmZzp4xLisZIGlzpJQuAgRkwWA +6BVMgRS+AaOMQ6wKPgz1x4v6T0cIELZEPq3piGxvvqkcLZKdCaeC3wCS6sxuafzZ +4qA3zMwWuLOzRftgX2hQto7d/2YkRXga7jSvQl3id/EI+xrYoH6zIWgjdU1AUaNq +NGT7DIo47vVMfnd9HFZNhREsd4GJE83I+JhTqIxiKPNxrKgESzyADmNPt0gXDnHo +tbV1pMZz5HpJtjnP/qVZhEK5oB0tqlKPv9yx074= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuTCCAj6gAwIBAgIRAKp1Rn3aL/g/6oiHVIXtCq8wCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMDMyMTdaGA8yMTIxMDUyNDIxMzIxN1owgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGTYWPILeBJXfcL3Dz4z +EWMUq78xB1HpjBwHoTURYfcMd5r96BTVG6yaUBWnAVCMeeD6yTG9a1eVGNhG14Hk +ZAEjgLiNB7RRbEG5JZ/XV7W/vODh09WCst2y9SLKsdgeAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUoE0qZHmDCDB+Bnm8GUa/evpfPwgwDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCnil5MMwhY3qoXv0xvcKZGxGPaBV15 +0CCssCKn0oVtdJQfJQ3Jrf3RSaEyijXIJsoCMQC35iJi4cWoNX3N/qfgnHohW52O +B5dg0DYMqy5cNZ40+UcAanRMyqNQ6P7fy3umGco= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQPXnDTPegvJrI98qz8WxrMjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxODIxNDAxMloYDzIxMjEwNTE4MjI0MDEyWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI0sR7gwutK5AB46hM761 +gcLTGBIYlURSEoM1jcBwy56CL+3CJKZwLLyJ7qoOKfWbu5GsVLUTWS8MV6Nw33cx +2KQD2svb694wi+Px2f4n9+XHkEFQw8BbiodDD7RZA70fo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTQSioOvnVLEMXwNSDg+zgln/vAkjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAMwu1hqm5Bc98uE/E0B5iMYbBQ4kpMxO +tP8FTfz5UR37HUn26nXE0puj6S/Ffj4oJgIwXI7s2c26tFQeqzq6u3lrNJHp5jC9 +Uxlo/hEJOLoDj5jnpxo8dMAtCNoQPaHdfL0P +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQEM1pS+bWfBJeu/6j1yIIFzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjMwOTE5MjIwMTM5WhgPMjEyMzA5MTkyMzAxMzlaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgY2Etd2VzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Pyp8p5z6HnlGB +daOj78gZ3ABufxnBFiu5NdFiGoMrS+eY//xxr2iKbnynJAzjmn5A6VKMNxtbuYIZ +WKAzDb/HrWlIYD2w7ZVBXpylfPhiz3jLNsl03WdPNnEruCcivhY2QMewEVtzjPU0 +ofdbZlO2KpF3biv1gjPuIuE7AUyQAbWnWTlrzETAVWLboJJRRqxASSkFUHNLXod7 +ow02FwlAhcnCp9gSe1SKRDrpvvEvYQBAFB7owfnoQzOGDdd87RGyYfyuW8aFI2Z0 +LHNvsA0dTafO4Rh986c72kDL7ijICQdr5OTgZR2OnuESLk1DSK4xYJ4fA6jb5dJ5 ++xsI6tCPykWCW98aO/pha35OsrVNifL/5cH5pdv/ecgQGdffJB+Vdj6f/ZMwR6s/ +Rm37cQ9l3tU8eu/qpzsFjLq1ZUzDaVDWgMW9t49+q/zjhdmbPOabZDao7nHXrVRw +rwPHWCmEY4OmH6ikEKQW3AChFjOdSg4me/J0Jr5l5jKggLPHWbNLRO8qTTK6N8qk +ui3aJDi+XQfsTPARXIw4UFErArNImTsoZVyqfX7I4shp0qZbEhP6kRAbfPljw5kW +Yat7ZlXqDanjsreqbLTaOU10P0rC0/4Ctv5cLSKCrzRLWtpXxhKa2wJTQ74G6fAZ +1oUA79qg3F8nyM+ZzDsfNI854+PNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLRWiDabEQZNkzEPUCr1ZVJV6xpwMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEATkVVzkkGBjEtLGDtERi+fSpIV0MxwAsA4PAeBBmb +myxo90jz6kWkKM1Wm4BkZM8/mq5VbxPef1kxHfb5CHksCL6SgG5KujfIvht+KT2a +MRJB+III3CbcTy0HtwCX5AlPIbXWydhQFoJTW/OkpecUWoyFM6SqYeYZx1itJpxl +sXshLjYOvw+QgvxRsDxqUfkcaC/N2yhu/30Zo2P8msJfAFry2UmA/TBrWOQKVQxl +Ee/yWgp4U/bC/GZnjWnWDTwkRFGQtI4wjxbVuX6V4FTLCT7kIoHBhG+zOSduJRn3 +Axej7gkEXEVc/PAnwp/kSJ/b0/JONLWdjGUFkyiMn1yJlhJ2sg39vepBN5r6yVYU +nJWoZAuupRpoIKfmC3/cZanXqYbYl4yxzX/PMB4kAACfdxGxLawjnnBjSzaWokXs +YVh2TjWpUMwLOi0RB2mtPUjHdDLKtjOTZ1zHZnR/wVp9BmVI1BXYnz5PAqU5XqeD +EmanyaAuFCeyol1EtbQhgtysThQ+vwYAXMm2iKzJxq0hik8wyG8X55FhnGEOGV3u +xxq7odd3/8BXkc3dGdBPQtH+k5glaQyPnAsLVAIUvyzTmy58saL+nJnQY4mmRrwV +1jJA7nnkaklI/L5fvfCg0W+TMinCOAGd+GQ4hK2SAsJLtcqiBgPf2wJHO8wiwUh9 +Luw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQGKVv+5VuzEZEBzJ+bVfx2zAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTc1MDU5WhgPMjEyMTA1MTkxODUwNTlaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMqdLJ0tZF/DGFZTKZDrGRJZID8ivC2I +JRCYTWweZKCKSCAzoiuGGHzJhr5RlLHQf/QgmFcgXsdmO2n3CggzhA4tOD9Ip7Lk +P05eHd2UPInyPCHRgmGjGb0Z+RdQ6zkitKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUC1yhRgVqU5bR8cGzOUCIxRpl4EYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMG0c/zLGECRPzGKJvYCkpFTCUvdP4J74YP0v/dPvKojL +t/BrR1Tg4xlfhaib7hPc7wIwFvgqHes20CubQnZmswbTKLUrgSUW4/lcKFpouFd2 +t2/ewfi/0VhkeUW+IiHhOMdU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOXxJuyXVkbfhZCkS/dOpfEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1OTEwWhgPMjEyMTA1MjUyMjU5MTBa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +xiP4RDYm4tIS12hGgn1csfO8onQDmK5SZDswUpl0HIKXOUVVWkHNlINkVxbdqpqH +FhbyZmNN6F/EWopotMDKe1B+NLrjNQf4zefv2vyKvPHJXhxoKmfyuTd5Wk8k1F7I +lNwLQzznB+ElhrLIDJl9Ro8t31YBBNFRGAGEnxyACFGcdkjlsa52UwfYrwreEg2l +gW5AzqHgjFfj9QRLydeU/n4bHm0F1adMsV7P3rVwilcUlqsENDwXnWyPEyv3sw6F +wNemLEs1129mB77fwvySb+lLNGsnzr8w4wdioZ74co+T9z2ca+eUiP+EQccVw1Is +D4Fh57IjPa6Wuc4mwiUYKkKY63+38aCfEWb0Qoi+zW+mE9nek6MOQ914cN12u5LX +dBoYopphRO5YmubSN4xcBy405nIdSdbrAVWwxXnVVyjqjknmNeqQsPZaxAhdoKhV +AqxNr8AUAdOAO6Sz3MslmcLlDXFihrEEOeUbpg/m1mSUUHGbu966ajTG1FuEHHwS +7WB52yxoJo/tHvt9nAWnh3uH5BHmS8zn6s6CGweWKbX5yICnZ1QFR1e4pogxX39v +XD6YcNOO+Vn+HY4nXmjgSYVC7l+eeP8eduMg1xJujzjrbmrXU+d+cBObgdTOAlpa +JFHaGwYw1osAwPCo9cZ2f04yitBfj9aPFia8ASKldakCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUqKS+ltlior0SyZKYAkJ/efv55towDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAdElvp8bW4B+Cv+1WSN87dg6TN +wGyIjJ14/QYURgyrZiYpUmZpj+/pJmprSWXu4KNyqHftmaidu7cdjL5nCAvAfnY5 +/6eDDbX4j8Gt9fb/6H9y0O0dn3mUPSEKG0crR+JRFAtPhn/2FNvst2P82yguWLv0 +pHjHVUVcq+HqDMtUIJsTPYjSh9Iy77Q6TOZKln9dyDOWJpCSkiUWQtMAKbCSlvzd +zTs/ahqpT+zLfGR1SR+T3snZHgQnbnemmz/XtlKl52NxccARwfcEEKaCRQyGq/pR +0PVZasyJS9JY4JfQs4YOdeOt4UMZ8BmW1+BQWGSkkb0QIRl8CszoKofucAlqdPcO +IT/ZaMVhI580LFGWiQIizWFskX6lqbCyHqJB3LDl8gJISB5vNTHOHpvpMOMs5PYt +cRl5Mrksx5MKMqG7y5R734nMlZxQIHjL5FOoOxTBp9KeWIL/Ib89T2QDaLw1SQ+w +ihqWBJ4ZdrIMWYpP3WqM+MXWk7WAem+xsFJdR+MDgOOuobVQTy5dGBlPks/6gpjm +rO9TjfQ36ppJ3b7LdKUPeRfnYmlR5RU4oyYJ//uLbClI443RZAgxaCXX/nyc12lr +eVLUMNF2abLX4/VF63m2/Z9ACgMRfqGshPssn1NN33OonrotQoj4S3N9ZrjvzKt8 +iHcaqd60QKpfiH2A3A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQPaVGRuu86nh/ylZVCLB0MzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMDMxNloYDzIxMjEwNTI1MjMwMzE2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEexNURoB9KE93MEtEAlJG +obz4LS/pD2hc8Gczix1WhVvpJ8bN5zCDXaKdnDMCebetyRQsmQ2LYlfmCwpZwSDu +0zowB11Pt3I5Avu2EEcuKTlKIDMBeZ1WWuOd3Tf7MEAMo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSaYbZPBvFLikSAjpa8mRJvyArMxzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOEJkuh3Zjb7Ih/zuNRd1RBqmIYcnyw0 +nwUZczKXry+9XebYj3VQxSRNadrarPWVqgIxAMg1dyGoDAYjY/L/9YElyMnvHltO +PwpJShmqHvCLc/mXMgjjYb/akK7yGthvW6j/uQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQChu3v5W1Doil3v6pgRIcVzANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MTgyMTM0MTVaGA8yMTIxMDUxODIyMzQxNVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1 +FUGQ5tf3OwpDR6hGBxhUcrkwKZhaXP+1St1lSOQvjG8wXT3RkKzRGMvb7Ee0kzqI +mzKKe4ASIhtV3UUWdlNmP0EA3XKnif6N79MismTeGkDj75Yzp5A6tSvqByCgxIjK +JqpJrch3Dszoyn8+XhwDxMZtkUa5nQVdJgPzJ6ltsQ8E4SWLyLtTu0S63jJDkqYY +S7cQblk7y7fel+Vn+LS5dGTdRRhMvSzEnb6mkVBaVzRyVX90FNUED06e8q+gU8Ob +htvQlf9/kRzHwRAdls2YBhH40ZeyhpUC7vdtPwlmIyvW5CZ/QiG0yglixnL6xahL +pbmTuTSA/Oqz4UGQZv2WzHe1lD2gRHhtFX2poQZeNQX8wO9IcUhrH5XurW/G9Xwl +Sat9CMPERQn4KC3HSkat4ir2xaEUrjfg6c4XsGyh2Pk/LZ0gLKum0dyWYpWP4JmM +RQNjrInXPbMhzQObozCyFT7jYegS/3cppdyy+K1K7434wzQGLU1gYXDKFnXwkX8R +bRKgx2pHNbH5lUddjnNt75+e8m83ygSq/ZNBUz2Ur6W2s0pl6aBjwaDES4VfWYlI +jokcmrGvJNDfQWygb1k00eF2bzNeNCHwgWsuo3HSxVgc/WGsbcGrTlDKfz+g3ich +bXUeUidPhRiv5UQIVCLIHpHuin3bj9lQO/0t6p+tAQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBSFmMBgm5IsRv3hLrvDPIhcPweXYTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAAa2EuozymOsQDJlEi7TqnyA2OhT +GXPfYqCyMJVkfrqNgcnsNpCAiNEiZbb+8sIPXnT8Ay8hrwJYEObJ5b7MHXpLuyft +z0Pu1oFLKnQxKjNxrIsCvaB4CRRdYjm1q7EqGhMGv76se9stOxkOqO9it31w/LoU +ENDk7GLsSqsV1OzYLhaH8t+MaNP6rZTSNuPrHwbV3CtBFl2TAZ7iKgKOhdFz1Hh9 +Pez0lG+oKi4mHZ7ajov6PD0W7njn5KqzCAkJR6OYmlNVPjir+c/vUtEs0j+owsMl +g7KE5g4ZpTRShyh5BjCFRK2tv0tkqafzNtxrKC5XNpEkqqVTCnLcKG+OplIEadtr +C7UWf4HyhCiR+xIyxFyR05p3uY/QQU/5uza7GlK0J+U1sBUytx7BZ+Fo8KQfPPqV +CqDCaYUksoJcnJE/KeoksyqNQys7sDGJhkd0NeUGDrFLKHSLhIwAMbEWnqGxvhli +E7sP2E5rI/I9Y9zTbLIiI8pfeZlFF8DBdoP/Hzg8pqsiE/yiXSFTKByDwKzGwNqz +F0VoFdIZcIbLdDbzlQitgGpJtvEL7HseB0WH7B2PMMD8KPJlYvPveO3/6OLzCsav ++CAkvk47NQViKMsUTKOA0JDCW+u981YRozxa3K081snhSiSe83zIPBz1ikldXxO9 +6YYLNPRrj3mi9T/f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMkvdFnVDb0mWWFiXqnKH68wCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkxMzI0WhgPMjEyMTA1MTkyMDEzMjRaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEy86DB+9th/0A5VcWqMSWDxIUblWTt/R0 +ao6Z2l3vf2YDF2wt1A2NIOGpfQ5+WAOJO/IQmnV9LhYo+kacB8sOnXdQa6biZZkR +IyouUfikVQAKWEJnh1Cuo5YMM4E2sUt5o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQ8u3OnecANmG8OoT7KLWDuFzZwBTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwQ817qkb7mWJFnieRAN+m9W3E0FLVKaV3zC5aYJUk2fcZ +TaUx3oLp3jPLGvY5+wgeAjEA6wAicAki4ZiDfxvAIuYiIe1OS/7H5RA++R8BH6qG +iRzUBM/FItFpnkus7u/eTkvo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQS/+Ryfgb/IOVEa1pWoe8oTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNjA2MjE1NDQyWhgPMjEyMjA2MDYyMjU0NDJaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDsX6fhdUWBQpYTdseBD/P3s96Dtw2Iw +OrXKNToCnmX5nMkUGdRn9qKNiz1pw3EPzaPxShbYwQ7LYP09ENK/JN4QQjxMihxC +jLFxS85nhBQQQGRCWikDAe38mD8fSvREQKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUIh1xZiseQYFjPYKJmGbruAgRH+AwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMFudS4zLy+UUGrtgNLtRMcu/DZ9BUzV4NdHxo0bkG44O +thnjl4+wTKI6VbyAbj2rkgIxAOHps8NMITU5DpyiMnKTxV8ubb/WGHrLl0BjB8Lw +ETVJk5DNuZvsIIcm7ykk6iL4Tw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQDcEmNIAVrDpUw5cH5ynutDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA3MDA0MDIzWhgPMjEyMjA1MDcwMTQwMjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvADk8t +Fl9bFlU5sajLPPDSOUpPAkKs6iPlz+27o1GJC88THcOvf3x0nVAcu9WYe9Qaas+4 +j4a0vv51agqyODRD/SNi2HnqW7DbtLPAm6KBHe4twl28ItB/JD5g7u1oPAHFoXMS +cH1CZEAs5RtlZGzJhcBXLFsHNv/7+SCLyZ7+2XFh9OrtgU4wMzkHoRNndhfwV5bu +17bPTwuH+VxH37zXf1mQ/KjhuJos0C9dL0FpjYBAuyZTAWhZKs8dpSe4DI544z4w +gkwUB4bC2nA1TBzsywEAHyNuZ/xRjNpWvx0ToWAA2iFJqC3VO3iKcnBplMvaUuMt +jwzVSNBnKcoabXCZL2XDLt4YTZR8FSwz05IvsmwcPB7uNTBXq3T9sjejW8QQK3vT +tzyfLq4jKmQE7PoS6cqYm+hEPm2hDaC/WP9bp3FdEJxZlPH26fq1b7BWYWhQ9pBA +Nv9zTnzdR1xohTyOJBUFQ81ybEzabqXqVXUIANqIOaNcTB09/sLJ7+zuMhp3mwBu +LtjfJv8PLuT1r63bU3seROhKA98b5KfzjvbvPSg3vws78JQyoYGbqNyDfyjVjg3U +v//AdVuPie6PNtdrW3upZY4Qti5IjP9e3kimaJ+KAtTgMRG56W0WxD3SP7+YGGbG +KhntDOkKsN39hLpn9UOafTIqFu7kIaueEy/NAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHAems86dTwdZbLe8AaPy3kfIUVoMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOBHpp0ICx81kmeoBcZTrMdJs2gnhcd85 +FoSCjXx9H5XE5rmN/lQcxxOgj8hr3uPuLdLHu+i6THAyzjrl2NA1FWiqpfeECGmy +0jm7iZsYORgGQYp/VKnDrwnKNSqlZvOuRr0kfUexwFlr34Y4VmupvEOK/RdGsd3S ++3hiemcHse9ST/sJLHx962AWMkN86UHPscJEe4+eT3f2Wyzg6La8ARwdWZSNS+WH +ZfybrncMmuiXuUdHv9XspPsqhKgtHhcYeXOGUtrwQPLe3+VJZ0LVxhlTWr9951GZ +GfmWwTV/9VsyKVaCFIXeQ6L+gjcKyEzYF8wpMtQlSc7FFqwgC4bKxvMBSaRy88Nr +lV2+tJD/fr8zGUeBK44Emon0HKDBWGX+/Hq1ZIv0Da0S+j6LbA4fusWxtGfuGha+ +luhHgVInCpALIOamiBEdGhILkoTtx7JrYppt3/Raqg9gUNCOOYlCvGhqX7DXeEfL +DGabooiY2FNWot6h04JE9nqGj5QqT8D6t/TL1nzxhRPzbcSDIHUd/b5R+a0bAA+7 +YTU6JqzEVCWKEIEynYmqikgLMGB/OzWsgyEL6822QW6hJAQ78XpbNeCzrICF4+GC +7KShLnwuWoWpAb26268lvOEvCTFM47VC6jNQl97md+2SA9Ma81C9wflid2M83Wle +cuLMVcQZceE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQAhAteLRCvizAElaWORFU2zANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE3MDkxNloYDzIwNjEwNTIwMTgwOTE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+qg7JAcOVKjh +N83SACnBFZPyB63EusfDr/0V9ZdL8lKcmZX9sv/CqoBo3N0EvBqHQqUUX6JvFb7F +XrMUZ740kr28gSRALfXTFgNODjXeDsCtEkKRTkac/UM8xXHn+hR7UFRPHS3e0GzI +iLiwQWDkr0Op74W8aM0CfaVKvh2bp4BI1jJbdDnQ9OKXpOxNHGUf0ZGb7TkNPkgI +b2CBAc8J5o3H9lfw4uiyvl6Fz5JoP+A+zPELAioYBXDrbE7wJeqQDJrETWqR9VEK +BXURCkVnHeaJy123MpAX2ozf4pqk0V0LOEOZRS29I+USF5DcWr7QIXR/w2I8ws1Q +7ys+qbE+kQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQFJ16n +1EcCMOIhoZs/F9sR+Jy++zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAOc5nXbT3XTDEZsxX2iD15YrQvmL5m13B3ImZWpx/pqmObsgx3/dg75rF2nQ +qS+Vl+f/HLh516pj2BPP/yWCq12TRYigGav8UH0qdT3CAClYy2o+zAzUJHm84oiB +ud+6pFVGkbqpsY+QMpJUbZWu52KViBpJMYsUEy+9cnPSFRVuRAHjYynSiLk2ZEjb +Wkdc4x0nOZR5tP0FgrX0Ve2KcjFwVQJVZLgOUqmFYQ/G0TIIGTNh9tcmR7yp+xJR +A2tbPV2Z6m9Yxx4E8lLEPNuoeouJ/GR4CkMEmF8cLwM310t174o3lKKUXJ4Vs2HO +Wj2uN6R9oI+jGLMSswTzCNV1vgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj6gAwIBAgIRAOocLeZWjYkG/EbHmscuy8gwCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjEyMTUwMDFaGA8yMTIxMDUyMTIyNTAwMVowgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCEr3jq1KtRncnZfK5cq +btY0nW6ZG3FMbh7XwBIR6Ca0f8llGZ4vJEC1pXgiM/4Dh045B9ZIzNrR54rYOIfa +2NcYZ7mk06DjIQML64hbAxbQzOAuNzLPx268MrlL2uW2XaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUln75pChychwN4RfHl+tOinMrfVowDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMGiyPINRU1mwZ4Crw01vpuPvxZxb2IOr +yX3RNlOIu4We1H+5dQk5tIvH8KGYFbWEpAIxAO9NZ6/j9osMhLgZ0yj0WVjb+uZx +YlZR9fyFisY/jNfX7QhSk+nrc3SFLRUNtpXrng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAKiaRZatN8eiz9p0s0lu0rQwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDIzNVoYDzIwNjEwNTIxMjMwMjM1WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCygVMf +qB865IR9qYRBRFHn4eAqGJOCFx+UbraQZmjr/mnRqSkY+nhbM7Pn/DWOrRnxoh+w +q5F9ZxdZ5D5T1v6kljVwxyfFgHItyyyIL0YS7e2h7cRRscCM+75kMedAP7icb4YN +LfWBqfKHbHIOqvvQK8T6+Emu/QlG2B5LvuErrop9K0KinhITekpVIO4HCN61cuOe +CADBKF/5uUJHwS9pWw3uUbpGUwsLBuhJzCY/OpJlDqC8Y9aToi2Ivl5u3/Q/sKjr +6AZb9lx4q3J2z7tJDrm5MHYwV74elGSXoeoG8nODUqjgklIWAPrt6lQ3WJpO2kug +8RhCdSbWkcXHfX95AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FOIxhqTPkKVqKBZvMWtKewKWDvDBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEAqoItII89lOl4TKvg0I1EinxafZLXIheLcdGCxpjRxlZ9QMQUN3yb +y/8uFKBL0otbQgJEoGhxm4h0tp54g28M6TN1U0332dwkjYxUNwvzrMaV5Na55I2Z +1hq4GB3NMXW+PvdtsgVOZbEN+zOyOZ5MvJHEQVkT3YRnf6avsdntltcRzHJ16pJc +Y8rR7yWwPXh1lPaPkxddrCtwayyGxNbNmRybjR48uHRhwu7v2WuAMdChL8H8bp89 +TQLMrMHgSbZfee9hKhO4Zebelf1/cslRSrhkG0ESq6G5MUINj6lMg2g6F0F7Xz2v +ncD/vuRN5P+vT8th/oZ0Q2Gc68Pun0cn/g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAJYlnmkGRj4ju/2jBQsnXJYwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMDQ0NFoYDzIwNjEwNTIyMDAwNDQ0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74V3eigv+pCj5 +nqDBqplY0Jp16pTeNB06IKbzb4MOTvNde6QjsZxrE1xUmprT8LxQqN9tI3aDYEYk +b9v4F99WtQVgCv3Y34tYKX9NwWQgwS1vQwnIR8zOFBYqsAsHEkeJuSqAB12AYUSd +Zv2RVFjiFmYJho2X30IrSLQfS/IE3KV7fCyMMm154+/K1Z2IJlcissydEAwgsUHw +edrE6CxJVkkJ3EvIgG4ugK/suxd8eEMztaQYJwSdN8TdfT59LFuSPl7zmF3fIBdJ +//WexcQmGabaJ7Xnx+6o2HTfkP8Zzzzaq8fvjAcvA7gyFH5EP26G2ZqMG+0y4pTx +SPVTrQEXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIWWuNEF +sUMOC82XlfJeqazzrkPDMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAgClmxcJaQTGpEZmjElL8G2Zc8lGc+ylGjiNlSIw8X25/bcLRptbDA90nuP+q +zXAMhEf0ccbdpwxG/P5a8JipmHgqQLHfpkvaXx+0CuP++3k+chAJ3Gk5XtY587jX ++MJfrPgjFt7vmMaKmynndf+NaIJAYczjhJj6xjPWmGrjM3MlTa9XesmelMwP3jep +bApIWAvCYVjGndbK9byyMq1nyj0TUzB8oJZQooaR3MMjHTmADuVBylWzkRMxbKPl +4Nlsk4Ef1JvIWBCzsMt+X17nuKfEatRfp3c9tbpGlAE/DSP0W2/Lnayxr4RpE9ds +ICF35uSis/7ZlsftODUe8wtpkQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQS7vMpOTVq2Jw457NdZ2ffjAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMzA5MTkyMjExNDNaGA8yMTIzMDkxOTIzMTE0M1owgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBjYS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARdgGSs/F2lpWKqS1ZpcmatFED1JurmNbXG +Sqhv1A/geHrKCS15MPwjtnfZiujYKY4fNkCCUseoGDwkC4281nwkokvnfWR1/cXy +LxfACoXNxsI4b+37CezSUBl48/5p1/OjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFFhLokGBuJGwKJhZcYSYKyZIitJtMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA8aQQlzJRHbqFsRY4O3u/cN0T8dzjcqnYn4NV1w+jvhzt +QPJLB+ggGyQhoFR6G2UrAjEA0be8OP5MWXD8d01KKbo5Dpy6TwukF5qoJmkFJKS3 +bKfEMvFWxXoV06HNZFWdI80u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAPvvd+MCcp8E36lHziv0xhMwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMTEwNloYDzIxMjEwNTIyMDAxMTA2WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbvwekKIKGcV/s +lDU96a71ZdN2pTYkev1X2e2/ICb765fw/i1jP9MwCzs8/xHBEQBJSxdfO4hPeNx3 +ENi0zbM+TrMKliS1kFVe1trTTEaHYjF8BMK9yTY0VgSpWiGxGwg4tshezIA5lpu8 +sF6XMRxosCEVCxD/44CFqGZTzZaREIvvFPDTXKJ6yOYnuEkhH3OcoOajHN2GEMMQ +ShuyRFDQvYkqOC/Q5icqFbKg7eGwfl4PmimdV7gOVsxSlw2s/0EeeIILXtHx22z3 +8QBhX25Lrq2rMuaGcD3IOMBeBo2d//YuEtd9J+LGXL9AeOXHAwpvInywJKAtXTMq +Wsy3LjhuANFrzMlzjR2YdjkGVzeQVx3dKUzJ2//Qf7IXPSPaEGmcgbxuatxjnvfT +H85oeKr3udKnXm0Kh7CLXeqJB5ITsvxI+Qq2iXtYCc+goHNR01QJwtGDSzuIMj3K +f+YMrqBXZgYBwU2J/kCNTH31nfw96WTbOfNGwLwmVRDgguzFa+QzmQsJW4FTDMwc +7cIjwdElQQVA+Gqa67uWmyDKAnoTkudmgAP+OTBkhnmc6NJuZDcy6f/iWUdl0X0u +/tsfgXXR6ZovnHonM13ANiN7VmEVqFlEMa0VVmc09m+2FYjjlk8F9sC7Rc4wt214 +7u5YvCiCsFZwx44baP5viyRZgkJVpQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBQgCZCsc34nVTRbWsniXBPjnUTQ2DAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAAQas3x1G6OpsIvQeMS9BbiHG3+kU9P/ba6Rrg+E +lUz8TmL04Bcd+I+R0IyMBww4NznT+K60cFdk+1iSmT8Q55bpqRekyhcdWda1Qu0r +JiTi7zz+3w2v66akofOnGevDpo/ilXGvCUJiLOBnHIF0izUqzvfczaMZGJT6xzKq +PcEVRyAN1IHHf5KnGzUlVFv9SGy47xJ9I1vTk24JU0LWkSLzMMoxiUudVmHSqJtN +u0h+n/x3Q6XguZi1/C1KOntH56ewRh8n5AF7c+9LJJSRM9wunb0Dzl7BEy21Xe9q +03xRYjf5wn8eDELB8FZPa1PrNKXIOLYM9egdctbKEcpSsse060+tkyBrl507+SJT +04lvJ4tcKjZFqxn+bUkDQvXYj0D3WK+iJ7a8kZJPRvz8BDHfIqancY8Tgw+69SUn +WqIb+HNZqFuRs16WFSzlMksqzXv6wcDSyI7aZOmCGGEcYW9NHk8EuOnOQ+1UMT9C +Qb1GJcipjRzry3M4KN/t5vN3hIetB+/PhmgTO4gKhBETTEyPC3HC1QbdVfRndB6e +U/NF2U/t8U2GvD26TTFLK4pScW7gyw4FQyXWs8g8FS8f+R2yWajhtS9++VDJQKom +fAUISoCH+PlPRJpu/nHd1Zrddeiiis53rBaLbXu2J1Q3VqjWOmtj0HjxJJxWnYmz +Pqj2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAI/U4z6+GF8/znpHM8Dq8G0wDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQ4MThaGA8yMTIyMDYwNjIyNDgxOFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK5WqMvyq888 +3uuOtEj1FcP6iZhqO5kJurdJF59Otp2WCg+zv6I+QwaAspEWHQsKD405XfFsTGKV +SKTCwoMxwBniuChSmyhlagQGKSnRY9+znOWq0v7hgmJRwp6FqclTbubmr+K6lzPy +hs86mEp68O5TcOTYWUlPZDqfKwfNTbtCl5YDRr8Gxb5buHmkp6gUSgDkRsXiZ5VV +b3GBmXRqbnwo5ZRNAzQeM6ylXCn4jKs310lQGUrFbrJqlyxUdfxzqdlaIRn2X+HY +xRSYbHox3LVNPpJxYSBRvpQVFSy9xbX8d1v6OM8+xluB31cbLBtm08KqPFuqx+cO +I2H5F0CYqYzhyOSKJsiOEJT6/uH4ewryskZzncx9ae62SC+bB5n3aJLmOSTkKLFY +YS5IsmDT2m3iMgzsJNUKVoCx2zihAzgBanFFBsG+Xmoq0aKseZUI6vd2qpd5tUST +/wS1sNk0Ph7teWB2ACgbFE6etnJ6stwjHFZOj/iTYhlnR2zDRU8akunFdGb6CB4/ +hMxGJxaqXSJeGtHm7FpadlUTf+2ESbYcVW+ui/F8sdBJseQdKZf3VdZZMgM0bcaX +NE47cauDTy72WdU9YJX/YXKYMLDE0iFHTnGpfVGsuWGPYhlwZ3dFIO07mWnCRM6X +u5JXRB1oy5n5HRluMsmpSN/R92MeBxKFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFNtH0F0xfijSLHEyIkRGD9gW6NazMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEACo+5jFeY3ygxoDDzL3xpfe5M0U1WxdKk+az4 +/OfjZvkoma7WfChi3IIMtwtKLYC2/seKWA4KjlB3rlTsCVNPnK6D+gAnybcfTKk/ +IRSPk92zagwQkSUWtAk80HpVfWJzpkSU16ejiajhedzOBRtg6BwsbSqLCDXb8hXr +eXWC1S9ZceGc+LcKRHewGWPu31JDhHE9bNcl9BFSAS0lYVZqxIRWxivZ+45j5uQv +wPrC8ggqsdU3K8quV6dblUQzzA8gKbXJpCzXZihkPrYpQHTH0szvXvgebh+CNUAG +rUxm8+yTS0NFI3U+RLbcLFVzSvjMOnEwCX0SPj5XZRYYXs5ajtQCoZhTUkkwpDV8 +RxXk8qGKiXwUxDO8GRvmvM82IOiXz5w2jy/h7b7soyIgdYiUydMq4Ja4ogB/xPZa +gf4y0o+bremO15HFf1MkaU2UxPK5FFVUds05pKvpSIaQWbF5lw4LHHj4ZtVup7zF +CLjPWs4Hs/oUkxLMqQDw0FBwlqa4uot8ItT8uq5BFpz196ZZ+4WXw5PVzfSxZibI +C/nwcj0AS6qharXOs8yPnPFLPSZ7BbmWzFDgo3tpglRqo3LbSPsiZR+sLeivqydr +0w4RK1btRda5Ws88uZMmW7+2aufposMKcbAdrApDEAVzHijbB/nolS5nsnFPHZoA +KDPtFEk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQVZ5Y/KqjR4XLou8MCD5pOjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyNTE2NTgzM1oYDzIxMjIwNTI1MTc1ODMzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo473OmpD5vkckdJajXg +brhmNFyoSa0WCY1njuZC2zMFp3zP6rX4I1r3imrYnJd9pFH/aSiV/r6L5ACE5RPx +4qdg5SQ7JJUaZc3DWsTOiOed7BCZSzM+KTYK/2QzDMApo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTmogc06+1knsej1ltKUOdWFvwgsjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAIs7TlLMbGTWNXpGiKf9DxaM07d/iDHe +F/Vv/wyWSTGdobxBL6iArQNVXz0Gr4dvPAIwd0rsoa6R0x5mtvhdRPtM37FYrbHJ +pbV+OMusQqcSLseunLBoCHenvJW0QOCQ8EDY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAO9dVdiLTEGO8kjUFExJmgowDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIyMTIwMjIwMjYwOFoYDzIxMjIxMjAyMjEyNjA4WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDkVHmJ +bUc8CNDGBcgPmXHSHj5dS1PDnnpk3doCu6pahyYXW8tqAOmOqsDuNz48exY7YVy4 +u9I9OPBeTYB9ZUKwxq+1ZNLsr1cwVz5DdOyDREVFOjlU4rvw0eTgzhP5yw/d+Ai/ ++WmPebZG0irwPKN2f60W/KJ45UNtR+30MT8ugfnPuSHWjjV+dqCOCp/mj8nOCckn +k8GoREwjuTFJMKInpQUC0BaVVX6LiIdgtoLY4wdx00EqNBuROoRTAvrked0jvm7J +UI39CSYxhNZJ9F6LdESZXjI4u2apfNQeSoy6WptxFHr+kh2yss1B2KT6lbwGjwWm +l9HODk9kbBNSy2NeewAms36q+p8wSLPavL28IRfK0UaBAiN1hr2a/2RDGCwOJmw6 +5erRC5IIX5kCStyXPEGhVPp18EvMuBd37eLIxjZBBO8AIDf4Ue8QmxSeZH0cT204 +3/Bd6XR6+Up9iMTxkHr1URcL1AR8Zd62lg/lbEfxePNMK9mQGxKP8eTMG5AjtW9G +TatEoRclgE0wZQalXHmKpBNshyYdGqQZhzL1MxCxWzfHNgZkTKIsdzxrjnP7RiBR +jdRH0YhXn6Y906QfLwMCaufwfQ5J8+nj/tu7nG138kSxsu6VUkhnQJhUcUsxuHD/ +NnBx0KGVEldtZiZf7ccgtRVp1lA0OrVtq3ZLMQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ2WC3p8rWeE2N0S4Om01KsNLpk/jAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAFFEVDt45Obr6Ax9E4RMgsKjj4QjMFB9 +wHev1jL7hezl/ULrHuWxjIusaIZEIcKfn+v2aWtqOq13P3ht7jV5KsV29CmFuCdQ +q3PWiAXVs+hnMskTOmGMDnptqd6/UuSIha8mlOKKAvnmRQJvfX9hIfb/b/mVyKWD +uvTTmcy3cOTJY5ZIWGyzuvmcqA0YNcb7rkJt/iaLq4RX3/ofq4y4w36hefbcvj++ +pXHOmXk3dAej3y6SMBOUcGMyCJcCluRPNYKDTLn+fitcPxPC3JG7fI5bxQ0D6Hpa +qbyGBQu96sfahQyMc+//H8EYlo4b0vPeS5RFFXJS/VBf0AyNT4vVc7H17Q6KjeNp +wEARqsIa7UalHx9MnxrQ/LSTTxiC8qmDkIFuQtw8iQMN0SoL5S0eCZNRD31awgaY +y1PvY8JMN549ugIUjOXnown/OxharLW1evWUraU5rArq3JfeFpPXl4K/u10T5SCL +iJRoxFilGPMFE3hvnmbi5rEy8wRUn7TpLb4I4s/CB/lT2qZTPqvQHwxKCnMm9BKF +NHb4rLL5dCvUi5NJ6fQ/exOoGdOVSfT7jqFeq2TtNunERSz9vpriweliB6iIe1Al +Thj8aEs1GqA764rLVGA+vUe18NhjJm9EemrdIzjSQFy/NdbN/DMaHqEzJogWloAI +izQWYnCS19TJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvTCCAkOgAwIBAgIQCIY7E/bFvFN2lK9Kckb0dTAKBggqhkjOPQQDAzCBnjEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5BbWF6 +b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUxODIxMDUxMFoYDzIxMjEwNTE4MjIwNTEwWjCB +njELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5B +bWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMI0hzf1JCEOI +Eue4+DmcNnSs2i2UaJxHMrNGGfU7b42a7vwP53F7045ffHPBGP4jb9q02/bStZzd +VHqfcgqkSRI7beBKjD2mfz82hF/wJSITTgCLs+NRpS6zKMFOFHUNo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBS8uF/6hk5mPLH4qaWv9NVZaMmyTjAOBgNV +HQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAO7Pu9wzLyM0X7Q08uLIL+vL +qaxe3UFuzFTWjM16MLJHbzLf1i9IDFKz+Q4hXCSiJwIwClMBsqT49BPUxVsJnjGr +EbyEk6aOOVfY1p2yQL649zh3M4h8okLnwf+bYIb1YpeU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQY+JhwFEQTe36qyRlUlF8ozANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MjQxNloYDzIwNjEwNTE5MjAyNDE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIye77j6ev40 +8wRPyN2OdKFSUfI9jB20Or2RLO+RDoL43+USXdrze0Wv4HMRLqaen9BcmCfaKMp0 +E4SFo47bXK/O17r6G8eyq1sqnHE+v288mWtYH9lAlSamNFRF6YwA7zncmE/iKL8J +0vePHMHP/B6svw8LULZCk+nZk3tgxQn2+r0B4FOz+RmpkoVddfqqUPMbKUxhM2wf +fO7F6bJaUXDNMBPhCn/3ayKCjYr49ErmnpYV2ZVs1i34S+LFq39J7kyv6zAgbHv9 ++/MtRMoRB1CjpqW0jIOZkHBdYcd1o9p1zFn591Do1wPkmMsWdjIYj+6e7UXcHvOB +2+ScIRAcnwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGtq2W +YSyMMxpdQ3IZvcGE+nyZqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEgoP3ixJsKSD5FN8dQ01RNHERl/IFbA7TRXfwC+L1yFocKnQh4Mp/msPRSV ++OeHIvemPW/wtZDJzLTOFJ6eTolGekHK1GRTQ6ZqsWiU2fmiOP8ks4oSpI+tQ9Lw +VrfZqTiEcS5wEIqyfUAZZfKDo7W1xp+dQWzfczSBuZJZwI5iaha7+ILM0r8Ckden +TVTapc5pLSoO15v0ziRuQ2bT3V3nwu/U0MRK44z+VWOJdSiKxdnOYDs8hFNnKhfe +klbTZF7kW7WbiNYB43OaAQBJ6BALZsIskEaqfeZT8FD71uN928TcEQyBDXdZpRN+ +iGQZDGhht0r0URGMDSs9waJtTfA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQXY/dmS+72lZPranO2JM9jjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjEzNDUxWhgPMjEyMTA1MjUyMjM0NTFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMyW9kBJjD/hx8e8 +b5E1sF42bp8TXsz1htSYE3Tl3T1Aq379DfEhB+xa/ASDZxt7/vwa81BkNo4M6HYq +okYIXeE7cu5SnSgjWXqcERhgPevtAwgmhdE3yREe8oz2DyOi2qKKZqah+1gpPaIQ +fK0uAqoeQlyHosye3KZZKkDHBatjBsQ5kf8lhuf7wVulEZVRHY2bP2X7N98PfbpL +QdH7mWXzDtJJ0LiwFwds47BrkgK1pkHx2p1mTo+HMkfX0P6Fq1atkVC2RHHtbB/X +iYyH7paaHBzviFrhr679zNqwXIOKlbf74w3mS11P76rFn9rS1BAH2Qm6eY5S/Fxe +HEKXm4kjPN63Zy0p3yE5EjPt54yPkvumOnT+RqDGJ2HCI9k8Ehcbve0ogfdRKNqQ +VHWYTy8V33ndQRHZlx/CuU1yN61TH4WSoMly1+q1ihTX9sApmlQ14B2pJi/9DnKW +cwECrPy1jAowC2UJ45RtC8UC05CbP9yrIy/7Noj8gQDiDOepm+6w1g6aNlWoiuQS +kyI6nzz1983GcnOHya73ga7otXo0Qfg9jPghlYiMomrgshlSLDHZG0Ib/3hb8cnR +1OcN9FpzNmVK2Ll1SmTMLrIhuCkyNYX9O/bOknbcf706XeESxGduSkHEjIw/k1+2 +Atteoq5dT6cwjnJ9hyhiueVlVkiDAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLUI+DD7RJs+0nRnjcwIVWzzYSsFMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAb1mcCHv4qMQetLGTBH9IxsB2YUUhr5dda0D2BcHr +UtDbfd0VQs4tux6h/6iKwHPx0Ew8fuuYj99WknG0ffgJfNc5/fMspxR/pc1jpdyU +5zMQ+B9wi0lOZPO9uH7/pr+d2odcNEy8zAwqdv/ihsTwLmGP54is9fVbsgzNW1cm +HKAVL2t/Ope+3QnRiRilKCN1lzhav4HHdLlN401TcWRWKbEuxF/FgxSO2Hmx86pj +e726lweCTMmnq/cTsPOVY0WMjs0or3eHDVlyLgVeV5ldyN+ptg3Oit60T05SRa58 +AJPTaVKIcGQ/gKkKZConpu7GDofT67P/ox0YNY57LRbhsx9r5UY4ROgz7WMQ1yoS +Y+19xizm+mBm2PyjMUbfwZUyCxsdKMwVdOq5/UmTmdms+TR8+m1uBHPOTQ2vKR0s +Pd/THSzPuu+d3dbzRyDSLQbHFFneG760CUlD/ZmzFlQjJ89/HmAmz8IyENq+Sjhx +Jgzy+FjVZb8aRUoYLlnffpUpej1n87Ynlr1GrvC4GsRpNpOHlwuf6WD4W0qUTsC/ +C9JO+fBzUj/aWlJzNcLEW6pte1SB+EdkR2sZvWH+F88TxemeDrV0jKJw5R89CDf8 +ZQNfkxJYjhns+YeV0moYjqQdc7tq4i04uggEQEtVzEhRLU5PE83nlh/K2NZZm8Kj +dIA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAPVSMfFitmM5PhmbaOFoGfUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMzQ1N1oYDzIwNjEwNTI1MjMzNDU3WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu9H7TBeGoDzMr +dxN6H8COntJX4IR6dbyhnj5qMD4xl/IWvp50lt0VpmMd+z2PNZzx8RazeGC5IniV +5nrLg0AKWRQ2A/lGGXbUrGXCSe09brMQCxWBSIYe1WZZ1iU1IJ/6Bp4D2YEHpXrW +bPkOq5x3YPcsoitgm1Xh8ygz6vb7PsvJvPbvRMnkDg5IqEThapPjmKb8ZJWyEFEE +QRrkCIRueB1EqQtJw0fvP4PKDlCJAKBEs/y049FoOqYpT3pRy0WKqPhWve+hScMd +6obq8kxTFy1IHACjHc51nrGII5Bt76/MpTWhnJIJrCnq1/Uc3Qs8IVeb+sLaFC8K +DI69Sw6bAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE7PCopt +lyOgtXX0Y1lObBUxuKaCMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAFj+bX8gLmMNefr5jRJfHjrL3iuZCjf7YEZgn89pS4z8408mjj9z6Q5D1H7yS +jNETVV8QaJip1qyhh5gRzRaArgGAYvi2/r0zPsy+Tgf7v1KGL5Lh8NT8iCEGGXwF +g3Ir+Nl3e+9XUp0eyyzBIjHtjLBm6yy8rGk9p6OtFDQnKF5OxwbAgip42CD75r/q +p421maEDDvvRFR4D+99JZxgAYDBGqRRceUoe16qDzbMvlz0A9paCZFclxeftAxv6 +QlR5rItMz/XdzpBJUpYhdzM0gCzAzdQuVO5tjJxmXhkSMcDP+8Q+Uv6FA9k2VpUV +E/O5jgpqUJJ2Hc/5rs9VkAPXeA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQW0yuFCle3uj4vWiGU0SaGzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkzNTE2WhgPMjEyMTA1MTkyMDM1MTZaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYWYtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDPiKNZSaXs3Un/J/v+LTsFDANHpi7en +oL2qh0u0DoqNzEBTbBjvO23bLN3k599zh6CY3HKW0r2k1yaIdbWqt4upMCRCcUFi +I4iedAmubgzh56wJdoMZztjXZRwDthTkJKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUWbYkcrvVSnAWPR5PJhIzppcAnZIwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMCESGqpat93CjrSEjE7z+Hbvz0psZTHwqaxuiH64GKUm +mYynIiwpKHyBrzjKBmeDoQIxANGrjIo6/b8Jl6sdIZQI18V0pAyLfLiZjlHVOnhM +MOTVgr82ZuPoEHTX78MxeMnYlw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAIbsx8XOl0sgTNiCN4O+18QwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1NDU4WhgPMjA2MTA1MjUyMjU0NTha +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +tROxwXWCgn5R9gI/2Ivjzaxc0g95ysBjoJsnhPdJEHQb7w3y2kWrVWU3Y9fOitgb +CEsnEC3PrhRnzNVW0fPsK6kbvOeCmjvY30rdbxbc8h+bjXfGmIOgAkmoULEr6Hc7 +G1Q/+tvv4lEwIs7bEaf+abSZxRJbZ0MBxhbHn7UHHDiMZYvzK+SV1MGCxx7JVhrm +xWu3GC1zZCsGDhB9YqY9eR6PmjbqA5wy8vqbC57dZZa1QVtWIQn3JaRXn+faIzHx +nLMN5CEWihsdmHBXhnRboXprE/OS4MFv1UrQF/XM/h5RBeCywpHePpC+Oe1T3LNC +iP8KzRFrjC1MX/WXJnmOVQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBS33XbXAUMs1znyZo4B0+B3D68WFTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBADuadd2EmlpueY2VlrIIPC30QkoA1EOSoCmZgN6124apkoY1 +HiV4r+QNPljN4WP8gmcARnNkS7ZeR4fvWi8xPh5AxQCpiaBMw4gcbTMCuKDV68Pw +P2dZCTMspvR3CDfM35oXCufdtFnxyU6PAyINUqF/wyTHguO3owRFPz64+sk3r2pT +WHmJjG9E7V+KOh0s6REgD17Gqn6C5ijLchSrPUHB0wOIkeLJZndHxN/76h7+zhMt +fFeNxPWHY2MfpcaLjz4UREzZPSB2U9k+y3pW1omCIcl6MQU9itGx/LpQE+H3ZeX2 +M2bdYd5L+ow+bdbGtsVKOuN+R9Dm17YpswF+vyQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAKlQ+3JX9yHXyjP/Ja6kZhkwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MTkxNzQ1MjBaGA8yMTIxMDUxOTE4NDUyMFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKtahBrpUjQ6 +H2mni05BAKU6Z5USPZeSKmBBJN3YgD17rJ93ikJxSgzJ+CupGy5rvYQ0xznJyiV0 +91QeQN4P+G2MjGQR0RGeUuZcfcZitJro7iAg3UBvw8WIGkcDUg+MGVpRv/B7ry88 +7E4OxKb8CPNoa+a9j6ABjOaaxaI22Bb7j3OJ+JyMICs6CU2bgkJaj3VUV9FCNUOc +h9PxD4jzT9yyGYm/sK9BAT1WOTPG8XQUkpcFqy/IerZDfiQkf1koiSd4s5VhBkUn +aQHOdri/stldT7a+HJFVyz2AXDGPDj+UBMOuLq0K6GAT6ThpkXCb2RIf4mdTy7ox +N5BaJ+ih+Ro3ZwPkok60egnt/RN98jgbm+WstgjJWuLqSNInnMUgkuqjyBWwePqX +Kib+wdpyx/LOzhKPEFpeMIvHQ3A0sjlulIjnh+j+itezD+dp0UNxMERlW4Bn/IlS +sYQVNfYutWkRPRLErXOZXtlxxkI98JWQtLjvGzQr+jywxTiw644FSLWdhKa6DtfU +2JWBHqQPJicMElfZpmfaHZjtXuCZNdZQXWg7onZYohe281ZrdFPOqC4rUq7gYamL +T+ZB+2P+YCPOLJ60bj/XSvcB7mesAdg8P0DNddPhHUFWx2dFqOs1HxIVB4FZVA9U +Ppbv4a484yxjTgG7zFZNqXHKTqze6rBBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFCEAqjighncv/UnWzBjqu1Ka2Yb4MA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAYyvumblckIXlohzi3QiShkZhqFzZultbFIu9 +GhA5CDar1IFMhJ9vJpO9nUK/camKs1VQRs8ZsBbXa0GFUM2p8y2cgUfLwFULAiC/ +sWETyW5lcX/xc4Pyf6dONhqFJt/ovVBxNZtcmMEWv/1D6Tf0nLeEb0P2i/pnSRR4 +Oq99LVFjossXtyvtaq06OSiUUZ1zLPvV6AQINg8dWeBOWRcQYhYcEcC2wQ06KShZ +0ahuu7ar5Gym3vuLK6nH+eQrkUievVomN/LpASrYhK32joQ5ypIJej3sICIgJUEP +UoeswJ+Z16f3ECoL1OSnq4A0riiLj1ZGmVHNhM6m/gotKaHNMxsK9zsbqmuU6IT/ +P6cR0S+vdigQG8ZNFf5vEyVNXhl8KcaJn6lMD/gMB2rY0qpaeTg4gPfU5wcg8S4Y +C9V//tw3hv0f2n+8kGNmqZrylOQDQWSSo8j8M2SRSXiwOHDoTASd1fyBEIqBAwzn +LvXVg8wQd1WlmM3b0Vrsbzltyh6y4SuKSkmgufYYvC07NknQO5vqvZcNoYbLNea3 +76NkFaMHUekSbwVejZgG5HGwbaYBgNdJEdpbWlA3X4yGRVxknQSUyt4dZRnw/HrX +k8x6/wvtw7wht0/DOqz1li7baSsMazqxx+jDdSr1h9xML416Q4loFCLgqQhil8Jq +Em4Hy3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQFn6AJ+uxaPDpNVx7174CpjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIxMjAyMjAxNDA4WhgPMjA2MjEyMDIyMTE0MDhaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgaWwtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2xGTSJ +fXorki/dkkTqdLyv4U1neeFYEyUCPN/HJ7ZloNwhj8RBrHYhZ4qtvUAvN+rs8fUm +L0wmaL69ye61S+CSfDzNwBDGwOzUm/cc1NEJOHCm8XA0unBNBvpJTjsFk2LQ+rz8 +oU0lVV4mjnfGektrTDeADonO1adJvUTYmF6v1wMnykSkp8AnW9EG/6nwcAJuAJ7d +BfaLThm6lfxPdsBNG81DLKi2me2TLQ4yl+vgRKJi2fJWwA77NaDqQuD5upRIcQwt +5noJt2kFFmeiro98ZMMRaDTHAHhJfWkwkw5f2QNIww7T4r85IwbQCgJVRo4m4ZTC +W/1eiEccU2407mECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +DNhVvGHzKXv0Yh6asK0apP9jJlUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQCoEVTUY/rF9Zrlpb1Y1hptEguw0i2pCLakcmv3YNj6thsubbGeGx8Z +RjUA/gPKirpoae2HU1y64WEu7akwr6pdTRtXXjbe9NReT6OW/0xAwceSXCOiStqS +cMsWWTGg6BA3uHqad5clqITjDZr1baQ8X8en4SXRBxXyhJXbOkB60HOQeFR9CNeh +pJdrWLeNYXwU0Z59juqdVMGwvDAYdugWUhW2rhafVUXszfRA5c8Izc+E31kq90aY +LmxFXUHUfG0eQOmxmg+Z/nG7yLUdHIFA3id8MRh22hye3KvRdQ7ZVGFni0hG2vQQ +Q01AvD/rhzyjg0czzJKLK9U/RttwdMaV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAJfKe4Zh4aWNt3bv6ZjQwogwDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDg1M1oYDzIxMjEwNTIxMjMwODUzWjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpgUH6 +Crzd8cOw9prAh2rkQqAOx2vtuI7xX4tmBG4I/um28eBjyVmgwQ1fpq0Zg2nCKS54 +Nn0pCmT7f3h6Bvopxn0J45AzXEtajFqXf92NQ3iPth95GVfAJSD7gk2LWMhpmID9 +JGQyoGuDPg+hYyr292X6d0madzEktVVGO4mKTF989qEg+tY8+oN0U2fRTrqa2tZp +iYsmg350ynNopvntsJAfpCO/srwpsqHHLNFZ9jvhTU8uW90wgaKO9i31j/mHggCE ++CAOaJCM3g+L8DPl/2QKsb6UkBgaaIwKyRgKSj1IlgrK+OdCBCOgM9jjId4Tqo2j +ZIrrPBGl6fbn1+etZX+2/tf6tegz+yV0HHQRAcKCpaH8AXF44bny9andslBoNjGx +H6R/3ib4FhPrnBMElzZ5i4+eM/cuPC2huZMBXb/jKgRC/QN1Wm3/nah5FWq+yn+N +tiAF10Ga0BYzVhHDEwZzN7gn38bcY5yi/CjDUNpY0OzEe2+dpaBKPlXTaFfn9Nba +CBmXPRF0lLGGtPeTAgjcju+NEcVa82Ht1pqxyu2sDtbu3J5bxp4RKtj+ShwN8nut +Tkf5Ea9rSmHEY13fzgibZlQhXaiFSKA2ASUwgJP19Putm0XKlBCNSGCoECemewxL ++7Y8FszS4Uu4eaIwvXVqUEE2yf+4ex0hqQ1acQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSeUnXIRxNbYsZLtKomIz4Y1nOZEzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAIpRvxVS0dzoosBh/qw65ghPUGSbP2D4 +dm6oYCv5g/zJr4fR7NzEbHOXX5aOQnHbQL4M/7veuOCLNPOW1uXwywMg6gY+dbKe +YtPVA1as8G9sUyadeXyGh2uXGsziMFXyaESwiAXZyiYyKChS3+g26/7jwECFo5vC +XGhWpIO7Hp35Yglp8AnwnEAo/PnuXgyt2nvyTSrxlEYa0jus6GZEZd77pa82U1JH +qFhIgmKPWWdvELA3+ra1nKnvpWM/xX0pnMznMej5B3RT3Y+k61+kWghJE81Ix78T ++tG4jSotgbaL53BhtQWBD1yzbbilqsGE1/DXPXzHVf9yD73fwh2tGWSaVInKYinr +a4tcrB3KDN/PFq0/w5/21lpZjVFyu/eiPj6DmWDuHW73XnRwZpHo/2OFkei5R7cT +rn/YdDD6c1dYtSw5YNnS6hdCQ3sOiB/xbPRN9VWJa6se79uZ9NLz6RMOr73DNnb2 +bhIR9Gf7XAA5lYKqQk+A+stoKbIT0F65RnkxrXi/6vSiXfCh/bV6B41cf7MY/6YW +ehserSdjhQamv35rTFdM+foJwUKz1QN9n9KZhPxeRmwqPitAV79PloksOnX25ElN +SlyxdndIoA1wia1HRd26EFm2pqfZ2vtD2EjU3wD42CXX4H8fKVDna30nNFSYF0yn +jGKc3k6UNxpg +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQaRHaEqqacXN20e8zZJtmDDANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjIzODM1WhgPMjEyMTA1MjUyMzM4MzVaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAInfBCaHuvj6Rb5c +L5Wmn1jv2PHtEGMHm+7Z8dYosdwouG8VG2A+BCYCZfij9lIGszrTXkY4O7vnXgru +JUNdxh0Q3M83p4X+bg+gODUs3jf+Z3Oeq7nTOk/2UYvQLcxP4FEXILxDInbQFcIx +yen1ESHggGrjEodgn6nbKQNRfIhjhW+TKYaewfsVWH7EF2pfj+cjbJ6njjgZ0/M9 +VZifJFBgat6XUTOf3jwHwkCBh7T6rDpgy19A61laImJCQhdTnHKvzTpxcxiLRh69 +ZObypR7W04OAUmFS88V7IotlPmCL8xf7kwxG+gQfvx31+A9IDMsiTqJ1Cc4fYEKg +bL+Vo+2Ii4W2esCTGVYmHm73drznfeKwL+kmIC/Bq+DrZ+veTqKFYwSkpHRyJCEe +U4Zym6POqQ/4LBSKwDUhWLJIlq99bjKX+hNTJykB+Lbcx0ScOP4IAZQoxmDxGWxN +S+lQj+Cx2pwU3S/7+OxlRndZAX/FKgk7xSMkg88HykUZaZ/ozIiqJqSnGpgXCtED +oQ4OJw5ozAr+/wudOawaMwUWQl5asD8fuy/hl5S1nv9XxIc842QJOtJFxhyeMIXt +LVECVw/dPekhMjS3Zo3wwRgYbnKG7YXXT5WMxJEnHu8+cYpMiRClzq2BEP6/MtI2 +AZQQUFu2yFjRGL2OZA6IYjxnXYiRAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFADCcQCPX2HmkqQcmuHfiQ2jjqnrMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEASXkGQ2eUmudIKPeOIF7RBryCoPmMOsqP0+1qxF8l +pGkwmrgNDGpmd9s0ArfIVBTc1jmpgB3oiRW9c6n2OmwBKL4UPuQ8O3KwSP0iD2sZ +KMXoMEyphCEzW1I2GRvYDugL3Z9MWrnHkoaoH2l8YyTYvszTvdgxBPpM2x4pSkp+ +76d4/eRpJ5mVuQ93nC+YG0wXCxSq63hX4kyZgPxgCdAA+qgFfKIGyNqUIqWgeyTP +n5OgKaboYk2141Rf2hGMD3/hsGm0rrJh7g3C0ZirPws3eeJfulvAOIy2IZzqHUSY +jkFzraz6LEH3IlArT3jUPvWKqvh2lJWnnp56aqxBR7qHH5voD49UpJWY1K0BjGnS +OHcurpp0Yt/BIs4VZeWdCZwI7JaSeDcPMaMDBvND3Ia5Fga0thgYQTG6dE+N5fgF +z+hRaujXO2nb0LmddVyvE8prYlWRMuYFv+Co8hcMdJ0lEZlfVNu0jbm9/GmwAZ+l +9umeYO9yz/uC7edC8XJBglMAKUmVK9wNtOckUWAcCfnPWYLbYa/PqtXBYcxrso5j +iaS/A7iEW51uteHBGrViCy1afGG+hiUWwFlesli+Rq4dNstX3h6h2baWABaAxEVJ +y1RnTQSz6mROT1VmZSgSVO37rgIyY0Hf0872ogcTS+FfvXgBxCxsNWEbiQ/XXva4 +0Ws= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAMyaTlVLN0ndGp4ffwKAfoMwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBtZS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNTA3MDA0NDM3WhgPMjEyMjA1MDcwMTQ0MzdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE19nCV1nsI6CohSor13+B25cr +zg+IHdi9Y3L7ziQnHWI6yjBazvnKD+oC71aRRlR8b5YXsYGUQxWzPLHN7EGPcSGv +bzA9SLG1KQYCJaQ0m9Eg/iGrwKWOgylbhVw0bCxoo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS4KsknsJXM9+QPEkBdZxUPaLr11zAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJaRgrYIEfXQMZQQDxMTYS0azpyWSseQooXo +L3nYq4OHGBgYyQ9gVjvRYWU85PXbfgIwdi82DtANQFkCu+j+BU0JBY/uRKPEeYzo +JG92igKIcXPqCoxIJ7lJbbzmuf73gQu5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAJwCobx0Os8F7ihbJngxrR8wDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjAxNzE1MzNaGA8yMTIxMDUyMDE4MTUzM1owgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANukKwlm+ZaI +Y5MkWGbEVLApEyLmlrHLEg8PfiiEa9ts7jssQcin3bzEPdTqGr5jo91ONoZ3ccWq +xJgg1W3bLu5CAO2CqIOXTXHRyCO/u0Ch1FGgWB8xETPSi3UHt/Vn1ltdO6DYdbDU +mYgwzYrvLBdRCwxsb9o+BuYQHVFzUYonqk/y9ujz3gotzFq7r55UwDTA1ita3vb4 +eDKjIb4b1M4Wr81M23WHonpje+9qkkrAkdQcHrkgvSCV046xsq/6NctzwCUUNsgF +7Q1a8ut5qJEYpz5ta8vI1rqFqAMBqCbFjRYlmAoTTpFPOmzAVxV+YoqTrW5A16su +/2SXlMYfJ/n/ad/QfBNPPAAQMpyOr2RCL/YiL/PFZPs7NxYjnZHNWxMLSPgFyI+/ +t2klnn5jR76KJK2qimmaXedB90EtFsMRUU1e4NxH9gDuyrihKPJ3aVnZ35mSipvR +/1KB8t8gtFXp/VQaz2sg8+uxPMKB81O37fL4zz6Mg5K8+aq3ejBiyHucpFGnsnVB +3kQWeD36ONkybngmgWoyPceuSWm1hQ0Z7VRAQX+KlxxSaHmSaIk1XxZu9h9riQHx +fMuev6KXjRn/CjCoUTn+7eFrt0dT5GryQEIZP+nA0oq0LKxogigHNZlwAT4flrqb +JUfZJrqgoce5HjZSXl10APbtPjJi0fW9AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFEfV+LztI29OVDRm0tqClP3NrmEWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAvSNe+0wuk53KhWlRlRf2x/97H2Q76X3anzF0 +5fOSVm022ldALzXMzqOfdnoKIhAu2oVKiHHKs7mMas+T6TL+Mkphx0CYEVxFE3PG +061q3CqJU+wMm9W9xsB79oB2XG47r1fIEywZZ3GaRsatAbjcNOT8uBaATPQAfJFN +zjFe4XyN+rA4cFrYNvfHTeu5ftrYmvks7JlRaJgEGWsz+qXux7uvaEEVPqEumd2H +uYeaRNOZ2V23R009X5lbgBFx9tq5VDTnKhQiTQ2SeT0rc1W3Dz5ik6SbQQNP3nSR +0Ywy7r/sZ3fcDyfFiqnrVY4Ympfvb4YW2PZ6OsQJbzH6xjdnTG2HtzEU30ngxdp1 +WUEF4zt6rjJCp7QBUqXgdlHvJqYu6949qtWjEPiFN9uSsRV2i1YDjJqN52dLjAPn +AipJKo8x1PHTwUzuITqnB9BdP+5TlTl8biJfkEf/+08eWDTLlDHr2VrZLOLompTh +bS5OrhDmqA2Q+O+EWrTIhMflwwlCpR9QYM/Xwvlbad9H0FUHbJsCVNaru3wGOgWo +tt3dNSK9Lqnv/Ej9K9v6CRr36in4ylJKivhJ5B9E7ABHg7EpBJ1xi7O5eNDkNoJG ++pFyphJq3AkBR2U4ni2tUaTAtSW2tks7IaiDV+UMtqZyGabT5ISQfWLLtLHSWn2F +Tspdjbg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJZFh4s9aZGzKaTMLrSb4acwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjEyODQxWhgPMjA2MTA1MTgyMjI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgQmV0YSB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +17i2yoU6diep+WrqxIn2CrDEO2NdJVwWTSckx4WMZlLpkQDoymSmkNHjq9ADIApD +A31Cx+843apL7wub8QkFZD0Tk7/ThdHWJOzcAM3ov98QBPQfOC1W5zYIIRP2F+vQ +TRETHQnLcW3rLv0NMk5oQvIKpJoC9ett6aeVrzu+4cU4DZVWYlJUoC/ljWzCluau +8blfW0Vwin6OB7s0HCG5/wijQWJBU5SrP/KAIPeQi1GqG5efbqAXDr/ple0Ipwyo +Xjjl73LenGUgqpANlC9EAT4i7FkJcllLPeK3NcOHjuUG0AccLv1lGsHAxZLgjk/x +z9ZcnVV9UFWZiyJTKxeKPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRWyMuZUo4gxCR3Luf9/bd2AqZ7CjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIqN2DlIKlvDFPO0QUZQVFbsi/tLdYM98/vvzBpttlTGVMyD +gJuQeHVz+MnhGIwoCGOlGU3OOUoIlLAut0+WG74qYczn43oA2gbMd7HoD7oL/IGg +njorBwJVcuuLv2G//SqM3nxGcLRtkRnQ+lvqPxMz9+0fKFUn6QcIDuF0QSfthLs2 +WSiGEPKO9c9RSXdRQ4pXA7c3hXng8P4A2ZmdciPne5Nu4I4qLDGZYRrRLRkNTrOi +TyS6r2HNGUfgF7eOSeKt3NWL+mNChcYj71/Vycf5edeczpUgfnWy9WbPrK1svKyl +aAs2xg+X6O8qB+Mnj2dNBzm+lZIS3sIlm+nO9sg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAPAlEk8VJPmEzVRRaWvTh2AwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI1MjI0MTU1WhgPMjEyMTA1MjUyMzQxNTVaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx5xjrup8II4HOJw15NTnS3H5yMrQGlbj +EDA5MMGnE9DmHp5dACIxmPXPMe/99nO7wNdl7G71OYPCgEvWm0FhdvVUeTb3LVnV +BnaXt32Ek7/oxGk1T+Df03C+W0vmuJ+wo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBTGXmqBWN/1tkSea4pNw0oHrjk2UDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAIqqZWCSrIkZ7zsv/FygtAusW6yvlL935YAWYPVXU30m +jkMFLM+/RJ9GMvnO8jHfCgIwB+whlkcItzE9CRQ6CsMo/d5cEHDUu/QW6jSIh9BR +OGh9pTYPVkUbBiKPA7lVVhre +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAJGY9kZITwfSRaAS/bSBOw8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MTEyMFoYDzIxMjEwNTE5MTkxMTIwWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe2vlDp6Eo4WQi +Wi32YJOgdXHhxTFrLjB9SRy22DYoMaWfginJIwJcSR8yse8ZDQuoNhERB9LRggAE +eng23mhrfvtL1yQkMlZfBu4vG1nOb22XiPFzk7X2wqz/WigdYNBCqa1kK3jrLqPx +YUy7jk2oZle4GLVRTNGuMfcid6S2hs3UCdXfkJuM2z2wc3WUlvHoVNk37v2/jzR/ +hSCHZv5YHAtzL/kLb/e64QkqxKll5QmKhyI6d7vt6Lr1C0zb+DmwxUoJhseAS0hI +dRk5DklMb4Aqpj6KN0ss0HAYqYERGRIQM7KKA4+hxDMUkJmt8KqWKZkAlCZgflzl +m8NZ31o2cvBzf6g+VFHx+6iVrSkohVQydkCxx7NJ743iPKsh8BytSM4qU7xx4OnD +H2yNXcypu+D5bZnVZr4Pywq0w0WqbTM2bpYthG9IC4JeVUvZ2mDc01lqOlbMeyfT +og5BRPLDXdZK8lapo7se2teh64cIfXtCmM2lDSwm1wnH2iSK+AWZVIM3iE45WSGc +vZ+drHfVgjJJ5u1YrMCWNL5C2utFbyF9Obw9ZAwm61MSbPQL9JwznhNlCh7F2ANW +ZHWQPNcOAJqzE4uVcJB1ZeVl28ORYY1668lx+s9yYeMXk3QQdj4xmdnvoBFggqRB +ZR6Z0D7ZohADXe024RzEo1TukrQgKQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBT7Vs4Y5uG/9aXnYGNMEs6ycPUT3jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBACN4Htp2PvGcQA0/sAS+qUVWWJoAXSsu8Pgc6Gar +7tKVlNJ/4W/a6pUV2Xo/Tz3msg4yiE8sMESp2k+USosD5n9Alai5s5qpWDQjrqrh +76AGyF2nzve4kIN19GArYhm4Mz/EKEG1QHYvBDGgXi3kNvL/a2Zbybp+3LevG+q7 +xtx4Sz9yIyMzuT/6Y7ijtiMZ9XbuxGf5wab8UtwT3Xq1UradJy0KCkzRJAz/Wy/X +HbTkEvKSaYKExH6sLo0jqdIjV/d2Io31gt4e0Ly1ER2wPyFa+pc/swu7HCzrN+iz +A2ZM4+KX9nBvFyfkHLix4rALg+WTYJa/dIsObXkdZ3z8qPf5A9PXlULiaa1mcP4+ +rokw74IyLEYooQ8iSOjxumXhnkTS69MAdGzXYE5gnHokABtGD+BB5qLhtLt4fqAp +8AyHpQWMyV42M9SJLzQ+iOz7kAgJOBOaVtJI3FV/iAg/eqWVm3yLuUTWDxSHrKuL +N19+pSjF6TNvUSFXwEa2LJkfDqIOCE32iOuy85QY//3NsgrSQF6UkSPa95eJrSGI +3hTRYYh3Up2GhBGl1KUy7/o0k3KRZTk4s38fylY8bZ3TakUOH5iIGoHyFVVcp361 +Pyy25SzFSmNalWoQd9wZVc/Cps2ldxhcttM+WLkFNzprd0VJa8qTz8vYtHP0ouDN +nWS0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQKKqVZvk6NsLET+uYv5myCzAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMjEyMDIyMDMyMjBaGA8yMTIyMTIwMjIxMzIyMFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASYwfvj8BmvLAP6UkNQ4X4dXBB/ +webBO7swW+8HnFN2DAu+Cn/lpcDpu+dys1JmkVX435lrCH3oZjol0kCDIM1lF4Cv ++78yoY1Jr/YMat22E4iz4AZd9q0NToS7+ZA0r2yjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFO/8Py16qPr7J2GWpvxlTMB+op7XMA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAwk+rg788+u8JL6sdix7l57WTo8E/M+o3TO5x +uRuPdShrBFm4ArGR2PPs4zCQuKgqAjEAi0TA3PVqAxKpoz+Ps8/054p9WTgDfBFZ +i/lm2yTaPs0xjY6FNWoy7fsVw5oEKxOn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOY7gfcBZgR2tqfBzMbFQCUwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY1NDU5WhgPMjEyMjA1MjUxNzU0NTla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +lfxER43FuLRdL08bddF0YhbCP+XXKj1A/TFMXmd2My8XDei8rPXFYyyjMig9+xZw +uAsIxLwz8uiA26CKA8bCZKg5VG2kTeOJAfvBJaLv1CZefs3Z4Uf1Sjvm6MF2yqEj +GoORfyfL9HiZFTDuF/hcjWoKYCfMuG6M/wO8IbdICrX3n+BiYQJu/pFO660Mg3h/ +8YBBWYDbHoCiH/vkqqJugQ5BM3OI5nsElW51P1icEEqti4AZ7JmtSv9t7fIFBVyR +oaEyOgpp0sm193F/cDJQdssvjoOnaubsSYm1ep3awZAUyGN/X8MBrPY95d0hLhfH +Ehc5Icyg+hsosBljlAyksmt4hFQ9iBnWIz/ZTfGMck+6p3HVL9RDgvluez+rWv59 +8q7omUGsiPApy5PDdwI/Wt/KtC34/2sjslIJfvgifdAtkRPkhff1WEwER00ADrN9 +eGGInaCpJfb1Rq8cV2n00jxg7DcEd65VR3dmIRb0bL+jWK62ni/WdEyomAOMfmGj +aWf78S/4rasHllWJ+QwnaUYY3u6N8Cgio0/ep4i34FxMXqMV3V0/qXdfhyabi/LM +wCxNo1Dwt+s6OtPJbwO92JL+829QAxydfmaMTeHBsgMPkG7RwAekeuatKGHNsc2Z +x2Q4C2wVvOGAhcHwxfM8JfZs3nDSZJndtVVnFlUY0UECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUpnG7mWazy6k97/tb5iduRB3RXgQwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCDLqq1Wwa9Tkuv7vxBnIeVvvFF +ecTn+P+wJxl9Qa2ortzqTHZsBDyJO62d04AgBwiDXkJ9a+bthgG0H1J7Xee8xqv1 +xyX2yKj24ygHjspLotKP4eDMdDi5TYq+gdkbPmm9Q69B1+W6e049JVGXvWG8/7kU +igxeuCYwtCCdUPRLf6D8y+1XMGgVv3/DSOHWvTg3MJ1wJ3n3+eve3rjGdRYWZeJu +k21HLSZYzVrCtUsh2YAeLnUbSxVuT2Xr4JehYe9zW5HEQ8Je/OUfnCy9vzoN/ITw +osAH+EBJQey7RxEDqMwCaRefH0yeHFcnOll0OXg/urnQmwbEYzQ1uutJaBPsjU0J +Qf06sMxI7GiB5nPE+CnI2sM6A9AW9kvwexGXpNJiLxF8dvPQthpOKGcYu6BFvRmt +6ctfXd9b7JJoVqMWuf5cCY6ihpk1e9JTlAqu4Eb/7JNyGiGCR40iSLvV28un9wiE +plrdYxwcNYq851BEu3r3AyYWw/UW1AKJ5tM+/Gtok+AphMC9ywT66o/Kfu44mOWm +L3nSLSWEcgfUVgrikpnyGbUnGtgCmHiMlUtNVexcE7OtCIZoVAlCGKNu7tyuJf10 +Qlk8oIIzfSIlcbHpOYoN79FkLoDNc2er4Gd+7w1oPQmdAB0jBJnA6t0OUBPKdDdE +Ufff2jrbfbzECn1ELg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQIuO1A8LOnmc7zZ/vMm3TrDANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQ2MThaGA8yMTIxMDUyNDIxNDYxOFow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDq +qRHKbG8ZK6/GkGm2cenznEF06yHwI1gD5sdsHjTgekDZ2Dl9RwtDmUH2zFuIQwGj +SeC7E2iKwrJRA5wYzL9/Vk8NOILEKQOP8OIKUHbc7q8rEtjs401KcU6pFBBEdO9G +CTiRhogq+8mhC13AM/UriZJbKhwgM2UaDOzAneGMhQAGjH8z83NsNcPxpYVE7tqM +sch5yLtIJLkJRusrmQQTeHUev16YNqyUa+LuFclFL0FzFCimkcxUhXlbfEKXbssS +yPzjiv8wokGyo7+gA0SueceMO2UjfGfute3HlXZDcNvBbkSY+ver41jPydyRD6Qq +oEkh0tyIbPoa3oU74kwipJtz6KBEA3u3iq61OUR0ENhR2NeP7CSKrC24SnQJZ/92 +qxusrbyV/0w+U4m62ug/o4hWNK1lUcc2AqiBOvCSJ7qpdteTFxcEIzDwYfERDx6a +d9+3IPvzMb0ZCxBIIUFMxLTF7yAxI9s6KZBBXSZ6tDcCCYIgEysEPRWMRAcG+ye/ +fZVn9Vnzsj4/2wchC2eQrYpb1QvG4eMXA4M5tFHKi+/8cOPiUzJRgwS222J8YuDj +yEBval874OzXk8H8Mj0JXJ/jH66WuxcBbh5K7Rp5oJn7yju9yqX6qubY8gVeMZ1i +u4oXCopefDqa35JplQNUXbWwSebi0qJ4EK0V8F9Q+QIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBT4ysqCxaPe7y+g1KUIAenqu8PAgzAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBALU8WN35KAjPZEX65tobtCDQFkIO +uJjv0alD7qLB0i9eY80C+kD87HKqdMDJv50a5fZdqOta8BrHutgFtDm+xo5F/1M3 +u5/Vva5lV4xy5DqPajcF4Mw52czYBmeiLRTnyPJsU93EQIC2Bp4Egvb6LI4cMOgm +4pY2hL8DojOC5PXt4B1/7c1DNcJX3CMzHDm4SMwiv2MAxSuC/cbHXcWMk+qXdrVx ++ayLUSh8acaAOy3KLs1MVExJ6j9iFIGsDVsO4vr4ZNsYQiyHjp+L8ops6YVBO5AT +k/pI+axHIVsO5qiD4cFWvkGqmZ0gsVtgGUchZaacboyFsVmo6QPrl28l6LwxkIEv +GGJYvIBW8sfqtGRspjfX5TlNy5IgW/VOwGBdHHsvg/xpRo31PR3HOFw7uPBi7cAr +FiZRLJut7af98EB2UvovZnOh7uIEGPeecQWeOTQfJeWet2FqTzFYd0NUMgqPuJx1 +vLKferP+ajAZLJvVnW1J7Vccx/pm0rMiUJEf0LRb/6XFxx7T2RGjJTi0EzXODTYI +gnLfBBjnolQqw+emf4pJ4pAtly0Gq1KoxTG2QN+wTd4lsCMjnelklFDjejwnl7Uy +vtxzRBAu/hi/AqDkDFf94m6j+edIrjbi9/JDFtQ9EDlyeqPgw0qwi2fwtJyMD45V +fejbXelUSJSzDIdY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAN7Y9G9i4I+ZaslPobE7VL4wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYzMzIzWhgPMjEyMTA1MjAxNzMzMjNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4BEPCiIfiK66Q/qa8k+eqf1Q3qsa6Xuu/fPkpuStXVBShhtXd3eqrM0iT4Xxs420 +Va0vSB3oZ7l86P9zYfa60n6PzRxdYFckYX330aI7L/oFIdaodB/C9szvROI0oLG+ +6RwmIF2zcprH0cTby8MiM7G3v9ykpq27g4WhDC1if2j8giOQL3oHpUaByekZNIHF +dIllsI3RkXmR3xmmxoOxJM1B9MZi7e1CvuVtTGOnSGpNCQiqofehTGwxCN2wFSK8 +xysaWlw48G0VzZs7cbxoXMH9QbMpb4tpk0d+T8JfAPu6uWO9UwCLWWydf0CkmA/+ +D50/xd1t33X9P4FEaPSg5lYbHXzSLWn7oLbrN2UqMLaQrkoEBg/VGvzmfN0mbflw ++T87bJ/VEOVNlG+gepyCTf89qIQVWOjuYMox4sK0PjzZGsYEuYiq1+OUT3vk/e5K +ag1fCcq2Isy4/iwB2xcXrsQ6ljwdk1fc+EmOnjGKrhuOHJY3S+RFv4ToQBsVyYhC +XGaC3EkqIX0xaCpDimxYhFjWhpDXAjG/zJ+hRLDAMCMhl/LPGRk/D1kzSbPmdjpl +lEMK5695PeBvEBTQdBQdOiYgOU3vWU6tzwwHfiM2/wgvess/q0FDAHfJhppbgbb9 +3vgsIUcsvoC5o29JvMsUxsDRvsAfEmMSDGkJoA/X6GECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUgEWm1mZCbGD6ytbwk2UU1aLaOUUwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBb4+ABTGBGwxK1U/q4g8JDqTQM +1Wh8Oz8yAk4XtPJMAmCctxbd81cRnSnePWw/hxViLVtkZ/GsemvXfqAQyOn1coN7 +QeYSw+ZOlu0j2jEJVynmgsR7nIRqE7QkCyZAU+d2FTJUfmee+IiBiGyFGgxz9n7A +JhBZ/eahBbiuoOik/APW2JWLh0xp0W0GznfJ8lAlaQTyDa8iDXmVtbJg9P9qzkvl +FgPXQttzEOyooF8Pb2LCZO4kUz+1sbU7tHdr2YE+SXxt6D3SBv+Yf0FlvyWLiqVk +GDEOlPPTDSjAWgKnqST8UJ0RDcZK/v1ixs7ayqQJU0GUQm1I7LGTErWXHMnCuHKe +UKYuiSZwmTcJ06NgdhcCnGZgPq13ryMDqxPeltQc3n5eO7f1cL9ERYLDLOzm6A9P +oQ3MfcVOsbHgGHZWaPSeNrQRN9xefqBXH0ZPasgcH9WJdsLlEjVUXoultaHOKx3b +UCCb+d3EfqF6pRT488ippOL6bk7zNubwhRa/+y4wjZtwe3kAX78ACJVcjPobH9jZ +ErySads5zdQeaoee5wRKdp3TOfvuCe4bwLRdhOLCHWzEcXzY3g/6+ppLvNom8o+h +Bh5X26G6KSfr9tqhQ3O9IcbARjnuPbvtJnoPY0gz3EHHGPhy0RNW8i2gl3nUp0ah +PtjwbKW0hYAhIttT0Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQQRBQTs6Y3H1DDbpHGta3lzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDYxMTAwMTI0M1oYDzIxMjEwNjExMDExMjQzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEs0942Xj4m/gKA+WA6F5h +AHYuek9eGpzTRoLJddM4rEV1T3eSueytMVKOSlS3Ub9IhyQrH2D8EHsLYk9ktnGR +pATk0kCYTqFbB7onNo070lmMJmGT/Q7NgwC8cySChFxbo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ20iKBKiNkcbIZRu0y1uoF1yJTEzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwYv0wTSrpQTaPaarfLN8Xcqrqu3hzl07n +FrESIoRw6Cx77ZscFi2/MV6AFyjCV/TlAjEAhpwJ3tpzPXpThRML8DMJYZ3YgMh3 +CMuLqhPpla3cL0PhybrD27hJWl29C4el6aMO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrDCCAjOgAwIBAgIQGcztRyV40pyMKbNeSN+vXTAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjEyMzE1NTZaGA8yMTIxMDUyMjAwMTU1NlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyB1cy1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfDcv+GGRESD9wT+I5YIPRsD3L+/jsiIis +Tr7t9RSbFl+gYpO7ZbDXvNbV5UGOC5lMJo/SnqFRTC6vL06NF7qOHfig3XO8QnQz +6T5uhhrhnX2RSY3/10d2kTyHq3ZZg3+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFLDyD3PRyNXpvKHPYYxjHXWOgfPnMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNnADBkAjB20HQp6YL7CqYD82KaLGzgw305aUKw2aMrdkBR29J183jY +6Ocj9+Wcif9xnRMS+7oCMAvrt03rbh4SU9BohpRUcQ2Pjkh7RoY0jDR4Xq4qzjNr +5UFr3BXpFvACxXF51BksGQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQeKbS5zvtqDvRtwr5H48cAjAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTcxOTU1WhgPMjEyMTA1MjAxODE5NTVaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgbWUtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEKjgUaAPmUlRMEQdBC7BScAGosJ1zRV +LDd38qTBjzgmwBfQJ5ZfGIvyEK5unB09MB4e/3qqK5I/L6Qn5Px/n5g4dq0c7MQZ +u7G9GBYm90U3WRJBf7lQrPStXaRnS4A/O6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUNKcAbGEIn03/vkwd8g6jNyiRdD4wDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMHIeTrjenCSYuGC6txuBt/0ZwnM/ciO9kHGWVCoK8QLs +jGghb5/YSFGZbmQ6qpGlSAIwVOQgdFfTpEfe5i+Vs9frLJ4QKAfc27cTNYzRIM0I +E+AJgK4C4+DiyyMzOpiCfmvq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQSFkEUzu9FYgC5dW+5lnTgjANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA2MTEwMDA4MzZaGA8yMTIxMDYxMTAxMDgzNlow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDx +my5Qmd8zdwaI/KOKV9Xar9oNbhJP5ED0JCiigkuvCkg5qM36klszE8JhsUj40xpp +vQw9wkYW4y+C8twBpzKGBvakqMnoaVUV7lOCKx0RofrnNwkZCboTBB4X/GCZ3fIl +YTybS7Ehi1UuiaZspIT5A2jidoA8HiBPk+mTg1UUkoWS9h+MEAPa8L4DY6fGf4pO +J1Gk2cdePuNzzIrpm2yPto+I8MRROwZ3ha7ooyymOXKtz2c7jEHHJ314boCXAv9G +cdo27WiebewZkHHH7Zx9iTIVuuk2abyVSzvLVeGv7Nuy4lmSqa5clWYqWsGXxvZ2 +0fZC5Gd+BDUMW1eSpW7QDTk3top6x/coNoWuLSfXiC5ZrJkIKimSp9iguULgpK7G +abMMN4PR+O+vhcB8E879hcwmS2yd3IwcPTl3QXxufqeSV58/h2ibkqb/W4Bvggf6 +5JMHQPlPHOqMCVFIHP1IffIo+Of7clb30g9FD2j3F4qgV3OLwEDNg/zuO1DiAvH1 +L+OnmGHkfbtYz+AVApkAZrxMWwoYrwpauyBusvSzwRE24vLTd2i80ZDH422QBLXG +rN7Zas8rwIiBKacJLYtBYETw8mfsNt8gb72aIQX6cZOsphqp6hUtKaiMTVgGazl7 +tBXqbB+sIv3S9X6bM4cZJKkMJOXbnyCCLZFYv8TurwIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBTOVtaS1b/lz6yJDvNk65vEastbQTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBABEONg+TmMZM/PrYGNAfB4S41zp1 +3CVjslZswh/pC4kgXSf8cPJiUOzMwUevuFQj7tCqxQtJEygJM2IFg4ViInIah2kh +xlRakEGGw2dEVlxZAmmLWxlL1s1lN1565t5kgVwM0GVfwYM2xEvUaby6KDVJIkD3 +aM6sFDBshvVA70qOggM6kU6mwTbivOROzfoIQDnVaT+LQjHqY/T+ok6IN0YXXCWl +Favai8RDjzLDFwXSRvgIK+1c49vlFFY4W9Efp7Z9tPSZU1TvWUcKdAtV8P2fPHAS +vAZ+g9JuNfeawhEibjXkwg6Z/yFUueQCQOs9TRXYogzp5CMMkfdNJF8byKYqHscs +UosIcETnHwqwban99u35sWcoDZPr6aBIrz7LGKTJrL8Nis8qHqnqQBXu/fsQEN8u +zJ2LBi8sievnzd0qI0kaWmg8GzZmYH1JCt1GXSqOFkI8FMy2bahP7TUQR1LBUKQ3 +hrOSqldkhN+cSAOnvbQcFzLr+iEYEk34+NhcMIFVE+51KJ1n6+zISOinr6mI3ckX +6p2tmiCD4Shk2Xx/VTY/KGvQWKFcQApWezBSvDNlGe0yV71LtLf3dr1pr4ofo7cE +rYucCJ40bfxEU/fmzYdBF32xP7AOD9U0FbOR3Mcthc6Z6w20WFC+zru8FGY08gPf +WT1QcNdw7ntUJP/w +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQARky6+5PNFRkFVOp3Ob1CTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNTIzMTg0MTI4WhgPMjEyMjA1MjMxOTQxMjdaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNVGL5oF7cfIBxKyWd2PVK/S5yQfaJY3 +QFHWvEdt6951n9JhiiPrHzfVHsxZp1CBjILRMzjgRbYWmc8qRoLkgGE7htGdwudJ +Fa/WuKzO574Prv4iZXUnVGTboC7JdvKbh6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgDeIIEKynwUbNXApdIPnmRWieZwwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMEOOJfucrST+FxuqJkMZyCM3gWGZaB+/w6+XUAJC6hFM +uSTY0F44/bERkA4XhH+YGAIxAIpJQBakCA1/mXjsTnQ+0El9ty+LODp8ibkn031c +8DKDS7pR9UK7ZYdR6zFg3ZCjQw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQJvkWUcYLbnxtuwnyjMmntDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMjI2MTJaGA8yMTIxMDUyNTIzMjYxMlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBldS13ZXN0LTMgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARENn8uHCyjn1dFax4OeXxvbV861qsXFD9G +DshumTmFzWWHN/69WN/AOsxy9XN5S7Cgad4gQgeYYYgZ5taw+tFo/jQvCLY//uR5 +uihcLuLJ78opvRPvD9kbWZ6oXfBtFkWjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKiK3LpoF+gDnqPldGSwChBPCYciMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA+7qfvRlnvF1Aosyp9HzxxCbN7VKu+QXXPhLEBWa5oeWW +UOcifunf/IVLC4/FGCsLAjEAte1AYp+iJyOHDB8UYkhBE/1sxnFaTiEPbvQBU0wZ +SuwWVLhu2wWDuSW+K7tTuL8p +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAKeDpqX5WFCGNo94M4v69sUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMTgzM1oYDzIwNjEwNTI1MjMxODMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcKOTEMTfzvs4H +WtJR8gI7GXN6xesulWtZPv21oT+fLGwJ+9Bv8ADCGDDrDxfeH/HxJmzG9hgVAzVn +4g97Bn7q07tGZM5pVi96/aNp11velZT7spOJKfJDZTlGns6DPdHmx48whpdO+dOb +6+eR0VwCIv+Vl1fWXgoACXYCoKjhxJs+R+fwY//0JJ1YG8yjZ+ghLCJmvlkOJmE1 +TCPUyIENaEONd6T+FHGLVYRRxC2cPO65Jc4yQjsXvvQypoGgx7FwD5voNJnFMdyY +754JGPOOe/SZdepN7Tz7UEq8kn7NQSbhmCsgA/Hkjkchz96qN/YJ+H/okiQUTNB0 +eG9ogiVFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjayw9Y +MjbxfF14XAhMM2VPl0PfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAAtmx6d9+9CWlMoU0JCirtp4dSS41bBfb9Oor6GQ8WIr2LdfZLL6uES/ubJPE +1Sh5Vu/Zon5/MbqLMVrfniv3UpQIof37jKXsjZJFE1JVD/qQfRzG8AlBkYgHNEiS +VtD4lFxERmaCkY1tjKB4Dbd5hfhdrDy29618ZjbSP7NwAfnwb96jobCmMKgxVGiH +UqsLSiEBZ33b2hI7PJ6iTJnYBWGuiDnsWzKRmheA4nxwbmcQSfjbrNwa93w3caL2 +v/4u54Kcasvcu3yFsUwJygt8z43jsGAemNZsS7GWESxVVlW93MJRn6M+MMakkl9L +tWaXdHZ+KUV7LhfYLb0ajvb40w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQJ5oxPEjefCsaESSwrxk68DANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjExNzA1WhgPMjA2MjA2MDYyMjE3MDVaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTQt5eX +g+VP3BjO9VBkWJhE0GfLrU/QIk32I6WvrnejayTrlup9H1z4QWlXF7GNJrqScRMY +KhJHlcP05aPsx1lYco6pdFOf42ybXyWHHJdShj4A5glU81GTT+VrXGzHSarLmtua +eozkQgPpDsSlPt0RefyTyel7r3Cq+5K/4vyjCTcIqbfgaGwTU36ffjM1LaPCuE4O +nINMeD6YuImt2hU/mFl20FZ+IZQUIFZZU7pxGLqTRz/PWcH8tDDxnkYg7tNuXOeN +JbTpXrw7St50/E9ZQ0llGS+MxJD8jGRAa/oL4G/cwnV8P2OEPVVkgN9xDDQeieo0 +3xkzolkDkmeKOnUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +bwu8635iQGQMRanekesORM8Hkm4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAgN6LE9mUgjsj6xGCX1afYE69fnmCjjb0rC6eEe1mb/QZNcyw4XBIW +6+zTXo4mjZ4ffoxb//R0/+vdTE7IvaLgfAZgFsLKJCtYDDstXZj8ujQnGR9Pig3R +W+LpNacvOOSJSawNQq0Xrlcu55AU4buyD5VjcICnfF1dqBMnGTnh27m/scd/ZMx/ +kapHZ/fMoK2mAgSX/NvUKF3UkhT85vSSM2BTtET33DzCPDQTZQYxFBa4rFRmFi4c +BLlmIReiCGyh3eJhuUUuYAbK6wLaRyPsyEcIOLMQmZe1+gAFm1+1/q5Ke9ugBmjf +PbTWjsi/lfZ5CdVAhc5lmZj/l5aKqwaS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAKKPTYKln9L4NTx9dpZGUjowCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIxMjI1NTIxWhgPMjEyMTA1MjEyMzU1MjFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/owTReDvaRqdmbtTzXbyRmEpKCETNj6O +hZMKH0F8oU9Tmn8RU7kQQj6xUKEyjLPrFBN7c+26TvrVO1KmJAvbc8bVliiJZMbc +C0yV5PtJTalvlMZA1NnciZuhxaxrzlK1o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBT4i5HaoHtrs7Mi8auLhMbKM1XevDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAK9A+8/lFdX4XJKgfP+ZLy5ySXC2E0Spoy12Gv2GdUEZ +p1G7c1KbWVlyb1d6subzkQIwKyH0Naf/3usWfftkmq8SzagicKz5cGcEUaULq4tO +GzA/AMpr63IDBAqkZbMDTCmH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQTgIvwTDuNWQo0Oe1sOPQEzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjEwNjM4WhgPMjEyMTA1MjQyMjA2MzhaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtbm9ydGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJuzXLU8q6WwSKXBvx8BbdIi3mPhb7Xo +rNJBfuMW1XRj5BcKH1ZoGaDGw+BIIwyBJg8qNmCK8kqIb4cH8/Hbo3Y+xBJyoXq/ +cuk8aPrxiNoRsKWwiDHCsVxaK9L7GhHHAqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUYgcsdU4fm5xtuqLNppkfTHM2QMYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMQDz/Rm89+QJOWJecYAmYcBWCcETASyoK1kbr4vw7Hsg +7Ew3LpLeq4IRmTyuiTMl0gMCMAa0QSjfAnxBKGhAnYxcNJSntUyyMpaXzur43ec0 +3D8npJghwC4DuICtKEkQiI5cSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAORIGqQXLTcbbYT2upIsSnQwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA1MjMxODM0MjJaGA8yMTIyMDUyMzE5MzQyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPKukwsW2s/h +1k+Hf65pOP0knVBnOnMQyT1mopp2XHGdXznj9xS49S30jYoUnWccyXgD983A1bzu +w4fuJRHg4MFdz/NWTgXvy+zy0Roe83OPIJjUmXnnzwUHQcBa9vl6XUO65iQ3pbSi +fQfNDFXD8cvuXbkezeADoy+iFAlzhXTzV9MD44GTuo9Z3qAXNGHQCrgRSCL7uRYt +t1nfwboCbsVRnElopn2cTigyVXE62HzBUmAw1GTbAZeFAqCn5giBWYAfHwTUldRL +6eEa6atfsS2oPNus4ZENa1iQxXq7ft+pMdNt0qKXTCZiiCZjmLkY0V9kWwHTRRF8 +r+75oSL//3di43QnuSCgjwMRIeWNtMud5jf3eQzSBci+9njb6DrrSUbx7blP0srg +94/C/fYOp/0/EHH34w99Th14VVuGWgDgKahT9/COychLOubXUT6vD1As47S9KxTv +yYleVKwJnF9cVjepODN72fNlEf74BwzgSIhUmhksmZSeJBabrjSUj3pdyo/iRZN/ +CiYz9YPQ29eXHPQjBZVIUqWbOVfdwsx0/Xu5T1e7yyXByQ3/oDulahtcoKPAFQ3J +ee6NJK655MdS7pM9hJnU2Rzu3qZ/GkM6YK7xTlMXVouPUZov/VbiaCKbqYDs8Dg+ +UKdeNXAT6+BMleGQzly1X7vjhgeA8ugVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFJdaPwpCf78UolFTEn6GO85/QwUIMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAWkxHIT3mers5YnZRSVjmpxCLivGj1jMB9VYC +iKqTAeIvD0940L0YaZgivQll5pue8UUcQ6M2uCdVVAsNJdmQ5XHIYiGOknYPtxzO +aO+bnZp7VIZw/vJ49hvH6RreA2bbxYMZO/ossYdcWsWbOKHFrRmAw0AhtK/my51g +obV7eQg+WmlE5Iqc75ycUsoZdc3NimkjBi7LQoNP1HMvlLHlF71UZhQDdq+/WdV7 +0zmg+epkki1LjgMmuPyb+xWuYkFKT1/faX+Xs62hIm5BY+aI4if4RuQ+J//0pOSs +UajrjTo+jLGB8A96jAe8HaFQenbwMjlaHRDAF0wvbkYrMr5a6EbneAB37V05QD0Y +Rh4L4RrSs9DX2hbSmS6iLDuPEjanHKzglF5ePEvnItbRvGGkynqDVlwF+Bqfnw8l +0i8Hr1f1/LP1c075UjkvsHlUnGgPbLqA0rDdcxF8Fdlv1BunUjX0pVlz10Ha5M6P +AdyWUOneOfaA5G7jjv7i9qg3r99JNs1/Lmyg/tV++gnWTAsSPFSSEte81kmPhlK3 +2UtAO47nOdTtk+q4VIRAwY1MaOR7wTFZPfer1mWs4RhKNu/odp8urEY87iIzbMWT +QYO/4I6BGj9rEWNGncvR5XTowwIthMCj2KWKM3Z/JxvjVFylSf+s+FFfO1bNIm6h +u3UBpZI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQenQbcP/Zbj9JxvZ+jXbRnTAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMTA1MjEyMjMzMjRaGA8yMTIxMDUyMTIzMzMyNFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATlBHiEM9LoEb1Hdnd5j2VpCDOU +5nGuFoBD8ROUCkFLFh5mHrHfPXwBc63heW9WrP3qnDEm+UZEUvW7ROvtWCTPZdLz +Z4XaqgAlSqeE2VfUyZOZzBSgUUJk7OlznXfkCMOjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDT/ThjQZl42Nv/4Z/7JYaPNMly2MA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAnZWmSgpEbmq+oiCa13l5aGmxSlfp9h12Orvw +Dq/W5cENJz891QD0ufOsic5oGq1JAjEAp5kSJj0MxJBTHQze1Aa9gG4sjHBxXn98 +4MP1VGsQuhfndNHQb4V0Au7OWnOeiobq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAMgnyikWz46xY6yRgiYwZ3swDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NDkxMloYDzIwNjEwNTIwMTc0OTEyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi8JYOc9cYSgZH +gYPxLk6Xcc7HqzamvsnjYU98Dcb98y6iDqS46Ra2Ne02MITtU5MDL+qjxb8WGDZV +RUA9ZS69tkTO3gldW8QdiSh3J6hVNJQW81F0M7ZWgV0gB3n76WCmfT4IWos0AXHM +5v7M/M4tqVmCPViQnZb2kdVlM3/Xc9GInfSMCgNfwHPTXl+PXX+xCdNBePaP/A5C +5S0oK3HiXaKGQAy3K7VnaQaYdiv32XUatlM4K2WS4AMKt+2cw3hTCjlmqKRHvYFQ +veWCXAuc+U5PQDJ9SuxB1buFJZhT4VP3JagOuZbh5NWpIbOTxlAJOb5pGEDuJTKi +1gQQQVEFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNXm+N87 +OFxK9Af/bjSxDCiulGUzMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAkqIbkgZ45spvrgRQ6n9VKzDLvNg+WciLtmVrqyohwwJbj4pYvWwnKQCkVc7c +hUOSBmlSBa5REAPbH5o8bdt00FPRrD6BdXLXhaECKgjsHe1WW08nsequRKD8xVmc +8bEX6sw/utBeBV3mB+3Zv7ejYAbDFM4vnRsWtO+XqgReOgrl+cwdA6SNQT9oW3e5 +rSQ+VaXgJtl9NhkiIysq9BeYigxqS/A13pHQp0COMwS8nz+kBPHhJTsajHCDc8F4 +HfLi6cgs9G0gaRhT8FCH66OdGSqn196sE7Y3bPFFFs/3U+vxvmQgoZC6jegQXAg5 +Prxd+VNXtNI/azitTysQPumH7A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAO8bekN7rUReuNPG8pSTKtEwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMjM0N1oYDzIwNjEwNTIxMjMyMzQ3WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTTYds +Tray+Q9VA5j5jTh5TunHKFQzn68ZbOzdqaoi/Rq4ohfC0xdLrxCpfqn2TGDHN6Zi +2qGK1tWJZEd1H0trhzd9d1CtGK+3cjabUmz/TjSW/qBar7e9MA67/iJ74Gc+Ww43 +A0xPNIWcL4aLrHaLm7sHgAO2UCKsrBUpxErOAACERScVYwPAfu79xeFcX7DmcX+e +lIqY16pQAvK2RIzrekSYfLFxwFq2hnlgKHaVgZ3keKP+nmXcXmRSHQYUUr72oYNZ +HcNYl2+gxCc9ccPEHM7xncVEKmb5cWEWvVoaysgQ+osi5f5aQdzgC2X2g2daKbyA +XL/z5FM9GHpS5BJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FBDAiJ7Py9/A9etNa/ebOnx5l5MGMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEALMh/+81fFPdJV/RrJUeoUvFCGMp8iaANu97NpeJyKitNOv7RoeVP +WjivS0KcCqZaDBs+p6IZ0sLI5ZH098LDzzytcfZg0PsGqUAb8a0MiU/LfgDCI9Ee +jsOiwaFB8k0tfUJK32NPcIoQYApTMT2e26lPzYORSkfuntme2PTHUnuC7ikiQrZk +P+SZjWgRuMcp09JfRXyAYWIuix4Gy0eZ4rpRuaTK6mjAb1/LYoNK/iZ/gTeIqrNt +l70OWRsWW8jEmSyNTIubGK/gGGyfuZGSyqoRX6OKHESkP6SSulbIZHyJ5VZkgtXo +2XvyRyJ7w5pFyoofrL3Wv0UF8yt/GDszmg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAMDk/F+rrhdn42SfE+ghPC8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIyNTEyMloYDzIxMjEwNTIxMjM1MTIyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2twMALVg9vRVu +VNqsr6N8thmp3Dy8jEGTsm3GCQ+C5P2YcGlD/T/5icfWW84uF7Sx3ezcGlvsqFMf +Ukj9sQyqtz7qfFFugyy7pa/eH9f48kWFHLbQYm9GEgbYBIrWMp1cy3vyxuMCwQN4 +DCncqU+yNpy0CprQJEha3PzY+3yJOjDQtc3zr99lyECCFJTDUucxHzyQvX89eL74 +uh8la0lKH3v9wPpnEoftbrwmm5jHNFdzj7uXUHUJ41N7af7z7QUfghIRhlBDiKtx +5lYZemPCXajTc3ryDKUZC/b+B6ViXZmAeMdmQoPE0jwyEp/uaUcdp+FlUQwCfsBk +ayPFEApTWgPiku2isjdeTVmEgL8bJTDUZ6FYFR7ZHcYAsDzcwHgIu3GGEMVRS3Uf +ILmioiyly9vcK4Sa01ondARmsi/I0s7pWpKflaekyv5boJKD/xqwz9lGejmJHelf +8Od2TyqJScMpB7Q8c2ROxBwqwB72jMCEvYigB+Wnbb8RipliqNflIGx938FRCzKL +UQUBmNAznR/yRRL0wHf9UAE/8v9a09uZABeiznzOFAl/frHpgdAbC00LkFlnwwgX +g8YfEFlkp4fLx5B7LtoO6uVNFVimLxtwirpyKoj3G4M/kvSTux8bTw0heBCmWmKR +57MS6k7ODzbv+Kpeht2hqVZCNFMxoQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRuMnDhJjoj7DcKALj+HbxEqj3r6jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBALSnXfx72C3ldhBP5kY4Mo2DDaGQ8FGpTOOiD95d +0rf7I9LrsBGVqu/Nir+kqqP80PB70+Jy9fHFFigXwcPBX3MpKGxK8Cel7kVf8t1B +4YD6A6bqlzP+OUL0uGWfZpdpDxwMDI2Flt4NEldHgXWPjvN1VblEKs0+kPnKowyg +jhRMgBbD/y+8yg0fIcjXUDTAw/+INcp21gWaMukKQr/8HswqC1yoqW9in2ijQkpK +2RB9vcQ0/gXR0oJUbZQx0jn0OH8Agt7yfMAnJAdnHO4M3gjvlJLzIC5/4aGrRXZl +JoZKfJ2fZRnrFMi0nhAYDeInoS+Rwx+QzaBk6fX5VPyCj8foZ0nmqvuYoydzD8W5 +mMlycgxFqS+DUmO+liWllQC4/MnVBlHGB1Cu3wTj5kgOvNs/k+FW3GXGzD3+rpv0 +QTLuwSbMr+MbEThxrSZRSXTCQzKfehyC+WZejgLb+8ylLJUA10e62o7H9PvCrwj+ +ZDVmN7qj6amzvndCP98sZfX7CFZPLfcBd4wVIjHsFjSNEwWHOiFyLPPG7cdolGKA +lOFvonvo4A1uRc13/zFeP0Xi5n5OZ2go8aOOeGYdI2vB2sgH9R2IASH/jHmr0gvY +0dfBCcfXNgrS0toq0LX/y+5KkKOxh52vEYsJLdhqrveuZhQnsFEm/mFwjRXkyO7c +2jpC +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGADCCA+igAwIBAgIQYe0HgSuFFP9ivYM2vONTrTANBgkqhkiG9w0BAQwFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MzMyMVoYDzIxMjEwNTE5MTkzMzIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuO7QPKfPMTo2 +POQWvzDLwi5f++X98hGjORI1zkN9kotCYH5pAzSBwBPoMNaIfedgmsIxGHj2fq5G +4oXagNhNuGP79Zl6uKW5H7S74W7aWM8C0s8zuxMOI4GZy5h2IfQk3m/3AzZEX5w8 +UtNPkzo2feDVOkerHT+j+vjXgAxZ4wHnuMDcRT+K4r9EXlAH6X9b/RO0JlfEwmNz +xlqqGxocq9qRC66N6W0HF2fNEAKP84n8H80xcZBOBthQORRi8HSmKcPdmrvwCuPz +M+L+j18q6RAVaA0ABbD0jMWcTf0UvjUfBStn5mvu/wGlLjmmRkZsppUTRukfwqXK +yltUsTq0tOIgCIpne5zA4v+MebbR5JBnsvd4gdh5BI01QH470yB7BkUefZ9bobOm +OseAAVXcYFJKe4DAA6uLDrqOfFSxV+CzVvEp3IhLRaik4G5MwI/h2c/jEYDqkg2J +HMflxc2gcSMdk7E5ByLz5f6QrFfSDFk02ZJTs4ssbbUEYohht9znPMQEaWVqATWE +3n0VspqZyoBNkH/agE5GiGZ/k/QyeqzMNj+c9kr43Upu8DpLrz8v2uAp5xNj3YVg +ihaeD6GW8+PQoEjZ3mrCmH7uGLmHxh7Am59LfEyNrDn+8Rq95WvkmbyHSVxZnBmo +h/6O3Jk+0/QhIXZ2hryMflPcYWeRGH0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU2eFK7+R3x/me8roIBNxBrplkM6EwDgYDVR0PAQH/BAQDAgGG +MA0GCSqGSIb3DQEBDAUAA4ICAQB5gWFe5s7ObQFj1fTO9L6gYgtFhnwdmxU0q8Ke +HWCrdFmyXdC39qdAFOwM5/7fa9zKmiMrZvy9HNvCXEp4Z7z9mHhBmuqPZQx0qPgU +uLdP8wGRuWryzp3g2oqkX9t31Z0JnkbIdp7kfRT6ME4I4VQsaY5Y3mh+hIHOUvcy +p+98i3UuEIcwJnVAV9wTTzrWusZl9iaQ1nSYbmkX9bBssJ2GmtW+T+VS/1hJ/Q4f +AlE3dOQkLFoPPb3YRWBHr2n1LPIqMVwDNAuWavRA2dSfaLl+kzbn/dua7HTQU5D4 +b2Fu2vLhGirwRJe+V7zdef+tI7sngXqjgObyOeG5O2BY3s+um6D4fS0Th3QchMO7 +0+GwcIgSgcjIjlrt6/xJwJLE8cRkUUieYKq1C4McpZWTF30WnzOPUzRzLHkcNzNA +0A7sKMK6QoYWo5Rmo8zewUxUqzc9oQSrYADP7PEwGncLtFe+dlRFx+PA1a+lcIgo +1ZGfXigYtQ3VKkcknyYlJ+hN4eCMBHtD81xDy9iP2MLE41JhLnoB2rVEtewO5diF +7o95Mwl84VMkLhhHPeGKSKzEbBtYYBifHNct+Bst8dru8UumTltgfX6urH3DN+/8 +JF+5h3U8oR2LL5y76cyeb+GWDXXy9zoQe2QvTyTy88LwZq1JzujYi2k8QiLLhFIf +FEv9Bg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsDCCAjagAwIBAgIRAMgApnfGYPpK/fD0dbN2U4YwCgYIKoZIzj0EAwMwgZcx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwnQW1h +em9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMCAXDTIxMDUxOTE4MzgxMVoYDzIxMjEwNTE5MTkzODExWjCBlzELMAkG +A1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzAR +BgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6b24g +UkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0 +bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfEWl6d4qSuIoECdZPp+39LaKsfsX7 +THs3/RrtT0+h/jl3bjZ7Qc68k16x+HGcHbaayHfqD0LPdzH/kKtNSfQKqemdxDQh +Z4pwkixJu8T1VpXZ5zzCvBXCl75UqgEFS92jQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFFPrSNtWS5JU+Tvi6ABV231XbjbEMA4GA1UdDwEB/wQEAwIBhjAK +BggqhkjOPQQDAwNoADBlAjEA+a7hF1IrNkBd2N/l7IQYAQw8chnRZDzh4wiGsZsC +6A83maaKFWUKIb3qZYXFSi02AjAbp3wxH3myAmF8WekDHhKcC2zDvyOiKLkg9Y6v +ZVmyMR043dscQbcsVoacOYv198c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAPhVkIsQ51JFhD2kjFK5uAkwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBldS1jZW50cmFsLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNjA2MjEyOTE3WhgPMjEyMjA2MDYyMjI5MTdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA5xnIEBtG5b2nmbj49UEwQza +yX0844fXjccYzZ8xCDUe9dS2XOUi0aZlGblgSe/3lwjg8fMcKXLObGGQfgIx1+5h +AIBjORis/dlyN5q/yH4U5sjS8tcR0GDGVHrsRUZCo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRK+lSGutXf4DkTjR3WNfv4+KeNFTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJ4NxQ1Gerqr70ZrnUqc62Vl8NNqTzInamCG +Kce3FTsMWbS9qkgrjZkO9QqOcGIw/gIwSLrwUT+PKr9+H9eHyGvpq9/3AIYSnFkb +Cf3dyWPiLKoAtLFwjzB/CkJlsAS1c8dS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQGZH12Q7x41qIh9vDu9ikTjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjIyMjMzWhgPMjEyMTA1MjUyMzIyMzNaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqE47sHXWzdpuqj +JHb+6jM9tDbQLDFnYjDWpq4VpLPZhb7xPNh9gnYYTPKG4avG421EblAHqzy9D2pN +1z90yKbIfUb/Sy2MhQbmZomsObhONEra06fJ0Dydyjswf1iYRp2kwpx5AgkVoNo7 +3dlws73zFjD7ImKvUx2C7B75bhnw2pJWkFnGcswl8fZt9B5Yt95sFOKEz2MSJE91 +kZlHtya19OUxZ/cSGci4MlOySzqzbGwUqGxEIDlY8I39VMwXaYQ8uXUN4G780VcL +u46FeyRGxZGz2n3hMc805WAA1V5uir87vuirTvoSVREET97HVRGVVNJJ/FM6GXr1 +VKtptybbo81nefYJg9KBysxAa2Ao2x2ry/2ZxwhS6VZ6v1+90bpZA1BIYFEDXXn/ +dW07HSCFnYSlgPtSc+Muh15mdr94LspYeDqNIierK9i4tB6ep7llJAnq0BU91fM2 +JPeqyoTtc3m06QhLf68ccSxO4l8Hmq9kLSHO7UXgtdjfRVaffngopTNk8qK7bIb7 +LrgkqhiQw/PRCZjUdyXL153/fUcsj9nFNe25gM4vcFYwH6c5trd2tUl31NTi1MfG +Mgp3d2dqxQBIYANkEjtBDMy3SqQLIo9EymqmVP8xx2A/gCBgaxvMAsI6FSWRoC7+ +hqJ8XH4mFnXSHKtYMe6WPY+/XZgtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFIkXqTnllT/VJnI2NqipA4XV8rh1MA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAKjSle8eenGeHgT8pltWCw/HzWyQruVKhfYIBfKJd +MhV4EnH5BK7LxBIvpXGsFUrb0ThzSw0fn0zoA9jBs3i/Sj6KyeZ9qUF6b8ycDXd+ +wHonmJiQ7nk7UuMefaYAfs06vosgl1rI7eBHC0itexIQmKh0aX+821l4GEgEoSMf +loMFTLXv2w36fPHHCsZ67ODldgcZbKNnpCTX0YrCwEYO3Pz/L398btiRcWGrewrK +jdxAAyietra8DRno1Zl87685tfqc6HsL9v8rVw58clAo9XAQvT+fmSOFw/PogRZ7 +OMHUat3gu/uQ1M5S64nkLLFsKu7jzudBuoNmcJysPlzIbqJ7vYc82OUGe9ucF3wi +3tbKQ983hdJiTExVRBLX/fYjPsGbG3JtPTv89eg2tjWHlPhCDMMxyRKl6isu2RTq +6VT489Z2zQrC33MYF8ZqO1NKjtyMAMIZwxVu4cGLkVsqFmEV2ScDHa5RadDyD3Ok +m+mqybhvEVm5tPgY6p0ILPMN3yvJsMSPSvuBXhO/X5ppNnpw9gnxpwbjQKNhkFaG +M5pkADZ14uRguOLM4VthSwUSEAr5VQYCFZhEwK+UOyJAGiB/nJz6IxL5XBNUXmRM +Hl8Xvz4riq48LMQbjcVQj0XvH941yPh+P8xOi00SGaQRaWp55Vyr4YKGbV0mEDz1 +r1o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAKwYju1QWxUZpn6D1gOtwgQwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NTM1NFoYDzIxMjEwNTIwMTc1MzU0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCKdBP1U4lqWWkc +Cb25/BKRTsvNVnISiKocva8GAzJyKfcGRa85gmgu41U+Hz6+39K+XkRfM0YS4BvQ +F1XxWT0bNyypuvwCvmYShSTjN1TY0ltncDddahTajE/4MdSOZb/c98u0yt03cH+G +hVwRyT50h0v/UEol50VfwcVAEZEgcQQYhf1IFUFlIvKpmDOqLuFakOnc7c9akK+i +ivST+JO1tgowbnNkn2iLlSSgUWgb1gjaOsNfysagv1RXdlyPw3EyfwkFifAQvF2P +Q0ayYZfYS640cccv7efM1MSVyFHR9PrrDsF/zr2S2sGPbeHr7R/HwLl+S5J/l9N9 +y0rk6IHAWV4dEkOvgpnuJKURwA48iu1Hhi9e4moNS6eqoK2KmY3VFpuiyWcA73nH +GSmyaH+YuMrF7Fnuu7GEHZL/o6+F5cL3mj2SJJhL7sz0ryf5Cs5R4yN9BIEj/f49 +wh84pM6nexoI0Q4wiSFCxWiBpjSmOK6h7z6+2utaB5p20XDZHhxAlmlx4vMuWtjh +XckgRFxc+ZpVMU3cAHUpVEoO49e/+qKEpPzp8Xg4cToKw2+AfTk3cmyyXQfGwXMQ +ZUHNZ3w9ILMWihGCM2aGUsLcGDRennvNmnmin/SENsOQ8Ku0/a3teEzwV9cmmdYz +5iYs1YtgPvKFobY6+T2RXXh+A5kprwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBSyUrsQVnKmA8z6/2Ech0rCvqpNmTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAFlj3IFmgiFz5lvTzFTRizhVofhTJsGr14Yfkuc7 +UrXPuXOwJomd4uot2d/VIeGJpfnuS84qGdmQyGewGTJ9inatHsGZgHl9NHNWRwKZ +lTKTbBiq7aqgtUSFa06v202wpzU+1kadxJJePrbABxiXVfOmIW/a1a4hPNcT3syH +FIEg1+CGsp71UNjBuwg3JTKWna0sLSKcxLOSOvX1fzxK5djzVpEsvQMB4PSAzXca +vENgg2ErTwgTA+4s6rRtiBF9pAusN1QVuBahYP3ftrY6f3ycS4K65GnqscyfvKt5 +YgjtEKO3ZeeX8NpubMbzC+0Z6tVKfPFk/9TXuJtwvVeqow0YMrLLyRiYvK7EzJ97 +rrkxoKnHYQSZ+rH2tZ5SE392/rfk1PJL0cdHnkpDkUDO+8cKsFjjYKAQSNC52sKX +74AVh6wMwxYwVZZJf2/2XxkjMWWhKNejsZhUkTISSmiLs+qPe3L67IM7GyKm9/m6 +R3r8x6NGjhTsKH64iYJg7AeKeax4b2e4hBb6GXFftyOs7unpEOIVkJJgM6gh3mwn +R7v4gwFbLKADKt1vHuerSZMiTuNTGhSfCeDM53XI/mjZl2HeuCKP1mCDLlaO+gZR +Q/G+E0sBKgEX4xTkAc3kgkuQGfExdGtnN2U2ehF80lBHB8+2y2E+xWWXih/ZyIcW +wOx+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQM4C8g5iFRucSWdC8EdqHeDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjEwNTIxMjIyODI2WhgPMjEyMTA1MjEyMzI4MjZaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeTsD/u +6saPiY4Sg0GlJlMXMBltnrcGAEkwq34OKQ0bCXqcoNJ2rcAMmuFC5x9Ho1Y3YzB7 +NO2GpIh6bZaO76GzSv4cnimcv9n/sQSYXsGbPD+bAtnN/RvNW1avt4C0q0/ghgF1 +VFS8JihIrgPYIArAmDtGNEdl5PUrdi9y6QGggbRfidMDdxlRdZBe1C18ZdgERSEv +UgSTPRlVczONG5qcQkUGCH83MMqL5MKQiby/Br5ZyPq6rxQMwRnQ7tROuElzyYzL +7d6kke+PNzG1mYy4cbYdjebwANCtZ2qYRSUHAQsOgybRcSoarv2xqcjO9cEsDiRU +l97ToadGYa4VVERuTaNZxQwrld4mvzpyKuirqZltOqg0eoy8VUsaRPL3dc5aChR0 +dSrBgRYmSAClcR2/2ZCWpXemikwgt031Dsc0A/+TmVurrsqszwbr0e5xqMow9LzO +MI/JtLd0VFtoOkL/7GG2tN8a+7gnLFxpv+AQ0DH5n4k/BY/IyS+H1erqSJhOTQ11 +vDOFTM5YplB9hWV9fp5PRs54ILlHTlZLpWGs3I2BrJwzRtg/rOlvsosqcge9ryai +AKm2j+JBg5wJ19R8oxRy8cfrNTftZePpISaLTyV2B16w/GsSjqixjTQe9LRN2DHk +cC+HPqYyzW2a3pUVyTGHhW6a7YsPBs9yzt6hAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFIqA8QkOs2cSirOpCuKuOh9VDfJfMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOUI90mEIsa+vNJku0iUwdBMnHiO4gm7E +5JloP7JG0xUr7d0hypDorMM3zVDAL+aZRHsq8n934Cywj7qEp1304UF6538ByGdz +tkfacJsUSYfdlNJE9KbA4T+U+7SNhj9jvePpVjdQbhgzxITE9f8CxY/eM40yluJJ +PhbaWvOiRagzo74wttlcDerzLT6Y/JrVpWhnB7IY8HvzK+BwAdaCsBUPC3HF+kth +CIqLq7J3YArTToejWZAp5OOI6DLPM1MEudyoejL02w0jq0CChmZ5i55ElEMnapRX +7GQTARHmjgAOqa95FjbHEZzRPqZ72AtZAWKFcYFNk+grXSeWiDgPFOsq6mDg8DDB +0kfbYwKLFFCC9YFmYzR2YrWw2NxAScccUc2chOWAoSNHiqBbHR8ofrlJSWrtmKqd +YRCXzn8wqXnTS3NNHNccqJ6dN+iMr9NGnytw8zwwSchiev53Fpc1mGrJ7BKTWH0t +ZrA6m32wzpMymtKozlOPYoE5mtZEzrzHEXfa44Rns7XIHxVQSXVWyBHLtIsZOrvW +U5F41rQaFEpEeUQ7sQvqUoISfTUVRNDn6GK6YaccEhCji14APLFIvhRQUDyYMIiM +4vll0F/xgVRHTgDVQ8b8sxdhSYlqB4Wc2Ym41YRz+X2yPqk3typEZBpc4P5Tt1/N +89cEIGdbjsA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQYjbPSg4+RNRD3zNxO1fuKDANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTkyMVoYDzIwNjEwNTI0MjE1OTIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA179eQHxcV0YL +XMkqEmhSBazHhnRVd8yICbMq82PitE3BZcnv1Z5Zs/oOgNmMkOKae4tCXO/41JCX +wAgbs/eWWi+nnCfpQ/FqbLPg0h3dqzAgeszQyNl9IzTzX4Nd7JFRBVJXPIIKzlRf ++GmFsAhi3rYgDgO27pz3ciahVSN+CuACIRYnA0K0s9lhYdddmrW/SYeWyoB7jPa2 +LmWpAs7bDOgS4LlP2H3eFepBPgNufRytSQUVA8f58lsE5w25vNiUSnrdlvDrIU5n +Qwzc7NIZCx4qJpRbSKWrUtbyJriWfAkGU7i0IoainHLn0eHp9bWkwb9D+C/tMk1X +ERZw2PDGkwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSFmR7s +dAblusFN+xhf1ae0KUqhWTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAHsXOpjPMyH9lDhPM61zYdja1ebcMVgfUvsDvt+w0xKMKPhBzYDMs/cFOi1N +Q8LV79VNNfI2NuvFmGygcvTIR+4h0pqqZ+wjWl3Kk5jVxCrbHg3RBX02QLumKd/i +kwGcEtTUvTssn3SM8bgM0/1BDXgImZPC567ciLvWDo0s/Fe9dJJC3E0G7d/4s09n +OMdextcxFuWBZrBm/KK3QF0ByA8MG3//VXaGO9OIeeOJCpWn1G1PjT1UklYhkg61 +EbsTiZVA2DLd1BGzfU4o4M5mo68l0msse/ndR1nEY6IywwpgIFue7+rEleDh6b9d +PYkG1rHVw2I0XDG4o17aOn5E94I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQC6W4HFghUkkgyQw14a6JljANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyMzE4MTYzMloYDzIwNjIwNTIzMTkxNjMyWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM/t4FV2R9Nx +UQG203UY83jInTa/6TMq0SPyg617FqYZxvz2kkx09x3dmxepUg9ttGMlPgjsRZM5 +LCFEi1FWk+hxHzt7vAdhHES5tdjwds3aIkgNEillmRDVrUsbrDwufLaa+MMDO2E1 +wQ/JYFXw16WBCCi2g1EtyQ2Xp+tZDX5IWOTnvhZpW8vVDptZ2AcJ5rMhfOYO3OsK +5EF0GGA5ldzuezP+BkrBYGJ4wVKGxeaq9+5AT8iVZrypjwRkD7Y5CurywK3+aBwm +s9Q5Nd8t45JCOUzYp92rFKsCriD86n/JnEvgDfdP6Hvtm0/DkwXK40Wz2q0Zrd0k +mjP054NRPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRR7yqd +SfKcX2Q8GzhcVucReIpewTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEszBRDwXcZyNm07VcFwI1Im94oKwKccuKYeJEsizTBsVon8VpEiMwDs+yGu +3p8kBhvkLwWybkD/vv6McH7T5b9jDX2DoOudqYnnaYeypsPH/00Vh3LvKagqzQza +orWLx+0tLo8xW4BtU+Wrn3JId8LvAhxyYXTn9bm+EwPcStp8xGLwu53OPD1RXYuy +uu+3ps/2piP7GVfou7H6PRaqbFHNfiGg6Y+WA0HGHiJzn8uLmrRJ5YRdIOOG9/xi +qTmAZloUNM7VNuurcMM2hWF494tQpsQ6ysg2qPjbBqzlGoOt3GfBTOZmqmwmqtam +K7juWM/mdMQAJ3SMlE5wI8nVdx4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAL9SdzVPcpq7GOpvdGoM80IwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTY1ODA3WhgPMjEyMTA1MjAxNzU4MDdaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJWDgXebvwjR+Ce+hxKOLbnsfN5W5dOlP +Zn8kwWnD+SLkU81Eac/BDJsXGrMk6jFD1vg16PEkoSevsuYWlC8xR6FmT6F6pmeh +fsMGOyJpfK4fyoEPhKeQoT23lFIc5Orjo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBSVNAN1CHAz0eZ77qz2adeqjm31TzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAMlQeHbcjor49jqmcJ9gRLWdEWpXG8thIf6zfYQ/OEAg +d7GDh4fR/OUk0VfjsBUN/gIwZB0bGdXvK38s6AAE/9IT051cz/wMe9GIrX1MnL1T +1F5OqnXJdiwfZRRTHsRQ/L00 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQalr16vDfX4Rsr+gfQ4iVFDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjEyNTIzWhgPMjEyMjA2MDYyMjI1MjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHbFg7 +2VhZor1YNtez0VlNFaobS3PwOMcEn45BE3y7HONnElIIWXGQa0811M8V2FnyqnE8 +Z5aO1EuvijvWf/3D8DPZkdmAkIfh5hlZYY6Aatr65kEOckwIAm7ZZzrwFogYuaFC +z/q0CW+8gxNK+98H/zeFx+IxiVoPPPX6UlrLvn+R6XYNERyHMLNgoZbbS5gGHk43 +KhENVv3AWCCcCc85O4rVd+DGb2vMVt6IzXdTQt6Kih28+RGph+WDwYmf+3txTYr8 +xMcCBt1+whyCPlMbC+Yn/ivtCO4LRf0MPZDRQrqTTrFf0h/V0BGEUmMGwuKgmzf5 +Kl9ILdWv6S956ioZin2WgAxhcn7+z//sN++zkqLreSf90Vgv+A7xPRqIpTdJ/nWG +JaAOUofBfsDsk4X4SUFE7xJa1FZAiu2lqB/E+y7jnWOvFRalzxVJ2Y+D/ZfUfrnK +4pfKtyD1C6ni1celrZrAwLrJ3PoXPSg4aJKh8+CHex477SRsGj8KP19FG8r0P5AG +8lS1V+enFCNvT5KqEBpDZ/Y5SQAhAYFUX+zH4/n4ql0l/emS+x23kSRrF+yMkB9q +lhC/fMk6Pi3tICBjrDQ8XAxv56hfud9w6+/ljYB2uQ1iUYtlE3JdIiuE+3ws26O8 +i7PLMD9zQmo+sVi12pLHfBHQ6RRHtdVRXbXRAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFBFot08ipEL9ZUXCG4lagmF53C0/MA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAi2mcZi6cpaeqJ10xzMY0F3L2eOKYnlEQ +h6QyhmNKCUF05q5u+cok5KtznzqMwy7TFOZtbVHl8uUX+xvgq/MQCxqFAnuStBXm +gr2dg1h509ZwvTdk7TDxGdftvPCfnPNJBFbMSq4CZtNcOFBg9Rj8c3Yj+Qvwd56V +zWs65BUkDNJrXmxdvhJZjUkMa9vi/oFN+M84xXeZTaC5YDYNZZeW9706QqDbAVES +5ulvKLavB8waLI/lhRBK5/k0YykCMl0A8Togt8D1QsQ0eWWbIM8/HYJMPVFhJ8Wj +vT1p/YVeDA3Bo1iKDOttgC5vILf5Rw1ZEeDxjf/r8A7VS13D3OLjBmc31zxRTs3n +XvHKP9MieQHn9GE44tEYPjK3/yC6BDFzCBlvccYHmqGb+jvDEXEBXKzimdC9mcDl +f4BBQWGJBH5jkbU9p6iti19L/zHhz7qU6UJWbxY40w92L9jS9Utljh4A0LCTjlnR +NQUgjnGC6K+jkw8hj0LTC5Ip87oqoT9w7Av5EJ3VJ4hcnmNMXJJ1DkWYdnytcGpO +DMVITQzzDZRwhbitCVPHagTN2wdi9TEuYE33J0VmFeTc6FSI50wP2aOAZ0Q1/8Aj +bxeM5jS25eaHc2CQAuhrc/7GLnxOcPwdWQb2XWT8eHudhMnoRikVv/KSK3mf6om4 +1YfpdH2jp30= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQTDc+UgTRtYO7ZGTQ8UWKDDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjI0NjI0WhgPMjA2MTA1MjEyMzQ2MjRaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1oGtthQ1YiVIC2 +i4u4swMAGxAjc/BZp0yq0eP5ZQFaxnxs7zFAPabEWsrjeDzrRhdVO0h7zskrertP +gblGhfD20JfjvCHdP1RUhy/nzG+T+hn6Takan/GIgs8grlBMRHMgBYHW7tklhjaH +3F7LujhceAHhhgp6IOrpb6YTaTTaJbF3GTmkqxSJ3l1LtEoWz8Al/nL/Ftzxrtez +Vs6ebpvd7sw37sxmXBWX2OlvUrPCTmladw9OrllGXtCFw4YyLe3zozBlZ3cHzQ0q +lINhpRcajTMfZrsiGCkQtoJT+AqVJPS2sHjqsEH8yiySW9Jbq4zyMbM1yqQ2vnnx +MJgoYMcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUaQG88UnV +JPTI+Pcti1P+q3H7pGYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBAkgr75V0sEJimC6QRiTVWEuj2Khy7unjSfudbM6zumhXEU2/sUaVLiYy6cA/x +3v0laDle6T07x9g64j5YastE/4jbzrGgIINFlY0JnaYmR3KZEjgi1s1fkRRf3llL +PJm9u4Q1mbwAMQK/ZjLuuRcL3uRIHJek18nRqT5h43GB26qXyvJqeYYpYfIjL9+/ +YiZAbSRRZG+Li23cmPWrbA1CJY121SB+WybCbysbOXzhD3Sl2KSZRwSw4p2HrFtV +1Prk0dOBtZxCG9luf87ultuDZpfS0w6oNBAMXocgswk24ylcADkkFxBWW+7BETn1 +EpK+t1Lm37mU4sxtuha00XAi +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQcY44/8NUvBwr6LlHfRy7KjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MjcxOFoYDzIwNjEwNTE5MTkyNzE4WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UaBeC+Usalu +EtXnV7+PnH+gi7/71tI/jkKVGKuhD2JDVvqLVoqbMHRh3+wGMvqKCjbHPcC2XMWv +566fpAj4UZ9CLB5fVzss+QVNTl+FH2XhEzigopp+872ajsNzcZxrMkifxGb4i0U+ +t0Zi+UrbL5tsfP2JonKR1crOrbS6/DlzHBjIiJazGOQcMsJjNuTOItLbMohLpraA +/nApa3kOvI7Ufool1/34MG0+wL3UUA4YkZ6oBJVxjZvvs6tI7Lzz/SnhK2widGdc +snbLqBpHNIZQSorVoiwcFaRBGYX/uzYkiw44Yfa4cK2V/B5zgu1Fbr0gbI2am4eh +yVYyg4jPawIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS9gM1m +IIjyh9O5H/7Vj0R/akI7UzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAF0Sm9HC2AUyedBVnwgkVXMibnYChOzz7T+0Y+fOLXYAEXex2s8oqGeZdGYX +JHkjBn7JXu7LM+TpTbPbFFDoc1sgMguD/ls+8XsqAl1CssW+amryIL+jfcfbgQ+P +ICwEUD9hGdjBgJ5WcuS+qqxHsEIlFNci3HxcxfBa9VsWs5TjI7Vsl4meL5lf7ZyL +wDV7dHRuU+cImqG1MIvPRIlvPnT7EghrCYi2VCPhP2pM/UvShuwVnkz4MJ29ebIk +WR9kpblFxFdE92D5UUvMCjC2kmtgzNiErvTcwIvOO9YCbBHzRB1fFiWrXUHhJWq9 +IkaxR5icb/IpAV0A1lYZEWMVsfQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAMa0TPL+QgbWfUPpYXQkf8wwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMTAzMjBaGA8yMTIxMDUyNDIyMDMyMFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANhS9LJVJyWp +6Rudy9t47y6kzvgnFYDrvJVtgEK0vFn5ifdlHE7xqMz4LZqWBFTnS+3oidwVRqo7 +tqsuuElsouStO8m315/YUzKZEPmkw8h5ufWt/lg3NTCoUZNkB4p4skr7TspyMUwE +VdlKQuWTCOLtofwmWT+BnFF3To6xTh3XPlT3ssancw27Gob8kJegD7E0TSMVsecP +B8je65+3b8CGwcD3QB3kCTGLy87tXuS2+07pncHvjMRMBdDQQQqhXWsRSeUNg0IP +xdHTWcuwMldYPWK5zus9M4dCNBDlmZjKdcZZVUOKeBBAm7Uo7CbJCk8r/Fvfr6mw +nXXDtuWhqn/WhJiI/y0QU27M+Hy5CQMxBwFsfAjJkByBpdXmyYxUgTmMpLf43p7H +oWfH1xN0cT0OQEVmAQjMakauow4AQLNkilV+X6uAAu3STQVFRSrpvMen9Xx3EPC3 +G9flHueTa71bU65Xe8ZmEmFhGeFYHY0GrNPAFhq9RThPRY0IPyCZe0Th8uGejkek +jQjm0FHPOqs5jc8CD8eJs4jSEFt9lasFLVDcAhx0FkacLKQjGHvKAnnbRwhN/dF3 +xt4oL8Z4JGPCLau056gKnYaEyviN7PgO+IFIVOVIdKEBu2ASGE8/+QJB5bcHefNj +04hEkDW0UYJbSfPpVbGAR0gFI/QpycKnAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFFMXvvjoaGGUcul8GA3FT05DLbZcMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAQLwFhd2JKn4K/6salLyIA4mP58qbA/9BTB/r +D9l0bEwDlVPSdY7R3gZCe6v7SWLfA9RjE5tdWDrQMi5IU6W2OVrVsZS/yGJfwnwe +a/9iUAYprA5QYKDg37h12XhVsDKlYCekHdC+qa5WwB1SL3YUprDLPWeaIQdg+Uh2 ++LxvpZGoxoEbca0fc7flwq9ke/3sXt/3V4wJDyY6AL2YNdjFzC+FtYjHHx8rYxHs +aesP7yunuN17KcfOZBBnSFRrx96k+Xm95VReTEEpwiBqAECqEpMbd+R0mFAayMb1 +cE77GaK5yeC2f67NLYGpkpIoPbO9p9rzoXLE5GpSizMjimnz6QCbXPFAFBDfSzim +u6azp40kEUO6kWd7rBhqRwLc43D3TtNWQYxMve5mTRG4Od+eMKwYZmQz89BQCeqm +aZiJP9y9uwJw4p/A5V3lYHTDQqzmbOyhGUk6OdpdE8HXs/1ep1xTT20QDYOx3Ekt +r4mmNYfH/8v9nHNRlYJOqFhmoh1i85IUl5IHhg6OT5ZTTwsGTSxvgQQXrmmHVrgZ +rZIqyBKllCgVeB9sMEsntn4bGLig7CS/N1y2mYdW/745yCLZv2gj0NXhPqgEIdVV +f9DhFD4ohE1C63XP0kOQee+LYg/MY5vH8swpCSWxQgX5icv5jVDz8YTdCKgUc5u8 +rM2p0kk= +-----END CERTIFICATE----- diff --git a/examples/lambda-rds-iam-auth/src/main.rs b/examples/lambda-rds-iam-auth/src/main.rs new file mode 100644 index 00000000..32cf3580 --- /dev/null +++ b/examples/lambda-rds-iam-auth/src/main.rs @@ -0,0 +1,109 @@ +use aws_config::BehaviorVersion; +use aws_credential_types::provider::ProvideCredentials; +use aws_sigv4::{ + http_request::{sign, SignableBody, SignableRequest, SigningSettings}, + sign::v4, +}; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use serde_json::{json, Value}; +use sqlx::postgres::PgConnectOptions; +use std::env; +use std::time::{Duration, SystemTime}; + +const RDS_CERTS: &[u8] = include_bytes!("global-bundle.pem"); + +async fn generate_rds_iam_token( + db_hostname: &str, + port: u16, + db_username: &str, +) -> Result { + let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await; + + let credentials = config + .credentials_provider() + .expect("no credentials provider found") + .provide_credentials() + .await + .expect("unable to load credentials"); + let identity = credentials.into(); + let region = config.region().unwrap().to_string(); + + let mut signing_settings = SigningSettings::default(); + signing_settings.expires_in = Some(Duration::from_secs(900)); + signing_settings.signature_location = aws_sigv4::http_request::SignatureLocation::QueryParams; + + let signing_params = v4::SigningParams::builder() + .identity(&identity) + .region(®ion) + .name("rds-db") + .time(SystemTime::now()) + .settings(signing_settings) + .build()?; + + let url = format!( + "https://{db_hostname}:{port}/?Action=connect&DBUser={db_user}", + db_hostname = db_hostname, + port = port, + db_user = db_username + ); + + let signable_request = + SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])) + .expect("signable request"); + + let (signing_instructions, _signature) = + sign(signable_request, &signing_params.into())?.into_parts(); + + let mut url = url::Url::parse(&url).unwrap(); + for (name, value) in signing_instructions.params() { + url.query_pairs_mut().append_pair(name, &value); + } + + let response = url.to_string().split_off("https://".len()); + + Ok(response) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(handler)).await +} + +async fn handler(_event: LambdaEvent) -> Result { + let db_host = env::var("DB_HOSTNAME").expect("DB_HOSTNAME must be set"); + let db_port = env::var("DB_PORT") + .expect("DB_PORT must be set") + .parse::() + .expect("PORT must be a valid number"); + let db_name = env::var("DB_NAME").expect("DB_NAME must be set"); + let db_user_name = env::var("DB_USERNAME").expect("DB_USERNAME must be set"); + + let token = generate_rds_iam_token(&db_host, db_port, &db_user_name).await?; + + let opts = PgConnectOptions::new() + .host(&db_host) + .port(db_port) + .username(&db_user_name) + .password(&token) + .database(&db_name) + .ssl_root_cert_from_pem(RDS_CERTS.to_vec()) + .ssl_mode(sqlx::postgres::PgSslMode::Require); + + let pool = sqlx::postgres::PgPoolOptions::new() + .connect_with(opts) + .await?; + + let result: i32 = sqlx::query_scalar("SELECT $1 + $2") + .bind(3) + .bind(2) + .fetch_one(&pool) + .await?; + + println!("Result: {:?}", result); + + Ok(json!({ + "statusCode": 200, + "content-type": "text/plain", + "body": format!("The selected sum is: {result}") + })) +} From 309e3abb4f266b502da67ac8e35ea8a6b531090d Mon Sep 17 00:00:00 2001 From: Martin Jesper Low Madsen Date: Thu, 11 Jul 2024 00:57:24 +0200 Subject: [PATCH 328/394] Expand on the CloudTrail event (#909) * Expand on the CloudTrail event The initial contribution of the CloudTrail event was not complete, but rather constructed from a handful of event examples. The structure is documented at https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html which covers a number of optional fields and fields that are not already covered. * Add three example tests for CloudTrail events --- .../src/event/cloudwatch_events/cloudtrail.rs | 77 +++++++++++++++++-- ...le-cloudwatch-cloudtrail-assumed-role.json | 52 +++++++++++++ ...loudwatch-cloudtrail-unknown-federate.json | 27 +++++++ ...oudwatch-cloudtrail-unknown-user-auth.json | 33 ++++++++ 4 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 36d071ea..3f6bcc3e 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -26,23 +27,89 @@ pub struct AWSAPICall { #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UserIdentity { +pub struct SessionIssuer { pub r#type: String, + pub user_name: Option, pub principal_id: String, pub arn: String, pub account_id: String, - pub session_context: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct SessionContext { - pub attributes: Attributes, +pub struct WebIdFederationData { + pub federated_provider: Option, + pub attributes: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attributes { pub mfa_authenticated: String, - pub creation_date: String, + pub creation_date: DateTime, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContext { + pub session_issuer: Option, + pub web_id_federation_data: Option, + pub attributes: Attributes, + pub source_identity: Option, + pub ec2_role_delivery: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OnBehalfOf { + pub user_id: String, + pub identity_store_arn: String, +} + +// https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + pub account_id: Option, + pub arn: Option, + pub credential_id: Option, + pub invoked_by: Option, + pub principal_id: Option, + pub session_context: Option, + pub user_name: Option, + pub on_behalf_of: Option, +} + +#[cfg(test)] +mod tests { + use super::AWSAPICall; + + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_unknown_assumed_role() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-assumed-role.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_unknown_federate() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-federate.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_assumed_role() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json new file mode 100644 index 00000000..6e8946e9 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json @@ -0,0 +1,52 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "AssumedRole", + "principalId": "ZZZZZZZZZZZZZZZZZZZZZ:me@dev.com", + "arn": "arn:aws:sts::123456789000:assumed-role/AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890/me@dev.com", + "accountId": "123456789000", + "accessKeyId": "ABCDEFGHI12345678890", + "sessionContext": { + "sessionIssuer": { + "type": "Role", + "principalId": "ZZZZZZZZZZZZZZZZZZZZZ", + "arn": "arn:aws:iam::123456789000:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890", + "accountId": "123456789000", + "userName": "AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890" + }, + "webIdFederationData": {}, + "attributes": { + "creationDate": "2024-07-10T16:03:25Z", + "mfaAuthenticated": "false" + } + }, + "invokedBy": "servicecatalog.amazonaws.com" + }, + "eventTime": "2024-07-10T16:48:26Z", + "eventSource": "controltower.amazonaws.com", + "eventName": "CreateManagedAccount", + "awsRegion": "eu-west-1", + "sourceIPAddress": "servicecatalog.amazonaws.com", + "userAgent": "servicecatalog.amazonaws.com", + "requestParameters": { + "accountEmail": "HIDDEN_DUE_TO_SECURITY_REASONS", + "accountName": "Account Name", + "parentOrganizationalUnitName": "Organizational Unit (ou-a1b2-abcdef12)", + "sSOUserEmail": "HIDDEN_DUE_TO_SECURITY_REASONS", + "sSOUserFirstName": "HIDDEN_DUE_TO_SECURITY_REASONS", + "sSOUserLastName": "HIDDEN_DUE_TO_SECURITY_REASONS", + "provisionedProductId": "pp-abcdefg123456", + "idempotencyToken": "abcdef12345-abcdef12345" + }, + "responseElements": { + "createManagedAccountExecutionId": "123456789000-abcdef12345-abcdef12345" + }, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsApiCall", + "managementEvent": true, + "recipientAccountId": "123456789000", + "eventCategory": "Management", + "sessionCredentialFromConsole": "true" +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json new file mode 100644 index 00000000..336edc1f --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json @@ -0,0 +1,27 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "Unknown", + "principalId": "00000000-0000-0000-0000-000000000000", + "accountId": "123456789000", + "userName": "me@dev.com" + }, + "eventTime": "2024-07-10T18:41:56Z", + "eventSource": "sso.amazonaws.com", + "eventName": "Federate", + "awsRegion": "eu-west-1", + "sourceIPAddress": "1.1.1.1", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "requestParameters": null, + "responseElements": null, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsServiceEvent", + "managementEvent": true, + "recipientAccountId": "123456789000", + "serviceEventDetails": { + "relayId": "00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000000" + }, + "eventCategory": "Management" +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json new file mode 100644 index 00000000..96fed176 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json @@ -0,0 +1,33 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "Unknown", + "principalId": "123456789000", + "arn": "", + "accountId": "123456789000", + "accessKeyId": "" + }, + "eventTime": "2024-07-10T16:05:11Z", + "eventSource": "signin.amazonaws.com", + "eventName": "UserAuthentication", + "awsRegion": "eu-west-1", + "sourceIPAddress": "1.1.1.1", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "requestParameters": null, + "responseElements": null, + "additionalEventData": { + "AuthWorkflowID": "00000000-0000-0000-0000-000000000000", + "LoginTo": "https://tenant.awsapps.com/start/", + "CredentialType": "EXTERNAL_IDP" + }, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsServiceEvent", + "managementEvent": true, + "recipientAccountId": "123456789000", + "serviceEventDetails": { + "UserAuthentication": "Success" + }, + "eventCategory": "Management" +} From e8761dae829a09c0858b2702dcef7d3853c3d377 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 12 Jul 2024 12:58:22 -0700 Subject: [PATCH 329/394] Error handling improvements (#907) * Error handling improvements Change how we handle error types to be more ergonomic: - Replace Cow with String: Diagnostics are serialized into JSON as soon as the function returns, which means that their value is copied right away. The performance improvement of using Cow is minimal in this case, but it has some ergonomic implications because we have to handle their lifetimes. By removing the explicit lifetimes, people can return Diagnostic values with static lifetimes which was not possible before. - Add `IntoDiagnostic` trait. This is a helper trait to facilitate transforming value types into Diagnostic. It gives external crates a better mechanism to transform values into `Diagnostic`. - Add features to implement `IntoDiagnostic` for anyhow, eyre, and miette error types. This helps people that use those creates to transform their errors into `Diagnostic` without double boxing their errors. * Recommend to implement `From`. Signed-off-by: David Calavera * Simplify implementation, remove `IntoDiagnostic` We can implement `From for Diagnostic` for error crates. * Remove implementation for From> Signed-off-by: David Calavera * Document features in Cargo.toml Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- README.md | 57 +++++- examples/basic-error-anyhow/Cargo.toml | 10 -- examples/basic-error-anyhow/src/main.rs | 21 --- .../.gitignore | 0 .../Cargo.toml | 12 ++ .../README.md | 6 +- .../src/main.rs | 44 +++++ .../.gitignore | 0 .../Cargo.toml | 2 +- .../README.md | 0 .../src/main.rs | 4 +- lambda-http/Cargo.toml | 6 +- lambda-http/src/lib.rs | 2 +- lambda-http/src/streaming.rs | 2 +- lambda-runtime/Cargo.toml | 10 +- lambda-runtime/src/diagnostic.rs | 162 ++++++++++++------ lambda-runtime/src/layers/api_response.rs | 21 ++- lambda-runtime/src/layers/panic.rs | 27 ++- lambda-runtime/src/lib.rs | 2 +- lambda-runtime/src/requests.rs | 8 +- lambda-runtime/src/runtime.rs | 10 +- 21 files changed, 275 insertions(+), 131 deletions(-) delete mode 100644 examples/basic-error-anyhow/Cargo.toml delete mode 100644 examples/basic-error-anyhow/src/main.rs rename examples/{basic-error-anyhow => basic-error-error-crates-integration}/.gitignore (100%) create mode 100644 examples/basic-error-error-crates-integration/Cargo.toml rename examples/{basic-error-anyhow => basic-error-error-crates-integration}/README.md (54%) create mode 100644 examples/basic-error-error-crates-integration/src/main.rs rename examples/{basic-error-diagnostic => basic-error-thiserror}/.gitignore (100%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/Cargo.toml (94%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/README.md (100%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/src/main.rs (90%) diff --git a/README.md b/README.md index 44ec1983..31ba399b 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ pip3 install cargo-lambda See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). -### Your first function +## Your first function To create your first function, run Cargo Lambda with the [subcommand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: @@ -71,6 +71,61 @@ async fn func(event: LambdaEvent) -> Result { } ``` +## Understanding Lambda errors + +when a function invocation fails, AWS Lambda expects you to return an object that can be serialized into JSON structure with the error information. This structure is represented in the following example: + +```json +{ + "error_type": "the type of error raised", + "error_message": "a string description of the error" +} +``` + +The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the converstion of several general errors types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. + +### Implement your own Diagnostic + +To get more descriptive `error_type` fields, you can implement `From` for your error type. That gives you full control on what the `error_type` is: + +```rust +use lambda_runtime::{Diagnostic, Error, LambdaEvent}; + +#[derive(Debug)] +struct ErrorResponse(&'static str); + +impl From for Diagnostic { + fn from(error: ErrorResponse) -> Diagnostic { + Diagnostic { + error_type: "MyErrorType".into(), + error_message: error.0.to_string(), + } + } +} + +async fn handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { + Err(ErrorResponse("this is an error response")) +} +``` + +We recommend you to use the [thiserror crate](https://crates.io/crates/thiserror) to declare your errors. You can see an example on how to integrate `thiserror` with the Runtime's diagnostics in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-thiserror) + +### Anyhow, Eyre, and Miette + +Popular error crates like Anyhow, Eyre, and Miette provide their own error types that encapsulate other errors. There is no direct transformation of those errors into `Diagnostic`, but we provide feature flags for each one of those crates to help you integrate them with your Lambda functions. + +If you enable the features `anyhow`, `eyre`, or `miette` in the `lambda_runtime` dependency of your package. The error types provided by those crates can have blanket transformations into `Diagnostic`. These features expose an `From for Diagnostic` implementation that transforms those error types into a `Diagnostic`. This is an example that transforms an `anyhow::Error` into a `Diagnostic`: + +```rust +use lambda_runtime::{Diagnostic, LambdaEvent}; + +async fn handler(_event: LambdaEvent) -> Result<(), Diagnostic> { + Err(anyhow::anyhow!("this is an error").into()) +} +``` + +You can see more examples on how to use these error crates in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-error-crates-integration). + ## Building and deploying your Lambda functions If you already have Cargo Lambda installed in your machine, run the next command to build your function: diff --git a/examples/basic-error-anyhow/Cargo.toml b/examples/basic-error-anyhow/Cargo.toml deleted file mode 100644 index a0ff62db..00000000 --- a/examples/basic-error-anyhow/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "basic-error-anyhow" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1" -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1" -tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/src/main.rs b/examples/basic-error-anyhow/src/main.rs deleted file mode 100644 index cbca84fd..00000000 --- a/examples/basic-error-anyhow/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::bail; -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::Deserialize; - -#[derive(Deserialize)] -struct Request {} - -/// Return anyhow::Result in the main body for the Lambda function. -async fn function_handler(_event: LambdaEvent) -> anyhow::Result<()> { - bail!("This is an error message"); -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_runtime::run(service_fn(|event: LambdaEvent| async move { - function_handler(event) - .await - .map_err(Into::>::into) - })) - .await -} diff --git a/examples/basic-error-anyhow/.gitignore b/examples/basic-error-error-crates-integration/.gitignore similarity index 100% rename from examples/basic-error-anyhow/.gitignore rename to examples/basic-error-error-crates-integration/.gitignore diff --git a/examples/basic-error-error-crates-integration/Cargo.toml b/examples/basic-error-error-crates-integration/Cargo.toml new file mode 100644 index 00000000..741ec713 --- /dev/null +++ b/examples/basic-error-error-crates-integration/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "basic-error-error-crates-integration" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +eyre = "0.6.12" +lambda_runtime = { path = "../../lambda-runtime", features = ["anyhow", "eyre", "miette"] } +miette = "7.2.0" +serde = "1" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/README.md b/examples/basic-error-error-crates-integration/README.md similarity index 54% rename from examples/basic-error-anyhow/README.md rename to examples/basic-error-error-crates-integration/README.md index b659c283..2e46c55d 100644 --- a/examples/basic-error-anyhow/README.md +++ b/examples/basic-error-error-crates-integration/README.md @@ -1,6 +1,8 @@ -# AWS Lambda Function Error Handling With `anyhow` Crate Example +# AWS Lambda Function Error Handling with several popular error crates. -This example shows how to use external error types like `anyhow::Error`. +This example shows how to use external error types like `anyhow::Error`, `eyre::Report`, and `miette::Report`. + +To use the integrations with these crates, you need to enable to respective feature flag in the runtime which provides the implemetation of `into_diagnostic` for specific error types provided by these crates. ## Build & Deploy diff --git a/examples/basic-error-error-crates-integration/src/main.rs b/examples/basic-error-error-crates-integration/src/main.rs new file mode 100644 index 00000000..f4048584 --- /dev/null +++ b/examples/basic-error-error-crates-integration/src/main.rs @@ -0,0 +1,44 @@ +use lambda_runtime::{run, service_fn, Diagnostic, Error, LambdaEvent}; +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum ErrorType { + Anyhow, + Eyre, + Miette, +} + +#[derive(Deserialize)] +struct Request { + error_type: ErrorType, +} + +fn anyhow_error() -> anyhow::Result<()> { + anyhow::bail!("This is an error message from Anyhow"); +} + +fn eyre_error() -> eyre::Result<()> { + eyre::bail!("This is an error message from Eyre"); +} + +fn miette_error() -> miette::Result<()> { + miette::bail!("This is an error message from Miette"); +} + +/// Transform an anyhow::Error, eyre::Report, or miette::Report into a lambda_runtime::Diagnostic. +/// It does it by enabling the feature `anyhow`, `eyre` or `miette` in the runtime dependency. +/// Those features enable the implementation of `From for Diagnostic` +/// for `anyhow::Error`, `eyre::Report`, and `miette::Report`. +async fn function_handler(event: LambdaEvent) -> Result<(), Diagnostic> { + match event.payload.error_type { + ErrorType::Anyhow => anyhow_error().map_err(|e| e.into()), + ErrorType::Eyre => eyre_error().map_err(|e| e.into()), + ErrorType::Miette => miette_error().map_err(|e| e.into()), + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(function_handler)).await +} diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-thiserror/.gitignore similarity index 100% rename from examples/basic-error-diagnostic/.gitignore rename to examples/basic-error-thiserror/.gitignore diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-thiserror/Cargo.toml similarity index 94% rename from examples/basic-error-diagnostic/Cargo.toml rename to examples/basic-error-thiserror/Cargo.toml index b81ef730..d7c7d725 100644 --- a/examples/basic-error-diagnostic/Cargo.toml +++ b/examples/basic-error-thiserror/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic-error-diagnostic" +name = "basic-error-thiserror" version = "0.1.0" edition = "2021" diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-thiserror/README.md similarity index 100% rename from examples/basic-error-diagnostic/README.md rename to examples/basic-error-thiserror/README.md diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-thiserror/src/main.rs similarity index 90% rename from examples/basic-error-diagnostic/src/main.rs rename to examples/basic-error-thiserror/src/main.rs index 11f68d4b..403309bf 100644 --- a/examples/basic-error-diagnostic/src/main.rs +++ b/examples/basic-error-thiserror/src/main.rs @@ -13,8 +13,8 @@ pub enum ExecutionError { Unexpected(String), } -impl<'a> From for Diagnostic<'a> { - fn from(value: ExecutionError) -> Diagnostic<'a> { +impl From for Diagnostic { + fn from(value: ExecutionError) -> Diagnostic { let (error_type, error_message) = match value { ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()), ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()), diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1165084b..fc822b95 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -22,7 +22,11 @@ apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] -tracing = ["lambda_runtime/tracing"] +tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities +opentelemetry = ["lambda_runtime/opentelemetry"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["lambda_runtime/anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["lambda_runtime/eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["lambda_runtime/miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] base64 = { workspace = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 90e59867..233d6992 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -194,7 +194,7 @@ where S: Service, S::Future: Send + 'a, R: IntoResponse, - E: std::fmt::Debug + for<'b> Into>, + E: std::fmt::Debug + Into, { lambda_runtime::run(Adapter::from(handler)).await } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index ad3471d3..a93408b4 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -20,7 +20,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), where S: Service, Error = E>, S::Future: Send + 'a, - E: Debug + for<'b> Into>, + E: Debug + Into, B: Body + Unpin + Send + 'static, B::Data: Into + Send, B::Error: Into + Send + Debug, diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9e56d05b..371b32f7 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -15,13 +15,18 @@ readme = "../README.md" [features] default = ["tracing"] -tracing = ["lambda_runtime_api_client/tracing"] -opentelemetry = ["opentelemetry-semantic-conventions"] +tracing = ["lambda_runtime_api_client/tracing"] # enables access to the Tracing utilities +opentelemetry = ["opentelemetry-semantic-conventions"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["dep:anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["dep:eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["dep:miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] +anyhow = { version = "1.0.86", optional = true } async-stream = "0.3" base64 = { workspace = true } bytes = { workspace = true } +eyre = { version = "0.6.12", optional = true } futures = { workspace = true } http = { workspace = true } http-body = { workspace = true } @@ -35,6 +40,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.14", optional = true } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index 9a7230a7..c03ce284 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{any::type_name, borrow::Cow}; +use std::any::type_name; use crate::{deserializer::DeserializeError, Error}; @@ -14,114 +14,144 @@ use crate::{deserializer::DeserializeError, Error}; /// [`error_type`][`Diagnostic::error_type`] is derived from the type name of /// the original error with [`std::any::type_name`] as a fallback, which may /// not be reliable for conditional error handling. -/// You can define your own error container that implements `Into` -/// if you need to handle errors based on error types. +/// +/// To get more descriptive [`error_type`][`Diagnostic::error_type`] fields, you can implement `From` for your error type. +/// That gives you full control on what the `error_type` is. /// /// Example: /// ``` /// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; -/// use std::borrow::Cow; /// /// #[derive(Debug)] -/// struct ErrorResponse(Error); +/// struct ErrorResponse(&'static str); /// -/// impl<'a> Into> for ErrorResponse { -/// fn into(self) -> Diagnostic<'a> { +/// impl From for Diagnostic { +/// fn from(error: ErrorResponse) -> Diagnostic { /// Diagnostic { /// error_type: "MyError".into(), -/// error_message: self.0.to_string().into(), +/// error_message: error.0.to_string(), /// } /// } /// } /// /// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { -/// // ... do something -/// Ok(()) +/// Err(ErrorResponse("this is an error response")) /// } /// ``` #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Diagnostic<'a> { - /// Error type. - /// - /// `error_type` is derived from the type name of the original error with - /// [`std::any::type_name`] as a fallback. - /// Please implement your own `Into` if you need more reliable - /// error types. - pub error_type: Cow<'a, str>, - /// Error message. +pub struct Diagnostic { + /// `error_type` is the type of exception or error returned by the function. + /// Use this field to categorize the different kinds of errors that your function + /// might experience. /// - /// `error_message` is the output from the [`Display`][std::fmt::Display] - /// implementation of the original error as a fallback. - pub error_message: Cow<'a, str>, + /// In standard implementations, `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`], however this is not descriptive enough for an error type. + /// Implement your own `Into` to return a more descriptive error type. + pub error_type: String, + /// `error_message` is a string expression of the error. + /// In standard implementations, it's the output from the [`Display`][std::fmt::Display] + /// implementation of the original error. + pub error_message: String, } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: DeserializeError) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: Error) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a, T> From> for Diagnostic<'a> -where - T: std::error::Error, -{ - fn from(value: Box) -> Self { +impl From> for Diagnostic { + fn from(value: Box) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From> for Diagnostic<'a> { - fn from(value: Box) -> Self { +impl From for Diagnostic { + fn from(value: std::convert::Infallible) -> Self { Diagnostic { - error_type: type_name::>().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: std::convert::Infallible) -> Self { +impl From for Diagnostic { + fn from(value: String) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: String) -> Self { +impl From<&'static str> for Diagnostic { + fn from(value: &'static str) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From<&'static str> for Diagnostic<'a> { - fn from(value: &'static str) -> Self { +impl From for Diagnostic { + fn from(value: std::io::Error) -> Self { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "anyhow")] +impl From for Diagnostic { + fn from(value: anyhow::Error) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "eyre")] +impl From for Diagnostic { + fn from(value: eyre::Report) -> Diagnostic { Diagnostic { - error_type: type_name::<&'static str>().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } +#[cfg(feature = "miette")] +impl From for Diagnostic { + fn from(value: miette::Report) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +pub(crate) fn type_name_of_val(_: T) -> String { + type_name::().into() +} + #[cfg(test)] mod test { use super::*; @@ -141,4 +171,34 @@ mod test { let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); assert_eq!(expected, actual); } + + #[cfg(feature = "anyhow")] + #[test] + fn test_anyhow_integration() { + use anyhow::Error as AnyhowError; + let error: AnyhowError = anyhow::anyhow!("anyhow error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&anyhow::Error"); + assert_eq!(diagnostic.error_message, "anyhow error"); + } + + #[cfg(feature = "eyre")] + #[test] + fn test_eyre_integration() { + use eyre::Report; + let error: Report = eyre::eyre!("eyre error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&eyre::Report"); + assert_eq!(diagnostic.error_message, "eyre error"); + } + + #[cfg(feature = "miette")] + #[test] + fn test_miette_integration() { + use miette::Report; + let error: Report = miette::miette!("miette error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&miette::eyreish::Report"); + assert_eq!(diagnostic.error_message, "miette error"); + } } diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 7c963f68..e744cde1 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -51,8 +51,7 @@ impl - Service +impl Service for RuntimeApiResponseService< S, EventPayload, @@ -63,7 +62,7 @@ impl<'a, S, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI StreamError, > where - S: Service, Response = Response, Error = Diagnostic<'a>>, + S: Service, Response = Response, Error = Diagnostic>, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -74,7 +73,7 @@ where type Response = http::Request; type Error = BoxError; type Future = - RuntimeApiResponseFuture<'a, S::Future, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>; + RuntimeApiResponseFuture; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { self.inner @@ -120,21 +119,21 @@ where } } -fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, BoxError> +fn build_event_error_request(request_id: &str, err: T) -> Result, BoxError> where - T: Into> + Debug, + T: Into + Debug, { error!(error = ?err, "Request payload deserialization into LambdaEvent failed. The handler will not be called. Log at TRACE level to see the payload."); EventErrorRequest::new(request_id, err).into_req() } #[pin_project(project = RuntimeApiResponseFutureProj)] -pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> { +pub enum RuntimeApiResponseFuture { Future( #[pin] F, String, PhantomData<( - &'a (), + (), Response, BufferedResponse, StreamingResponse, @@ -145,10 +144,10 @@ pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingRe Ready(Option, BoxError>>), } -impl<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> Future - for RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +impl Future + for RuntimeApiResponseFuture where - F: Future>>, + F: Future>, Response: IntoFunctionResponse, BufferedResponse: Serialize, StreamingResponse: Stream> + Unpin + Send + 'static, diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 036b747b..c76348ac 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -1,9 +1,7 @@ -use crate::{Diagnostic, LambdaEvent}; +use crate::{diagnostic::type_name_of_val, Diagnostic, LambdaEvent}; use futures::{future::CatchUnwind, FutureExt}; use pin_project::pin_project; -use std::{ - any::Any, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task, -}; +use std::{any::Any, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task}; use tower::Service; use tracing::error; @@ -33,9 +31,9 @@ impl<'a, S, Payload> Service> for CatchPanicService<'a, S> where S: Service>, S::Future: 'a, - S::Error: Into> + Debug, + S::Error: Into + Debug, { - type Error = Diagnostic<'a>; + type Error = Diagnostic; type Response = S::Response; type Future = CatchPanicFuture<'a, S::Future>; @@ -71,9 +69,9 @@ pub enum CatchPanicFuture<'a, F> { impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> where F: Future>, - E: Into> + Debug, + E: Into + Debug, { - type Output = Result>; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { use task::Poll; @@ -98,20 +96,15 @@ where } impl<'a, F> CatchPanicFuture<'a, F> { - fn build_panic_diagnostic(err: &Box) -> Diagnostic<'a> { - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { + fn build_panic_diagnostic(err: &Box) -> Diagnostic { + let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; Diagnostic { - error_type: Cow::Borrowed(error_type), - error_message: Cow::Owned(msg), + error_type: type_name_of_val(err), + error_message, } } } - -fn type_name_of_val(_: T) -> &'static str { - std::any::type_name::() -} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c3e68cc4..76d0562a 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -112,7 +112,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service, Response = R>, F::Future: Future>, - F::Error: for<'a> Into> + fmt::Debug, + F::Error: Into + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 0e2eb59a..535f1e8e 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -158,11 +158,11 @@ fn test_event_completion_request() { // /runtime/invocation/{AwsRequestId}/error pub(crate) struct EventErrorRequest<'a> { pub(crate) request_id: &'a str, - pub(crate) diagnostic: Diagnostic<'a>, + pub(crate) diagnostic: Diagnostic, } impl<'a> EventErrorRequest<'a> { - pub(crate) fn new(request_id: &'a str, diagnostic: impl Into>) -> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, diagnostic: impl Into) -> EventErrorRequest<'a> { EventErrorRequest { request_id, diagnostic: diagnostic.into(), @@ -224,8 +224,8 @@ mod tests { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), - error_message: std::borrow::Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 56c8efad..1c676480 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -72,7 +72,7 @@ impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -207,7 +207,7 @@ fn wrap_handler<'a, F, EventPayload, Response, BufferedResponse, StreamingRespon where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -257,7 +257,7 @@ mod endpoint_tests { use httpmock::prelude::*; use lambda_runtime_api_client::Client; - use std::{borrow::Cow, env, sync::Arc}; + use std::{env, sync::Arc}; use tokio_stream::StreamExt; #[tokio::test] @@ -324,8 +324,8 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { let diagnostic = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }; let body = serde_json::to_string(&diagnostic)?; From 2708342e6f9d62848f311a962416be1d4454e754 Mon Sep 17 00:00:00 2001 From: mx Date: Sun, 14 Jul 2024 13:05:37 +1200 Subject: [PATCH 330/394] Fixed clippy warnings in lambda-events (#910) --- lambda-events/src/event/cloudwatch_events/cloudtrail.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 3f6bcc3e..fc332306 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -91,7 +91,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-assumed-role.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] @@ -100,7 +100,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-federate.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] @@ -109,7 +109,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } From 150c5f0bee9fa81077a20faf402b1289189a22f5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 24 Jul 2024 20:24:18 -0700 Subject: [PATCH 331/394] Release version 0.13 (#912) - Release the error handling improvements introduced after 0.12. --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index fc822b95..08b70d4e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.12.0" +version = "0.13.0" authors = [ "David Calavera ", "Harold Sun ", @@ -38,7 +38,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.12.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.13.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 371b32f7..0f63cd47 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.12.0" +version = "0.13.0" authors = [ "David Calavera ", "Harold Sun ", From 2cfef413ea79885912a4d82ecddef7111517c427 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 12 Aug 2024 17:00:27 +0100 Subject: [PATCH 332/394] fix: remove unused struct (#914) --- lambda-runtime/src/requests.rs | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 535f1e8e..729272f2 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -186,23 +186,6 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { } } -// /runtime/init/error -struct InitErrorRequest; - -impl IntoRequest for InitErrorRequest { - fn into_req(self) -> Result, Error> { - let uri = "/2018-06-01/runtime/init/error".to_string(); - let uri = Uri::from_str(&uri)?; - - let req = build_request() - .method(Method::POST) - .uri(uri) - .header("lambda-runtime-function-error-type", "unhandled") - .body(Body::empty())?; - Ok(req) - } -} - #[cfg(test)] mod tests { use super::*; @@ -237,17 +220,4 @@ mod tests { None => false, }); } - - #[test] - fn test_init_error_request() { - let req = InitErrorRequest; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/init/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); - } } From 6bbd1c111cf9dd6182bc7817268f884bcc262e8c Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 12 Aug 2024 18:10:14 +0100 Subject: [PATCH 333/394] fix: bedrock tests (#915) --- .../src/event/bedrock_agent_runtime/mod.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index cf84d4d3..c1425b85 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -82,31 +82,34 @@ pub struct Agent { #[cfg(test)] mod tests { + + use crate::event::bedrock_agent_runtime::AgentEvent; + #[test] - #[cfg(feature = "bedrock-agent-runtime")] - fn example_bedrock_agent__runtime_event() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + #[cfg(feature = "bedrock_agent_runtime")] + fn example_bedrock_agent_runtime_event() { + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] - #[cfg(feature = "bedrock-agent-runtime")] + #[cfg(feature = "bedrock_agent_runtime")] fn example_bedrock_agent_runtime_event_without_parameters() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] - #[cfg(feature = "bedrock-agent-runtime")] + #[cfg(feature = "bedrock_agent_runtime")] fn example_bedrock_agent_runtime_event_without_request_body() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } From d7c53b2d448421a4a4680c9d77eebb25ccfb9e55 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 13 Aug 2024 18:10:56 +0100 Subject: [PATCH 334/394] add support for integration test via GitHub Actions (#913) --- .github/workflows/run-integration-test.yml | 62 +++ lambda-integration-tests/Cargo.toml | 30 +- lambda-integration-tests/python/main.py | 4 - lambda-integration-tests/samconfig.toml | 23 ++ lambda-integration-tests/src/authorizer.rs | 53 +++ .../src/bin/extension-fn.rs | 28 -- .../src/bin/extension-trait.rs | 88 ----- lambda-integration-tests/src/bin/http-fn.rs | 26 -- .../src/bin/http-trait.rs | 83 ---- .../src/bin/logs-trait.rs | 75 ---- .../src/bin/runtime-fn.rs | 36 -- .../src/bin/runtime-trait.rs | 92 ----- lambda-integration-tests/src/helloworld.rs | 26 ++ lambda-integration-tests/template.yaml | 353 +++--------------- .../tests/integration_test.rs | 12 + 15 files changed, 240 insertions(+), 751 deletions(-) create mode 100644 .github/workflows/run-integration-test.yml delete mode 100644 lambda-integration-tests/python/main.py create mode 100644 lambda-integration-tests/samconfig.toml create mode 100644 lambda-integration-tests/src/authorizer.rs delete mode 100644 lambda-integration-tests/src/bin/extension-fn.rs delete mode 100644 lambda-integration-tests/src/bin/extension-trait.rs delete mode 100644 lambda-integration-tests/src/bin/http-fn.rs delete mode 100644 lambda-integration-tests/src/bin/http-trait.rs delete mode 100644 lambda-integration-tests/src/bin/logs-trait.rs delete mode 100644 lambda-integration-tests/src/bin/runtime-fn.rs delete mode 100644 lambda-integration-tests/src/bin/runtime-trait.rs create mode 100644 lambda-integration-tests/src/helloworld.rs create mode 100644 lambda-integration-tests/tests/integration_test.rs diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml new file mode 100644 index 00000000..f1e30d09 --- /dev/null +++ b/.github/workflows/run-integration-test.yml @@ -0,0 +1,62 @@ +name: Run integration tests + +permissions: + id-token: write + contents: read + +on: + workflow_dispatch: + push: + +jobs: + run-integration-tests: + runs-on: ubuntu-latest + steps: + - name: install Cargo Lambda + uses: jaxxstorm/action-install-gh-release@v1.9.0 + with: + repo: cargo-lambda/cargo-lambda + platform: linux + arch: x86_64 + - name: install Zig toolchain + uses: korandoru/setup-zig@v1 + with: + zig-version: 0.10.0 + - name: install SAM + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + - uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ secrets.ROLE_SESSION_NAME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: build stack + run: cd lambda-integration-tests && sam build --beta-features + - name: validate stack + run: cd lambda-integration-tests && sam validate --lint + - name: deploy stack + id: deploy_stack + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd lambda-integration-tests + stackName="aws-lambda-rust-integ-test-$GITHUB_RUN_ID" + echo "STACK_NAME=$stackName" >> "$GITHUB_OUTPUT" + echo "Stack name = $stackName" + sam deploy --stack-name "${stackName}" --parameter-overrides "ParameterKey=SecretToken,ParameterValue=${{ secrets.SECRET_TOKEN }}" "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }}" --no-confirm-changeset --no-progressbar > disable_output + TEST_ENDPOINT=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | .OutputValue') + echo "TEST_ENDPOINT=$TEST_ENDPOINT" >> "$GITHUB_OUTPUT" + - name: run test + env: + SECRET_TOKEN: ${{ secrets.SECRET_TOKEN }} + TEST_ENDPOINT: ${{ steps.deploy_stack.outputs.TEST_ENDPOINT }} + run: cd lambda-integration-tests && cargo test + - name: cleanup + if: always() + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + STACK_NAME: ${{ steps.deploy_stack.outputs.STACK_NAME }} + run: sam delete --stack-name "${STACK_NAME}" --no-prompts diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 1b0fc3ef..ee44a969 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "lambda_integration_tests" -version = "0.5.0" -authors = ["Nicolas Moutschen "] -edition = "2018" +name = "aws_lambda_rust_integration_tests" +version = "0.1.0" +authors = ["Maxime David"] +edition = "2021" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" @@ -10,13 +10,21 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "../README.md" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -lambda_http = { path = "../lambda-http" } lambda_runtime = { path = "../lambda-runtime" } -lambda-extension = { path = "../lambda-extension" } -serde = { version = "1", features = ["derive"] } +aws_lambda_events = { path = "../lambda-events" } +serde_json = "1.0.121" tokio = { version = "1", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +serde = { version = "1.0.204", features = ["derive"] } + +[dev-dependencies] +reqwest = { version = "0.12.5", features = ["blocking"] } +openssl = { version = "0.10", features = ["vendored"] } + +[[bin]] +name = "helloworld" +path = "src/helloworld.rs" + +[[bin]] +name = "authorizer" +path = "src/authorizer.rs" diff --git a/lambda-integration-tests/python/main.py b/lambda-integration-tests/python/main.py deleted file mode 100644 index e7e2114b..00000000 --- a/lambda-integration-tests/python/main.py +++ /dev/null @@ -1,4 +0,0 @@ -def handler(event, context): - return { - "message": event["command"].upper() - } \ No newline at end of file diff --git a/lambda-integration-tests/samconfig.toml b/lambda-integration-tests/samconfig.toml new file mode 100644 index 00000000..0212b62a --- /dev/null +++ b/lambda-integration-tests/samconfig.toml @@ -0,0 +1,23 @@ +version = 0.1 + +[default] +[default.build.parameters] +cached = true +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +s3_bucket = "aws-lambda-rust-runtime-integration-testing" + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" \ No newline at end of file diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs new file mode 100644 index 00000000..41ddd2d8 --- /dev/null +++ b/lambda-integration-tests/src/authorizer.rs @@ -0,0 +1,53 @@ +use std::env; + +use aws_lambda_events::{ + apigw::{ApiGatewayCustomAuthorizerPolicy, ApiGatewayCustomAuthorizerResponse}, + event::iam::IamPolicyStatement, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; +use serde::Deserialize; +use serde_json::json; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct APIGatewayCustomAuthorizerRequest { + authorization_token: String, + method_arn: String, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func( + event: LambdaEvent, +) -> Result { + let expected_token = env::var("SECRET_TOKEN").expect("could not read the secret token"); + if event.payload.authorization_token == expected_token { + return Ok(allow(&event.payload.method_arn)); + } + panic!("token is not valid"); +} + +fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { + let stmt = IamPolicyStatement { + action: vec!["execute-api:Invoke".to_string()], + resource: vec![method_arn.to_owned()], + effect: aws_lambda_events::iam::IamPolicyEffect::Allow, + condition: None, + }; + let policy = ApiGatewayCustomAuthorizerPolicy { + version: Some("2012-10-17".to_string()), + statement: vec![stmt], + }; + ApiGatewayCustomAuthorizerResponse { + principal_id: Some("user".to_owned()), + policy_document: policy, + context: json!({ "hello": "world" }), + usage_identifier_key: None, + } +} diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs deleted file mode 100644 index 5e9ec553..00000000 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ /dev/null @@ -1,28 +0,0 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; -use tracing::info; - -async fn my_extension(event: LambdaEvent) -> Result<(), Error> { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension-fn] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - info!("[extension-fn] Request event received: {:?}", e); - } - } - - Ok(()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(service_fn(my_extension)).await -} diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs deleted file mode 100644 index e2c73fa3..00000000 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; -use tracing::info; - -struct MyExtension { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyExtension { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyExtension { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyExtension { - type Error = Error; - type Future = Pin>>>; - type Response = (); - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[extension] Service was already ready"); - } else { - info!("[extension] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, event: LambdaEvent) -> Self::Future { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - self.invoke_count += 1; - info!("[extension] Request event {} received: {:?}", self.invoke_count, e); - } - } - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[extension] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[extension] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(MyExtension::default()).await -} diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs deleted file mode 100644 index 8107f423..00000000 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ /dev/null @@ -1,26 +0,0 @@ -use lambda_http::{ext::RequestExt, service_fn, Body, Error, IntoResponse, Request, Response}; -use tracing::info; - -async fn handler(event: Request) -> Result { - let _context = event.lambda_context(); - info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); - - Ok(Response::builder() - .status(200) - .body(Body::from("Hello, world!")) - .unwrap()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let handler = service_fn(handler); - lambda_http::run(handler).await -} diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs deleted file mode 100644 index d8e6f74f..00000000 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_http::{ext::RequestExt, Body, Error, Request, Response, Service}; -use tracing::info; - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyHandler { - type Error = Error; - type Future = Pin> + Send>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[http-trait] Service was already ready"); - } else { - info!("[http-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: Request) -> Self::Future { - self.invoke_count += 1; - info!("[http-trait] Received event {}: {:?}", self.invoke_count, request); - info!("[http-trait] Lambda context: {:?}", request.lambda_context()); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[http-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[http-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response::builder() - .status(200) - .body(Body::from("Hello, World!")) - .unwrap()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_http::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs deleted file mode 100644 index b474bc8d..00000000 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ /dev/null @@ -1,75 +0,0 @@ -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; -use std::{ - future::{ready, Future}, - pin::Pin, - sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, - }, - task::Poll, -}; -use tracing::info; - -/// Custom log processor that increments a counter for each log record. -/// -/// This is a simple example of a custom log processor that can be used to -/// count the number of log records that are processed. -/// -/// This needs to derive Clone (and store the counter in an Arc) as the runtime -/// could need multiple `Service`s to process the logs. -#[derive(Clone, Default)] -struct MyLogsProcessor { - counter: Arc, -} - -impl MyLogsProcessor { - pub fn new() -> Self { - Self::default() - } -} - -type MyLogsFuture = Pin> + Send>>; - -/// Implementation of the actual log processor -/// -/// This receives a `Vec` whenever there are new log entries available. -impl Service> for MyLogsProcessor { - type Response = (); - type Error = Error; - type Future = MyLogsFuture; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, logs: Vec) -> Self::Future { - let counter = self.counter.fetch_add(1, SeqCst); - for log in logs { - match log.record { - LambdaLogRecord::Function(record) => { - info!("[logs] {} [function] {}: {}", log.time, counter, record.trim()) - } - LambdaLogRecord::Extension(record) => { - info!("[logs] {} [extension] {}: {}", log.time, counter, record.trim()) - } - _ => (), - } - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let logs_processor = SharedService::new(MyLogsProcessor::new()); - Extension::new().with_logs_processor(logs_processor).run().await?; - - Ok(()) -} diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs deleted file mode 100644 index d16717aa..00000000 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ /dev/null @@ -1,36 +0,0 @@ -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -async fn handler(event: LambdaEvent) -> Result { - info!("[handler-fn] Received event: {:?}", event); - - let (event, _) = event.into_parts(); - - Ok(Response { - message: event.command.to_uppercase(), - }) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(service_fn(handler)).await -} diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs deleted file mode 100644 index 0bf31e43..00000000 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_runtime::{Error, LambdaEvent, Service}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service> for MyHandler { - type Error = Error; - type Future = Pin>>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[runtime-trait] Service was already ready"); - } else { - info!("[runtime-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: LambdaEvent) -> Self::Future { - self.invoke_count += 1; - info!("[runtime-trait] Received event {}: {:?}", self.invoke_count, request); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[runtime-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[runtime-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response { - message: request.payload.command.to_uppercase(), - }))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs new file mode 100644 index 00000000..9989da43 --- /dev/null +++ b/lambda-integration-tests/src/helloworld.rs @@ -0,0 +1,26 @@ +use aws_lambda_events::{ + apigw::{ApiGatewayProxyRequest, ApiGatewayProxyResponse}, + http::HeaderMap, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(_event: LambdaEvent) -> Result { + let mut headers = HeaderMap::new(); + headers.insert("content-type", "text/html".parse().unwrap()); + let resp = ApiGatewayProxyResponse { + status_code: 200, + multi_value_headers: headers.clone(), + is_base64_encoded: false, + body: Some("Hello world!".into()), + headers, + }; + Ok(resp) +} diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index d148c264..1aa69fc8 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -1,325 +1,62 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 +Description: maxday-test + +Parameters: + LambdaRole: + Type: String + SecretToken: + Type: String Globals: Function: - MemorySize: 128 - Handler: bootstrap - Timeout: 5 + Timeout: 3 Resources: - # Rust function using runtime_fn running on AL2023 - RuntimeFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL2 - RuntimeFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL1 - RuntimeFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2023 - RuntimeTraitAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2 - RuntimeTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL1 - RuntimeTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL2023 - HttpFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ + API: + Type: AWS::Serverless::Api + Properties: + StageName: integ-test + Auth: + DefaultAuthorizer: MyLambdaAuthorizer + Authorizers: + MyLambdaAuthorizer: + FunctionArn: !GetAtt AuthorizerFunction.Arn + HelloWorldFunction: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: helloworld + Properties: + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 + Role: !Ref LambdaRole Events: - ApiGet: + HelloWorld: Type: Api Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait + RestApiId: !Ref API + Path: /hello + Method: get - # Rust function using lambda_http::service_fn running on AL2 - HttpFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2023 - HttpTraitAl2023: + AuthorizerFunction: Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: authorizer Properties: - CodeUri: ../build/http-trait/ + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2 - HttpTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL1 - HttpFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL1 - HttpTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL2 - PythonAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.9 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL1 - Python: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.7 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - LogsTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/logs-trait/ - - ExtensionFn: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-fn/ - - ExtensionTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-trait/ + Role: !Ref LambdaRole + Environment: + Variables: + SECRET_TOKEN: !Ref SecretToken Outputs: - RuntimeFnAl2: - Value: !GetAtt RuntimeFnAl2.Arn - RuntimeFn: - Value: !GetAtt RuntimeFn.Arn - RuntimeTraitAl2: - Value: !GetAtt RuntimeTraitAl2.Arn - RuntimeTrait: - Value: !GetAtt RuntimeTrait.Arn - PythonAl2: - Value: !GetAtt PythonAl2.Arn - Python: - Value: !GetAtt Python.Arn - - RestApiUrl: - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod" - HttpApiUrl: - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" \ No newline at end of file + HelloApiEndpoint: + Description: "API Gateway endpoint URL for HelloWorld" + Value: !Sub "https://${API}.execute-api.${AWS::Region}.amazonaws.com/integ-test/hello/" \ No newline at end of file diff --git a/lambda-integration-tests/tests/integration_test.rs b/lambda-integration-tests/tests/integration_test.rs new file mode 100644 index 00000000..557b8e0d --- /dev/null +++ b/lambda-integration-tests/tests/integration_test.rs @@ -0,0 +1,12 @@ +#[test] +fn test_calling_lambda_should_return_200() { + let test_endpoint = std::env::var("TEST_ENDPOINT").expect("could not read TEST_ENDPOINT"); + let secret_token = std::env::var("SECRET_TOKEN").expect("could not read SECRET_TOKEN"); + let client = reqwest::blocking::Client::new(); + let res = client + .get(test_endpoint) + .header("Authorization", secret_token) + .send() + .expect("could not the request"); + assert_eq!(res.status(), 200); +} From 622820458deb0dab6fb2712fbe9c230af960e691 Mon Sep 17 00:00:00 2001 From: msiqueira <42478839+mksiq@users.noreply.github.com> Date: Sat, 24 Aug 2024 22:20:00 -0400 Subject: [PATCH 335/394] Fix trivial typo in docs of Tracing (#918) --- lambda-runtime-api-client/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 51e187e4..bdbdc3d8 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -23,7 +23,7 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; /// if they're configured for your function. /// /// This subscriber sets the logging level based on environment variables: -/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes predecence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes precedence over any other environment variables. /// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. /// - if none of those two variables are set, use `INFO` as the logging level. /// From 27191d0c4f2ce34ce982df7a67822b6a8d023bf8 Mon Sep 17 00:00:00 2001 From: Mohammed Laota <39041283+mlaota@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:50:09 -0500 Subject: [PATCH 336/394] feat: ergonomic improvements to HTTP payload deserialization (#921) BREAKING CHANGE: adds additional methods to the `RequestPayloadExt` trait. implements RFC #917 --- lambda-http/src/ext/request.rs | 525 ++++++++++++++++++++++++++++----- 1 file changed, 458 insertions(+), 67 deletions(-) diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index 11c49012..c56518f6 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -20,6 +20,36 @@ pub enum PayloadError { WwwFormUrlEncoded(SerdeError), } +/// Indicates a problem processing a JSON payload. +#[derive(Debug)] +pub enum JsonPayloadError { + /// Problem deserializing a JSON payload. + Parsing(serde_json::Error), +} + +/// Indicates a problem processing an x-www-form-urlencoded payload. +#[derive(Debug)] +pub enum FormUrlEncodedPayloadError { + /// Problem deserializing an x-www-form-urlencoded payload. + Parsing(SerdeError), +} + +impl From for PayloadError { + fn from(err: JsonPayloadError) -> Self { + match err { + JsonPayloadError::Parsing(inner_err) => PayloadError::Json(inner_err), + } + } +} + +impl From for PayloadError { + fn from(err: FormUrlEncodedPayloadError) -> Self { + match err { + FormUrlEncodedPayloadError::Parsing(inner_err) => PayloadError::WwwFormUrlEncoded(inner_err), + } + } +} + impl fmt::Display for PayloadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -41,55 +71,7 @@ impl Error for PayloadError { } } -/// Extensions for `lambda_http::Request` structs. -/// -/// # Examples -/// -/// A request's body can be deserialized if its correctly encoded as per -/// the request's `Content-Type` header. The two supported content types are -/// `application/x-www-form-urlencoded` and `application/json`. -/// -/// The following handler will work an http request body of `x=1&y=2` -/// as well as `{"x":1, "y":2}` respectively. -/// -/// ```rust,no_run -/// use lambda_http::{ -/// service_fn, Body, Context, Error, IntoResponse, Request, RequestPayloadExt, Response, -/// }; -/// use serde::Deserialize; -/// -/// #[derive(Debug, Default, Deserialize)] -/// struct Args { -/// #[serde(default)] -/// x: usize, -/// #[serde(default)] -/// y: usize -/// } -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Error> { -/// lambda_http::run(service_fn(add)).await?; -/// Ok(()) -/// } -/// -/// async fn add( -/// request: Request -/// ) -> Result, Error> { -/// let args: Args = request.payload() -/// .unwrap_or_else(|_parse_err| None) -/// .unwrap_or_default(); -/// Ok( -/// Response::new( -/// format!( -/// "{} + {} = {}", -/// args.x, -/// args.y, -/// args.x + args.y -/// ).into() -/// ) -/// ) -/// } -/// ``` +/// Extends `http::Request` with payload deserialization helpers. pub trait RequestPayloadExt { /// Return the result of a payload parsed into a type that implements [`serde::Deserialize`] /// @@ -97,11 +79,148 @@ pub trait RequestPayloadExt { /// and `application/json` flavors of content type /// are supported /// - /// A [`PayloadError`] will be returned for undeserializable - /// payloads. If no body is provided, `Ok(None)` will be returned. + /// A [`PayloadError`] will be returned for undeserializable payloads. + /// If no body is provided, the content-type header is missing, + /// or the content-type header is unsupported, then `Ok(None)` will + /// be returned. Note that a blank body (e.g. an empty string) is treated + /// like a present payload by some deserializers and may result in an error. + /// + /// ### Examples + /// + /// A request's body can be deserialized if its correctly encoded as per + /// the request's `Content-Type` header. The two supported content types are + /// `application/x-www-form-urlencoded` and `application/json`. + /// + /// The following handler will work an http request body of `x=1&y=2` + /// as well as `{"x":1, "y":2}` respectively. + /// + /// ```rust,no_run + /// use lambda_http::{ + /// service_fn, Body, Context, Error, IntoResponse, Request, RequestPayloadExt, Response, + /// }; + /// use serde::Deserialize; + /// + /// #[derive(Debug, Default, Deserialize)] + /// struct Args { + /// #[serde(default)] + /// x: usize, + /// #[serde(default)] + /// y: usize + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// lambda_http::run(service_fn(add)).await?; + /// Ok(()) + /// } + /// + /// async fn add( + /// request: Request + /// ) -> Result, Error> { + /// let args: Args = request.payload() + /// .unwrap_or_else(|_parse_err| None) + /// .unwrap_or_default(); + /// Ok( + /// Response::new( + /// format!( + /// "{} + {} = {}", + /// args.x, + /// args.y, + /// args.x + args.y + /// ).into() + /// ) + /// ) + /// } fn payload(&self) -> Result, PayloadError> where D: DeserializeOwned; + + /// Attempts to deserialize the request payload as JSON. When there is no payload, + /// `Ok(None)` is returned. + /// + /// ### Errors + /// + /// If a present payload is not a valid JSON payload matching the annotated type, + /// a [`JsonPayloadError`] is returned. + /// + /// ### Examples + /// + /// #### 1. Parsing a JSONString. + /// ```ignore + /// let req = http::Request::builder() + /// .body(Body::from("\"I am a JSON string\"")) + /// .expect("failed to build request"); + /// match req.json::() { + /// Ok(Some(json)) => assert_eq!(json, "I am a JSON string"), + /// Ok(None) => panic!("payload is missing."), + /// Err(err) => panic!("error processing json: {err:?}"), + /// } + /// ``` + /// + /// #### 2. Parsing a JSONObject. + /// ```ignore + /// #[derive(Deserialize, Eq, PartialEq, Debug)] + /// struct Person { + /// name: String, + /// age: u8, + /// } + /// + /// let req = http::Request::builder() + /// .body(Body::from(r#"{"name": "Adam", "age": 23}"#)) + /// .expect("failed to build request"); + /// + /// match req.json::() { + /// Ok(Some(person)) => assert_eq!( + /// person, + /// Person { + /// name: "Adam".to_string(), + /// age: 23 + /// } + /// ), + /// Ok(None) => panic!("payload is missing"), + /// Err(JsonPayloadError::Parsing(err)) => { + /// if err.is_data() { + /// panic!("payload does not match Person schema: {err:?}") + /// } + /// if err.is_syntax() { + /// panic!("payload is invalid json: {err:?}") + /// } + /// panic!("failed to parse json: {err:?}") + /// } + /// } + /// ``` + fn json(&self) -> Result, JsonPayloadError> + where + D: DeserializeOwned; + + /// Attempts to deserialize the request payload as an application/x-www-form-urlencoded + /// content type. When there is no payload, `Ok(None)` is returned. + /// + /// ### Errors + /// + /// If a present payload is not a valid application/x-www-form-urlencoded payload + /// matching the annotated type, a [`FormUrlEncodedPayloadError`] is returned. + /// + /// ### Examples + /// ```ignore + /// let req = http::Request::builder() + /// .body(Body::from("name=Adam&age=23")) + /// .expect("failed to build request"); + /// match req.form_url_encoded::() { + /// Ok(Some(person)) => assert_eq!( + /// person, + /// Person { + /// name: "Adam".to_string(), + /// age: 23 + /// } + /// ), + /// Ok(None) => panic!("payload is missing."), + /// Err(err) => panic!("error processing payload: {err:?}"), + /// } + /// ``` + fn form_url_encoded(&self) -> Result, FormUrlEncodedPayloadError> + where + D: DeserializeOwned; } impl RequestPayloadExt for http::Request { @@ -114,28 +233,47 @@ impl RequestPayloadExt for http::Request { .map(|ct| match ct.to_str() { Ok(content_type) => { if content_type.starts_with("application/x-www-form-urlencoded") { - return serde_urlencoded::from_bytes::(self.body().as_ref()) - .map_err(PayloadError::WwwFormUrlEncoded) - .map(Some); + return self.form_url_encoded().map_err(PayloadError::from); } else if content_type.starts_with("application/json") { - return serde_json::from_slice::(self.body().as_ref()) - .map_err(PayloadError::Json) - .map(Some); + return self.json().map_err(PayloadError::from); } - Ok(None) } _ => Ok(None), }) .unwrap_or_else(|| Ok(None)) } + + fn json(&self) -> Result, JsonPayloadError> + where + D: DeserializeOwned, + { + if self.body().is_empty() { + return Ok(None); + } + serde_json::from_slice::(self.body().as_ref()) + .map(Some) + .map_err(JsonPayloadError::Parsing) + } + + fn form_url_encoded(&self) -> Result, FormUrlEncodedPayloadError> + where + D: DeserializeOwned, + { + if self.body().is_empty() { + return Ok(None); + } + serde_urlencoded::from_bytes::(self.body().as_ref()) + .map(Some) + .map_err(FormUrlEncodedPayloadError::Parsing) + } } #[cfg(test)] mod tests { use serde::Deserialize; - use super::RequestPayloadExt; + use super::{FormUrlEncodedPayloadError, JsonPayloadError, RequestPayloadExt}; use crate::Body; @@ -145,6 +283,20 @@ mod tests { baz: usize, } + fn get_test_payload_as_json_body() -> Body { + Body::from(r#"{"foo":"bar", "baz": 2}"#) + } + + fn assert_eq_test_payload(payload: Option) { + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + #[test] fn requests_have_form_post_parsable_payloads() { let request = http::Request::builder() @@ -165,16 +317,10 @@ mod tests { fn requests_have_json_parsable_payloads() { let request = http::Request::builder() .header("Content-Type", "application/json") - .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .body(get_test_payload_as_json_body()) .expect("failed to build request"); let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); + assert_eq_test_payload(payload) } #[test] @@ -217,4 +363,249 @@ mod tests { let payload: Option = request.payload().unwrap_or_default(); assert_eq!(payload, None); } + + #[test] + fn requests_omitting_body_returns_none() { + let request = http::Request::builder() + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[test] + fn requests_with_json_content_type_hdr_omitting_body_returns_none() { + let request = http::Request::builder() + .header("Content-Type", "application/json; charset=UTF-8") + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[test] + fn requests_with_formurlencoded_content_type_hdr_omitting_body_returns_none() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[derive(Deserialize, Eq, PartialEq, Debug)] + struct Person { + name: String, + age: u8, + } + + #[test] + fn json_fn_parses_json_strings() { + let req = http::Request::builder() + .body(Body::from("\"I am a JSON string\"")) + .expect("failed to build request"); + match req.json::() { + Ok(Some(json)) => assert_eq!(json, "I am a JSON string"), + Ok(None) => panic!("payload is missing."), + Err(err) => panic!("error processing json: {err:?}"), + } + } + + #[test] + fn json_fn_parses_objects() { + let req = http::Request::builder() + .body(Body::from(r#"{"name": "Adam", "age": 23}"#)) + .expect("failed to build request"); + + match req.json::() { + Ok(Some(person)) => assert_eq!( + person, + Person { + name: "Adam".to_string(), + age: 23 + } + ), + Ok(None) => panic!("request data missing"), + Err(JsonPayloadError::Parsing(err)) => { + if err.is_data() { + panic!("payload does not match Person: {err:?}") + } + if err.is_syntax() { + panic!("invalid json: {err:?}") + } + panic!("failed to parse json: {err:?}") + } + } + } + + #[test] + fn json_fn_parses_list_of_objects() { + let req = http::Request::builder() + .body(Body::from( + r#"[{"name": "Adam", "age": 23}, {"name": "Sarah", "age": 47}]"#, + )) + .expect("failed to build request"); + let expected_result = vec![ + Person { + name: "Adam".to_string(), + age: 23, + }, + Person { + name: "Sarah".to_string(), + age: 47, + }, + ]; + let result: Vec = req.json().expect("invalid payload").expect("missing payload"); + assert_eq!(result, expected_result); + } + + #[test] + fn json_fn_parses_nested_objects() { + #[derive(Deserialize, Eq, PartialEq, Debug)] + struct Pet { + name: String, + owner: Person, + } + + let req = http::Request::builder() + .body(Body::from( + r#"{"name": "Gumball", "owner": {"name": "Adam", "age": 23}}"#, + )) + .expect("failed to build request"); + + let expected_result = Pet { + name: "Gumball".to_string(), + owner: Person { + name: "Adam".to_string(), + age: 23, + }, + }; + let result: Pet = req.json().expect("invalid payload").expect("missing payload"); + assert_eq!(result, expected_result); + } + + #[test] + fn json_fn_accepts_request_with_content_type_header() { + let request = http::Request::builder() + .header("Content-Type", "application/json") + .body(get_test_payload_as_json_body()) + .expect("failed to build request"); + let payload: Option = request.json().unwrap(); + assert_eq_test_payload(payload) + } + + #[test] + fn json_fn_accepts_request_without_content_type_header() { + let request = http::Request::builder() + .body(get_test_payload_as_json_body()) + .expect("failed to build request"); + let payload: Option = request.json().expect("failed to parse json"); + assert_eq_test_payload(payload) + } + + #[test] + fn json_fn_given_nonjson_payload_returns_syntax_error() { + let request = http::Request::builder() + .body(Body::Text(String::from("Not a JSON"))) + .expect("failed to build request"); + let payload = request.json::(); + assert!(payload.is_err()); + + if let Err(JsonPayloadError::Parsing(err)) = payload { + assert!(err.is_syntax()) + } else { + panic!( + "{}", + format!("payload should have caused a parsing error. instead, it was {payload:?}") + ); + } + } + + #[test] + fn json_fn_given_unexpected_payload_shape_returns_data_error() { + let request = http::Request::builder() + .body(Body::from(r#"{"foo":"bar", "baz": "!SHOULD BE A NUMBER!"}"#)) + .expect("failed to build request"); + let result = request.json::(); + + if let Err(JsonPayloadError::Parsing(err)) = result { + assert!(err.is_data()) + } else { + panic!( + "{}", + format!("payload should have caused a parsing error. instead, it was {result:?}") + ); + } + } + + #[test] + fn json_fn_given_empty_payload_returns_none() { + let empty_request = http::Request::default(); + let payload: Option = empty_request.json().expect("failed to parse json"); + assert_eq!(payload, None) + } + + #[test] + fn form_url_encoded_fn_parses_forms() { + let req = http::Request::builder() + .body(Body::from("name=Adam&age=23")) + .expect("failed to build request"); + match req.form_url_encoded::() { + Ok(Some(person)) => assert_eq!( + person, + Person { + name: "Adam".to_string(), + age: 23 + } + ), + Ok(None) => panic!("payload is missing."), + Err(err) => panic!("error processing payload: {err:?}"), + } + } + + #[test] + fn form_url_encoded_fn_accepts_request_with_content_type_header() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded") + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.form_url_encoded().unwrap(); + assert_eq_test_payload(payload); + } + + #[test] + fn form_url_encoded_fn_accepts_request_without_content_type_header() { + let request = http::Request::builder() + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.form_url_encoded().expect("failed to parse form"); + assert_eq_test_payload(payload); + } + + #[test] + fn form_url_encoded_fn_given_non_form_urlencoded_payload_errors() { + let request = http::Request::builder() + .body(Body::Text(String::from("Not a url-encoded form"))) + .expect("failed to build request"); + let payload = request.form_url_encoded::(); + assert!(payload.is_err()); + assert!(matches!(payload, Err(FormUrlEncodedPayloadError::Parsing(_)))); + } + + #[test] + fn form_url_encoded_fn_given_unexpected_payload_shape_errors() { + let request = http::Request::builder() + .body(Body::from("foo=bar&baz=SHOULD_BE_A_NUMBER")) + .expect("failed to build request"); + let result = request.form_url_encoded::(); + assert!(result.is_err()); + assert!(matches!(result, Err(FormUrlEncodedPayloadError::Parsing(_)))); + } + + #[test] + fn form_url_encoded_fn_given_empty_payload_returns_none() { + let empty_request = http::Request::default(); + let payload: Option = empty_request.form_url_encoded().expect("failed to parse form"); + assert_eq!(payload, None); + } } From 20206d70b4baef445e6d196e29e6496e21f95c81 Mon Sep 17 00:00:00 2001 From: Martin Bartlett Date: Mon, 16 Sep 2024 19:38:39 +0200 Subject: [PATCH 337/394] Use event filter (#925) * Use LevelFilter instead of Level Originally a `Level` was parsed from one of two environment variables (or defaulted) and then converted into a `LevelFilter` before initializing the subscriber. However, this precludes using `RUST_LOG=off` since `Level` does not recognize that as valid, resulting in `Level::INFO` (the default) being used. `LevelFilter` (to which the above is converted anyway) _does_ allow the value to be `off` - so it seems a little more flexible (and very very minutely faster) to parse the env var or default value directly into a `LevelFilter`. * rustfmt --------- Co-authored-by: Martin Bartlett --- lambda-runtime-api-client/src/tracing.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index bdbdc3d8..09f41991 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -33,14 +33,15 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; pub fn init_default_subscriber() { let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); - let log_level = Level::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(Level::INFO); + let log_level = + LevelFilter::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(LevelFilter::INFO); let collector = tracing_subscriber::fmt() .with_target(false) .without_time() .with_env_filter( EnvFilter::builder() - .with_default_directive(LevelFilter::from_level(log_level).into()) + .with_default_directive(log_level.into()) .from_env_lossy(), ); From 8572af6a5777ac190278d567c5ccba6a56929860 Mon Sep 17 00:00:00 2001 From: Mats Jun Date: Wed, 18 Sep 2024 04:53:54 +0200 Subject: [PATCH 338/394] fix: expose CloudWatchMetricAlarm and CloudWatchCompositeAlarm (#926) * fix: expose CloudWatchMetricAlarm and CloudWatchCompositeAlarm These types are referred to by the CloudWatchAlarm type, but they previously not exported by the crate and left unused in its module. * chore: apply rustfmt --- lambda-events/src/event/cloudwatch_alarms/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 5c4b704d..01174566 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -36,12 +36,11 @@ where } /// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. -#[allow(unused)] -type CloudWatchMetricAlarm = CloudWatchAlarm; +pub type CloudWatchMetricAlarm = + CloudWatchAlarm; /// `CloudWatchCompositeAlarm` is the structure of an event triggered by CloudWatch composite alarms. -#[allow(unused)] -type CloudWatchCompositeAlarm = +pub type CloudWatchCompositeAlarm = CloudWatchAlarm; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] From c3575f69aa49a035e5b2e72c02bea7ff7228492f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 23 Sep 2024 07:51:58 -0700 Subject: [PATCH 339/394] Add AppConfig feature flags example (#928) This example shows how to integrate AppConfig with Rust Lambda functions. It includes CDK constructs to deploy the basic AppConfig scaffolding, and the AppConfig Lambda extension to reduce the latency fetching the AppConfig configuration. --- .gitignore | 3 + .../.gitignore | 1 + .../Cargo.toml | 24 + .../README.md | 65 + .../cdk/.npmignore | 6 + .../cdk/README.md | 14 + .../cdk/bin/cdk.ts | 21 + .../cdk/cdk.json | 72 + .../cdk/jest.config.js | 8 + .../cdk/lib/cdk-stack.ts | 110 + .../cdk/package-lock.json | 7550 +++++++++++++++++ .../cdk/package.json | 28 + .../cdk/test/cdk.test.ts | 17 + .../cdk/tsconfig.json | 31 + .../src/appconfig.rs | 88 + .../src/main.rs | 126 + 16 files changed, 8164 insertions(+) create mode 100644 examples/advanced-appconfig-feature-flags/.gitignore create mode 100644 examples/advanced-appconfig-feature-flags/Cargo.toml create mode 100644 examples/advanced-appconfig-feature-flags/README.md create mode 100644 examples/advanced-appconfig-feature-flags/cdk/.npmignore create mode 100644 examples/advanced-appconfig-feature-flags/cdk/README.md create mode 100644 examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/cdk.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/jest.config.js create mode 100644 examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/package-lock.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/package.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/tsconfig.json create mode 100644 examples/advanced-appconfig-feature-flags/src/appconfig.rs create mode 100644 examples/advanced-appconfig-feature-flags/src/main.rs diff --git a/.gitignore b/.gitignore index d8feb244..d5e188a4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ output.json .aws-sam build .vscode + +node_modules +cdk.out diff --git a/examples/advanced-appconfig-feature-flags/.gitignore b/examples/advanced-appconfig-feature-flags/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/Cargo.toml b/examples/advanced-appconfig-feature-flags/Cargo.toml new file mode 100644 index 00000000..52ebb843 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "lambda-appconfig" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +async-trait = "0.1.68" +lambda_runtime = "0.13" +reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-appconfig-feature-flags/README.md b/examples/advanced-appconfig-feature-flags/README.md new file mode 100644 index 00000000..3a95ec9f --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/README.md @@ -0,0 +1,65 @@ +# Rust Lambda with AppConfig Feature Flag + +This project demonstrates a Rust-based AWS Lambda function that uses AWS AppConfig for feature flagging. The function is deployed using AWS CDK and includes automatic rollback capabilities based on error rates. + +## Lambda Function (src/main.rs) + +The Lambda function is written in Rust and does the following: + +1. Integrates with AWS AppConfig to fetch configuration at runtime. +2. Uses a feature flag to determine whether to respond in Spanish. +3. Processes incoming events. +4. Returns a response based on the event and the current feature flag state. + +The function is designed to work with the AWS AppConfig Extension for Lambda, allowing for efficient configuration retrieval. + +## Deployment (cdk directory) + +The project uses AWS CDK for infrastructure as code and deployment. To deploy the project: + +1. Ensure you have the AWS CDK CLI installed and configured. +2. Navigate to the `cdk` directory. +3. Install dependencies: + ``` + npm install + ``` +4. Build the CDK stack: + ``` + npm run build + ``` +5. Deploy the stack: + ``` + cdk deploy + ``` + +## AWS Resources (cdk/lib/cdk-stack.ts) + +The CDK stack creates the following AWS resources: + +1. **AppConfig Application**: Named "MyRustLambdaApp", this is the container for your configuration and feature flags. + +2. **AppConfig Environment**: A "Production" environment is created within the application. + +3. **AppConfig Configuration Profile**: Defines the schema and validation for your configuration. + +4. **AppConfig Hosted Configuration Version**: Contains the actual configuration data, including the "spanish-response" feature flag. + +5. **AppConfig Deployment Strategy**: Defines how configuration changes are rolled out. + +6. **Lambda Function**: A Rust-based function that uses the AppConfig configuration. + - Uses the AWS AppConfig Extension Layer for efficient configuration retrieval. + - Configured with ARM64 architecture and 128MB of memory. + - 30-second timeout. + +7. **CloudWatch Alarm**: Monitors the Lambda function's error rate. + - Triggers if there are more than 5 errors per minute. + +8. **AppConfig Deployment**: Connects all AppConfig components and includes a rollback trigger based on the CloudWatch alarm. + +9. **IAM Role**: Grants the Lambda function permissions to interact with AppConfig and CloudWatch. + +This setup allows for feature flagging with automatic rollback capabilities, ensuring rapid and safe deployment of new features or configurations. + +## Usage + +After deployment, you can update the feature flag in AppConfig to control the Lambda function's behavior. The function will automatically fetch the latest configuration, and if error rates exceed the threshold, AppConfig will automatically roll back to the previous stable configuration. diff --git a/examples/advanced-appconfig-feature-flags/cdk/.npmignore b/examples/advanced-appconfig-feature-flags/cdk/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/advanced-appconfig-feature-flags/cdk/README.md b/examples/advanced-appconfig-feature-flags/cdk/README.md new file mode 100644 index 00000000..9315fe5b --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK TypeScript project + +This is a blank project for CDK development with TypeScript. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +## Useful commands + +* `npm run build` compile typescript to js +* `npm run watch` watch for changes and compile +* `npm run test` perform the jest unit tests +* `npx cdk deploy` deploy this stack to your default AWS account/region +* `npx cdk diff` compare deployed stack with current state +* `npx cdk synth` emits the synthesized CloudFormation template diff --git a/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts b/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts new file mode 100644 index 00000000..1d8bfd97 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { CdkStack } from '../lib/cdk-stack'; + +const app = new cdk.App(); +new CdkStack(app, 'CdkStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/cdk/cdk.json b/examples/advanced-appconfig-feature-flags/cdk/cdk.json new file mode 100644 index 00000000..87963c5f --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/cdk.json @@ -0,0 +1,72 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/jest.config.js b/examples/advanced-appconfig-feature-flags/cdk/jest.config.js new file mode 100644 index 00000000..08263b89 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts b/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts new file mode 100644 index 00000000..3e76deb9 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts @@ -0,0 +1,110 @@ +import * as cdk from 'aws-cdk-lib'; +import * as appconfig from 'aws-cdk-lib/aws-appconfig'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { RustFunction } from 'cargo-lambda-cdk'; + +export class CdkStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create AppConfig Application + const application = new appconfig.CfnApplication(this, 'MyApplication', { + name: 'MyRustLambdaApp', + }); + + // Create AppConfig Environment + const environment = new appconfig.CfnEnvironment(this, 'MyEnvironment', { + applicationId: application.ref, + name: 'Production', + }); + + // Create AppConfig Configuration Profile + const configProfile = new appconfig.CfnConfigurationProfile(this, 'MyConfigProfile', { + applicationId: application.ref, + name: 'MyConfigProfile', + locationUri: 'hosted', + }); + + // Create AppConfig Hosted Configuration Version + const hostedConfig = new appconfig.CfnHostedConfigurationVersion(this, 'MyHostedConfig', { + applicationId: application.ref, + configurationProfileId: configProfile.ref, + content: JSON.stringify({ + 'spanish-response': false + }), + contentType: 'application/json', + }); + + // Create AppConfig Deployment Strategy + const deploymentStrategy = new appconfig.CfnDeploymentStrategy(this, 'MyDeploymentStrategy', { + name: 'MyDeploymentStrategy', + deploymentDurationInMinutes: 0, + growthFactor: 100, + replicateTo: 'NONE', + }); + + const architecture = lambda.Architecture.ARM_64; + const layerVersion = architecture === lambda.Architecture.ARM_64 ? '68' : '60'; + + // Create Lambda function using cargo-lambda-cdk + const myFunction = new RustFunction(this, 'MyRustFunction', { + functionName: 'my-rust-lambda', + manifestPath: '..', // Points to the parent directory where Cargo.toml is located + architecture, + memorySize: 128, + timeout: cdk.Duration.seconds(30), + environment: { + APPLICATION_ID: application.ref, + ENVIRONMENT_ID: environment.ref, + CONFIGURATION_PROFILE_ID: configProfile.ref, + AWS_APPCONFIG_EXTENSION_PREFETCH_LIST: `/applications/${application.ref}/environments/${environment.ref}/configurations/${configProfile.ref}`, + }, + layers: [ + lambda.LayerVersion.fromLayerVersionArn( + this, + 'AppConfigExtensionLayer', + `arn:aws:lambda:${this.region}:027255383542:layer:AWS-AppConfig-Extension:${layerVersion}` + ), + ], + }); + + // Create CloudWatch Alarm for rollback + const errorRateAlarm = new cloudwatch.Alarm(this, 'ErrorRateAlarm', { + metric: myFunction.metricErrors({ + period: cdk.Duration.minutes(1), + statistic: 'sum', + }), + threshold: 5, + evaluationPeriods: 1, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + alarmDescription: 'Alarm if the error rate is greater than 5 errors per minute', + }); + + // Create AppConfig Deployment with rollback configuration + new appconfig.CfnDeployment(this, 'MyDeployment', { + applicationId: application.ref, + environmentId: environment.ref, + deploymentStrategyId: deploymentStrategy.ref, + configurationProfileId: configProfile.ref, + configurationVersion: hostedConfig.ref, + tags: [ + { + key: 'RollbackTrigger', + value: errorRateAlarm.alarmArn, + }, + ], + }); + + // Grant AppConfig permissions to the Lambda function + myFunction.addToRolePolicy(new cdk.aws_iam.PolicyStatement({ + actions: [ + 'appconfig:GetConfiguration', + 'appconfig:StartConfigurationSession', + 'cloudwatch:PutMetricData', + ], + resources: ['*'], + })); + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json new file mode 100644 index 00000000..61c9a537 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json @@ -0,0 +1,7550 @@ +{ + "name": "cdk", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cdk", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "2.159.1", + "cargo-lambda-cdk": "^0.0.22", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "cdk": "bin/cdk.js" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "20.14.2", + "aws-cdk": "2.159.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", + "typescript": "~5.4.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.203", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", + "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "36.3.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", + "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dependencies": { + "jsonschema": "^1.4.1", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.18.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/aws-cdk": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.159.1.tgz", + "integrity": "sha512-bkJOxic/NpJYQCF3MQhfyJVlFtIzMJeVGZp9jZa7TczxJp79Q/TNKzVJYv6GFabNS1wglGPfWkFB/rIJlRhJkg==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", + "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cargo-lambda-cdk": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.22.tgz", + "integrity": "sha512-lpRh4TFR03FSWw/Ji6D3ZD18mAJjeWvU8ST5UdS6RwbdSXQT/0TbEjG/ccMCpqNGtrlx+txQ8WYURgtNbnDKcw==", + "bundleDependencies": [ + "js-toml" + ], + "dependencies": { + "js-toml": "^0.1.1" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.63.0", + "constructs": "^10.0.5" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@babel/runtime-corejs3": { + "version": "7.23.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/gast": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/types": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/utils": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/chevrotain": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/core-js-pure": { + "version": "3.33.2", + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/js-toml": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/lodash": { + "version": "4.17.21", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regenerator-runtime": { + "version": "0.14.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regexp-to-ast": { + "version": "0.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/xregexp": { + "version": "5.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.27", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", + "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.203", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", + "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" + }, + "@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "@aws-cdk/cloud-assembly-schema": { + "version": "36.3.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", + "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "requires": { + "jsonschema": "^1.4.1", + "semver": "^7.6.3" + }, + "dependencies": { + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "semver": { + "version": "7.6.3", + "bundled": true + } + } + }, + "@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + } + }, + "@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true + }, + "@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true + }, + "@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "dev": true, + "requires": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + } + }, + "@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.8" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.8" + } + }, + "@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + } + }, + "@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "requires": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + } + }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + } + }, + "@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "aws-cdk": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.159.1.tgz", + "integrity": "sha512-bkJOxic/NpJYQCF3MQhfyJVlFtIzMJeVGZp9jZa7TczxJp79Q/TNKzVJYv6GFabNS1wglGPfWkFB/rIJlRhJkg==", + "dev": true, + "requires": { + "fsevents": "2.3.2" + } + }, + "aws-cdk-lib": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", + "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true + }, + "ajv": { + "version": "8.17.1", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fast-uri": { + "version": "3.0.1", + "bundled": true + }, + "fs-extra": { + "version": "11.2.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "ignore": { + "version": "5.3.2", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "bundled": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", + "bundled": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", + "bundled": true + }, + "semver": { + "version": "7.6.3", + "bundled": true + }, + "slice-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.8.2", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "bundled": true + }, + "uri-js": { + "version": "4.4.1", + "requires": { + "punycode": "^2.1.0" + } + }, + "yaml": { + "version": "1.10.2", + "bundled": true + } + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "dev": true + }, + "cargo-lambda-cdk": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.22.tgz", + "integrity": "sha512-lpRh4TFR03FSWw/Ji6D3ZD18mAJjeWvU8ST5UdS6RwbdSXQT/0TbEjG/ccMCpqNGtrlx+txQ8WYURgtNbnDKcw==", + "requires": { + "js-toml": "^0.1.1" + }, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.23.2", + "bundled": true, + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } + }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "bundled": true + }, + "@chevrotain/utils": { + "version": "10.5.0", + "bundled": true + }, + "chevrotain": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "core-js-pure": { + "version": "3.33.2", + "bundled": true + }, + "js-toml": { + "version": "0.1.1", + "bundled": true, + "requires": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "lodash": { + "version": "4.17.21", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.14.0", + "bundled": true + }, + "regexp-to-ast": { + "version": "0.5.0", + "bundled": true + }, + "xregexp": { + "version": "5.1.1", + "bundled": true, + "requires": { + "@babel/runtime-corejs3": "^7.16.5" + } + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "requires": {} + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.5.27", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", + "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "requires": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + } + }, + "jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + } + }, + "jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "requires": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + } + }, + "jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "requires": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "requires": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/package.json b/examples/advanced-appconfig-feature-flags/cdk/package.json new file mode 100644 index 00000000..83cb9552 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/package.json @@ -0,0 +1,28 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "20.14.2", + "aws-cdk": "2.159.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", + "typescript": "~5.4.5" + }, + "dependencies": { + "aws-cdk-lib": "2.159.1", + "cargo-lambda-cdk": "^0.0.22", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts b/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts new file mode 100644 index 00000000..1e6b29c8 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Cdk from '../lib/cdk-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/cdk-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Cdk.CdkStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json b/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json new file mode 100644 index 00000000..aaa7dc51 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/examples/advanced-appconfig-feature-flags/src/appconfig.rs b/examples/advanced-appconfig-feature-flags/src/appconfig.rs new file mode 100644 index 00000000..9dba6610 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/src/appconfig.rs @@ -0,0 +1,88 @@ +//! # Rust AppConfig Client +//! +//! This library provides a Rust client for interacting with the AWS AppConfig local extension for AWS Lambda and ECS. +//! It allows you to retrieve configuration data for your application based on the application ID, environment ID, +//! and configuration profile ID. +//! +//! ## Features +//! +//! - Simple API for retrieving configuration data +//! - Asynchronous operations using `tokio` and `reqwest` +//! - Error handling with custom `AppConfigError` type +//! - Deserialization of configuration data into user-defined types +//! +//! ## Usage +//! +//! ```rust +//! use appconfig::{AppConfigClient, ConfigurationFetcher, AppConfigError}; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), AppConfigError> { +//! let client = AppConfigClient::new("app_id", "env_id", "profile_id", 2772); +//! +//! let config: YourConfigType = client.get_configuration().await?; +//! +//! println!("Received config: {:?}", config); +//! +//! Ok(()) +//! } +//! ``` +use async_trait::async_trait; +use reqwest::Client; +use serde::de::DeserializeOwned; +use thiserror::Error; + +#[derive(Clone)] +pub struct AppConfigClient { + client: Client, + application_id: String, + environment_id: String, + configuration_profile_id: String, + port: u16, +} + +impl AppConfigClient { + pub fn new(application_id: &str, environment_id: &str, configuration_profile_id: &str, port: u16) -> Self { + AppConfigClient { + client: Client::new(), + application_id: application_id.to_string(), + environment_id: environment_id.to_string(), + configuration_profile_id: configuration_profile_id.to_string(), + port, + } + } +} + +#[async_trait] +impl ConfigurationFetcher for AppConfigClient { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send, + { + let url = format!( + "http://localhost:{}/applications/{}/environments/{}/configurations/{}", + self.port, self.application_id, self.environment_id, self.configuration_profile_id + ); + + let response = self.client.get(&url).send().await?; + let config: T = response.json().await?; + + Ok(config) + } +} + +#[derive(Error, Debug)] +pub enum AppConfigError { + #[error("Failed to send request: {0}")] + RequestError(#[from] reqwest::Error), + + #[error("Failed to parse JSON: {0}")] + JsonParseError(#[from] serde_json::Error), +} + +#[async_trait] +pub trait ConfigurationFetcher { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send; +} diff --git a/examples/advanced-appconfig-feature-flags/src/main.rs b/examples/advanced-appconfig-feature-flags/src/main.rs new file mode 100644 index 00000000..b7d5e515 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/src/main.rs @@ -0,0 +1,126 @@ +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; +use std::env; + +mod appconfig; +use crate::appconfig::{AppConfigClient, ConfigurationFetcher}; + +#[derive(Deserialize)] +struct Request { + quote: String, +} + +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +#[derive(Deserialize)] +struct AppConfig { + #[serde(rename = "spanish-response")] + spanish_response: bool, + // Add other fields as needed +} + +async fn function_handler( + event: LambdaEvent, + config_fetcher: &T, +) -> Result { + // Extract some useful info from the request + let quote = event.payload.quote; + + // Send a GET request to the local AppConfig endpoint + let config: AppConfig = config_fetcher.get_configuration().await?; + + // Use the feature flag + let msg = if config.spanish_response { + format!("{}, in spanish.", quote) + } else { + format!("{}.", quote) + }; + + // Return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(Response { + req_id: event.context.request_id, + msg, + }) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + // Extract the AppConfig port from the environment + let app_config_port = env::var("AWS_APPCONFIG_EXTENSION_HTTP_PORT") + .unwrap_or_else(|_| "2772".to_string()) + .parse::() + .expect("Invalid port number for AWS_APPCONFIG_EXTENSION_HTTP_PORT"); + + // Create a new AppConfigClient with the extracted port + let app_config_client = AppConfigClient::new( + &env::var("APPLICATION_ID").expect("APPLICATION_ID must be set"), + &env::var("ENVIRONMENT_ID").expect("ENVIRONMENT_ID must be set"), + &env::var("CONFIGURATION_PROFILE_ID").expect("CONFIGURATION_PROFILE_ID must be set"), + app_config_port, + ); + + // Use a closure to capture app_config_client and pass it to function_handler + run(service_fn(|event| function_handler(event, &app_config_client))).await +} + +#[cfg(test)] +mod tests { + use super::*; + use async_trait::async_trait; + use lambda_runtime::Context; + use serde::de::DeserializeOwned; + + struct MockConfigFetcher { + spanish_response: bool, + } + + #[async_trait] + impl ConfigurationFetcher for MockConfigFetcher { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send, + { + let value = serde_json::json!({ + "spanish-response": self.spanish_response + }); + let value: T = serde_json::from_value(value)?; + Ok(value) + } + } + + #[tokio::test] + async fn test_function_handler_english() { + let mock_fetcher = MockConfigFetcher { + spanish_response: false, + }; + let request = Request { + quote: "Hello, world".to_string(), + }; + let context = Context::default(); + let event = LambdaEvent::new(request, context); + + let result = function_handler(event, &mock_fetcher).await.unwrap(); + + assert_eq!(result.msg, "Hello, world."); + } + + #[tokio::test] + async fn test_function_handler_spanish() { + let mock_fetcher = MockConfigFetcher { spanish_response: true }; + let request = Request { + quote: "Hello, world".to_string(), + }; + let context = Context::default(); + let event = LambdaEvent::new(request, context); + + let result = function_handler(event, &mock_fetcher).await.unwrap(); + + assert_eq!(result.msg, "Hello, world, in spanish."); + } +} From 1c6343983ad030396dd84cbac45a35629ff1b907 Mon Sep 17 00:00:00 2001 From: George Hahn Date: Thu, 3 Oct 2024 12:57:00 -0500 Subject: [PATCH 340/394] Update to tower 0.5 (#929) * Update to tower 0.5 * Switch to `tower` from workspace * Update tower version in example --- Cargo.toml | 2 +- examples/opentelemetry-tracing/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c55f0e60..867e9c0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,6 @@ http-serde = "2.0" hyper = "1.0" hyper-util = "0.1.1" pin-project-lite = "0.2" -tower = "0.4" +tower = "0.5" tower-layer = "0.3" tower-service = "0.3" diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index c80b997d..98108d39 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -12,7 +12,7 @@ opentelemetry-stdout = { version = "0.3", features = ["trace"] } pin-project = "1" serde_json = "1.0" tokio = "1" -tower = "0.4" +tower = "0.5" tracing = "0.1" tracing-opentelemetry = "0.23" tracing-subscriber = "0.3" diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index c1afd732..16b6dace 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -35,5 +35,5 @@ tokio = { version = "1.0", features = [ "rt-multi-thread", ] } tokio-stream = "0.1.2" -tower = { version = "0.4", features = ["make", "util"] } +tower = { workspace = true, features = ["make", "util"] } tracing = { version = "0.1", features = ["log"] } From 19c190d6762ea755cb14462b6c0347afb4352a29 Mon Sep 17 00:00:00 2001 From: Raees Iqbal <10067728+RaeesBhatti@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:40:54 -0400 Subject: [PATCH 341/394] Only add URL query if it's not empty (#933) --- lambda-http/src/request.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index afe233eb..a9281b46 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -129,8 +129,10 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request Date: Mon, 18 Nov 2024 20:20:55 +0800 Subject: [PATCH 342/394] Release events 0.16.0 and http 0.14.0 (#938) --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index d9774104..b8d34055 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.15.1" +version = "0.16.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 08b70d4e..46198dca 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.13.0" +version = "0.14.0" authors = [ "David Calavera ", "Harold Sun ", @@ -50,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.15.0" +version = "0.16.0" default-features = false features = ["alb", "apigw"] From 16799d8501ecc69792528d568ace39ddfa674cb5 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 18 Nov 2024 13:10:30 +0000 Subject: [PATCH 343/394] feat: add dependabot (#941) --- .github/dependabot.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..24a9fc5b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "." + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-events" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-extension" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-http" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-integration-tests" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-runtime-api-client" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-runtime" + schedule: + interval: "weekly" \ No newline at end of file From a1f8c05dcc0333dca83661a705cf8f04215de719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:33:30 +0000 Subject: [PATCH 344/394] Update opentelemetry-semantic-conventions requirement from 0.14 to 0.27 (#942) * Update opentelemetry-semantic-conventions requirement from 0.14 to 0.27 Updates the requirements on [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-rust) to permit the latest version. - [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases) - [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-semantic-conventions-0.14.0...opentelemetry-semantic-conventions-0.27.0) --- updated-dependencies: - dependency-name: opentelemetry-semantic-conventions dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: add semconv_experimental flag (#943) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maxime David --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 0f63cd47..b4a7ad3d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -41,7 +41,7 @@ hyper-util = { workspace = true, features = [ ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.14", optional = true } +opentelemetry-semantic-conventions = { version = "0.27", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From f024fd0c3b87791adc7b98c7fd0ed020f05ebb3a Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 9 Dec 2024 11:12:11 +0000 Subject: [PATCH 345/394] fix: clippy error + bump MSRV (#948) * test-ci * fix: bump MSRV * fix: fix GitHub Actions trigger path * fix: clippy errors * fix: cleanup --- .github/workflows/build-events.yml | 4 +++- .github/workflows/build-extension.yml | 6 +++++- .github/workflows/build-runtime.yml | 4 +++- lambda-events/src/custom_serde/codebuild_time.rs | 2 +- lambda-events/src/custom_serde/float_unix_epoch.rs | 2 +- lambda-events/src/custom_serde/http_method.rs | 2 +- lambda-events/src/encodings/http.rs | 2 +- lambda-events/src/event/cloudwatch_alarms/mod.rs | 2 +- lambda-events/src/event/documentdb/events/insert_event.rs | 1 - lambda-events/src/event/s3/object_lambda.rs | 1 - lambda-extension/src/extension.rs | 6 +++--- lambda-http/src/lib.rs | 2 +- lambda-runtime/src/layers/panic.rs | 6 +++--- lambda-runtime/src/requests.rs | 4 ++-- lambda-runtime/src/runtime.rs | 4 ++-- 15 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 3026f1ac..6894e331 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -4,9 +4,11 @@ on: push: paths: - "lambda-events/**" + - "Cargo.toml" pull_request: paths: - "lambda-events/**" + - "Cargo.toml" jobs: build: @@ -14,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index d09b08c0..cb23c289 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -5,11 +5,15 @@ on: paths: - 'lambda-runtime-api-client/**' - 'lambda-extension/**' + - 'lambda-runtime/**' + - 'Cargo.toml' pull_request: paths: - 'lambda-runtime-api-client/**' - 'lambda-extension/**' + - 'lambda-runtime/**' + - 'Cargo.toml' jobs: @@ -18,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index a52927b5..dfc59ee8 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -6,12 +6,14 @@ on: - 'lambda-runtime-api-client/**' - 'lambda-runtime/**' - 'lambda-http/**' + - 'Cargo.toml' pull_request: paths: - 'lambda-runtime-api-client/**' - 'lambda-runtime/**' - 'lambda-http/**' + - 'Cargo.toml' jobs: build-runtime: @@ -19,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 07bd0a5c..92b0f796 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -10,7 +10,7 @@ use std::fmt; const CODEBUILD_TIME_FORMAT: &str = "%b %e, %Y %l:%M:%S %p"; struct TimeVisitor; -impl<'de> Visitor<'de> for TimeVisitor { +impl Visitor<'_> for TimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 437e2b1c..805c672f 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -73,7 +73,7 @@ where d.deserialize_f64(SecondsFloatTimestampVisitor) } -impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { +impl de::Visitor<'_> for SecondsFloatTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 42a94dd7..1332c777 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -10,7 +10,7 @@ pub fn serialize(method: &Method, ser: S) -> Result Visitor<'de> for MethodVisitor { +impl Visitor<'_> for MethodVisitor { type Value = Method; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 56cce76a..d978f522 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -197,7 +197,7 @@ impl<'de> Deserialize<'de> for Body { { struct BodyVisitor; - impl<'de> Visitor<'de> for BodyVisitor { + impl Visitor<'_> for BodyVisitor { type Value = Body; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 01174566..d99f3c94 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -232,7 +232,7 @@ impl Serialize for CloudWatchAlarmStateReasonData { struct ReasonDataVisitor; -impl<'de> Visitor<'de> for ReasonDataVisitor { +impl Visitor<'_> for ReasonDataVisitor { type Value = CloudWatchAlarmStateReasonData; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index de9b5843..2f4df397 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -4,7 +4,6 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentK #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] - pub struct ChangeInsertEvent { #[serde(rename = "_id")] id: DocumentId, diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 2abcc797..738cd72c 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -87,7 +87,6 @@ pub struct UserRequest { /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] - pub struct UserIdentity { pub r#type: String, pub principal_id: String, diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index d9717243..15e0befd 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -43,7 +43,7 @@ pub struct Extension<'a, E, L, T> { telemetry_port_number: u16, } -impl<'a> Extension<'a, Identity, MakeIdentity>, MakeIdentity>> { +impl Extension<'_, Identity, MakeIdentity>, MakeIdentity>> { /// Create a new base [`Extension`] with a no-op events processor pub fn new() -> Self { Extension { @@ -62,8 +62,8 @@ impl<'a> Extension<'a, Identity, MakeIdentity>, Make } } -impl<'a> Default - for Extension<'a, Identity, MakeIdentity>, MakeIdentity>> +impl Default + for Extension<'_, Identity, MakeIdentity>, MakeIdentity>> { fn default() -> Self { Self::new() diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 233d6992..92cd5dae 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -114,7 +114,7 @@ pub enum TransformResponse<'a, R, E> { Response(RequestOrigin, ResponseFuture), } -impl<'a, R, E> Future for TransformResponse<'a, R, E> +impl Future for TransformResponse<'_, R, E> where R: IntoResponse, { diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index c76348ac..4b92e3c8 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -18,7 +18,7 @@ pub struct CatchPanicService<'a, S> { _phantom: PhantomData<&'a ()>, } -impl<'a, S> CatchPanicService<'a, S> { +impl CatchPanicService<'_, S> { pub fn new(inner: S) -> Self { Self { inner, @@ -66,7 +66,7 @@ pub enum CatchPanicFuture<'a, F> { Error(Box), } -impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> +impl Future for CatchPanicFuture<'_, F> where F: Future>, E: Into + Debug, @@ -95,7 +95,7 @@ where } } -impl<'a, F> CatchPanicFuture<'a, F> { +impl CatchPanicFuture<'_, F> { fn build_panic_diagnostic(err: &Box) -> Diagnostic { let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 729272f2..e8b0183c 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -72,7 +72,7 @@ where } } -impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> +impl IntoRequest for EventCompletionRequest<'_, R, B, S, D, E> where R: IntoFunctionResponse, B: Serialize, @@ -170,7 +170,7 @@ impl<'a> EventErrorRequest<'a> { } } -impl<'a> IntoRequest for EventErrorRequest<'a> { +impl IntoRequest for EventErrorRequest<'_> { fn into_req(self) -> Result, Error> { let uri = format!("/2018-06-01/runtime/invocation/{}/error", self.request_id); let uri = Uri::from_str(&uri)?; diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 1c676480..5749fbb7 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -55,11 +55,11 @@ pub struct Runtime { client: Arc, } -impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +impl Runtime< RuntimeApiClientService< RuntimeApiResponseService< - CatchPanicService<'a, F>, + CatchPanicService<'_, F>, EventPayload, Response, BufferedResponse, From 9416d726ff98cb0bc02ee328c4b1c246d2f36679 Mon Sep 17 00:00:00 2001 From: Alessandro Bologna Date: Thu, 12 Dec 2024 05:28:44 -0500 Subject: [PATCH 346/394] Feature/otel span kind support (#946) * feat(otel): add span kind support to Lambda invocation tracing * fix fmt * docs(example): demonstrate OpenTelemetry span kind usage * fix fmt --- examples/opentelemetry-tracing/src/main.rs | 3 +++ lambda-runtime/src/layers/otel.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 85c3791c..062f5a11 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,5 +1,6 @@ use lambda_runtime::{ layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + tracing::Span, LambdaEvent, Runtime, }; use opentelemetry::trace::TracerProvider; @@ -8,6 +9,8 @@ use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; async fn echo(event: LambdaEvent) -> Result { + let span = Span::current(); + span.record("otel.kind", "SERVER"); Ok(event.payload) } diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index e6b7cfff..f50f36f7 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -4,7 +4,7 @@ use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; -use tracing::{instrument::Instrumented, Instrument}; +use tracing::{field, instrument::Instrumented, Instrument}; /// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts /// a function to flush OpenTelemetry after the end of the invocation. @@ -75,6 +75,7 @@ where let span = tracing::info_span!( "Lambda function invocation", "otel.name" = req.context.env_config.function_name, + "otel.kind" = field::Empty, { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, { traceconv::FAAS_COLDSTART } = self.coldstart From f69280e9b92e9a7acc92cfa04a860d9b2b83b5c0 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 17 Dec 2024 06:00:55 +0800 Subject: [PATCH 347/394] Update MSRV to 1.71.1 (#949) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31ba399b..1a93d85c 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.71.1, and is not guaranteed to build on compiler versions earlier than that. ## Security From 2ee91c0fc0785fc9b7fe8ffc91df129f2c32178a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:48:50 +0100 Subject: [PATCH 348/394] Bump aws-cdk-lib from 2.126.0 to 2.186.0 in /examples/http-axum/cdk (#976) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.126.0 to 2.186.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.186.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.186.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 258 ++++++++++++++--------- examples/http-axum/cdk/package.json | 2 +- 2 files changed, 156 insertions(+), 104 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index 9a7e3026..e572dacf 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "^2.126.0", + "aws-cdk-lib": "^2.186.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -40,19 +40,52 @@ } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.230", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", + "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==", + "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", - "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "40.7.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", + "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 14.15.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/@babel/code-frame": { "version": "7.23.5", @@ -1315,9 +1348,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.126.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", - "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "version": "2.186.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", + "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1328,21 +1361,24 @@ "punycode", "semver", "table", - "yaml" + "yaml", + "mime-types" ], + "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^40.7.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.0", - "jsonschema": "^1.4.1", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.5.4", - "table": "^6.8.1", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "engines": { @@ -1358,14 +1394,14 @@ "license": "Apache-2.0" }, "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.12.0", + "version": "8.17.1", "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -1455,8 +1491,23 @@ "inBundle": true, "license": "MIT" }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -1474,7 +1525,7 @@ "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.2", "inBundle": true, "license": "MIT", "engines": { @@ -1506,7 +1557,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "inBundle": true, "license": "MIT", "engines": { @@ -1518,15 +1569,23 @@ "inBundle": true, "license": "MIT" }, - "node_modules/aws-cdk-lib/node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", "inBundle": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, "node_modules/aws-cdk-lib/node_modules/minimatch": { @@ -1557,12 +1616,9 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.5.4", + "version": "7.7.1", "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -1611,7 +1667,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.1", + "version": "6.9.0", "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -1633,19 +1689,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-cdk-lib/node_modules/uri-js": { - "version": "4.4.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/aws-cdk-lib/node_modules/yaml": { "version": "1.10.2", "inBundle": true, @@ -4408,19 +4451,33 @@ } }, "@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" - }, - "@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.230", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", + "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==" }, "@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", - "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "@aws-cdk/cloud-assembly-schema": { + "version": "40.7.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", + "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "requires": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "dependencies": { + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "semver": { + "version": "7.7.1", + "bundled": true + } + } }, "@babel/code-frame": { "version": "7.23.5", @@ -5423,22 +5480,23 @@ } }, "aws-cdk-lib": { - "version": "2.126.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", - "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "version": "2.186.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", + "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^40.7.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.0", - "jsonschema": "^1.4.1", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.5.4", - "table": "^6.8.1", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "dependencies": { @@ -5447,13 +5505,13 @@ "bundled": true }, "ajv": { - "version": "8.12.0", + "version": "8.17.1", "bundled": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" } }, "ansi-regex": { @@ -5510,8 +5568,12 @@ "version": "3.1.3", "bundled": true }, + "fast-uri": { + "version": "3.0.6", + "bundled": true + }, "fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "bundled": true, "requires": { "graceful-fs": "^4.2.0", @@ -5524,7 +5586,7 @@ "bundled": true }, "ignore": { - "version": "5.3.0", + "version": "5.3.2", "bundled": true }, "is-fullwidth-code-point": { @@ -5544,18 +5606,22 @@ } }, "jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true }, "lodash.truncate": { "version": "4.4.2", "bundled": true }, - "lru-cache": { - "version": "6.0.0", + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", "bundled": true, "requires": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -5574,11 +5640,8 @@ "bundled": true }, "semver": { - "version": "7.5.4", - "bundled": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.7.1", + "bundled": true }, "slice-ansi": { "version": "4.0.0", @@ -5606,7 +5669,7 @@ } }, "table": { - "version": "6.8.1", + "version": "6.9.0", "bundled": true, "requires": { "ajv": "^8.0.1", @@ -5620,17 +5683,6 @@ "version": "2.0.1", "bundled": true }, - "uri-js": { - "version": "4.4.1", - "bundled": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true - }, "yaml": { "version": "1.10.2", "bundled": true diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json index 9117d19f..d9e6f496 100644 --- a/examples/http-axum/cdk/package.json +++ b/examples/http-axum/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.3.3" }, "dependencies": { - "aws-cdk-lib": "^2.126.0", + "aws-cdk-lib": "^2.186.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From af605cc137df335bb97fd0325c19f3ddbefd95ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:00:59 +0100 Subject: [PATCH 349/394] Bump aws-cdk-lib from 2.186.0 to 2.193.0 in /examples/http-axum/cdk (#979) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.186.0 to 2.193.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.193.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.193.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 34 ++++++++++++------------ examples/http-axum/cdk/package.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index e572dacf..a7df2926 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "^2.186.0", + "aws-cdk-lib": "^2.193.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -52,9 +52,9 @@ "license": "Apache-2.0" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "40.7.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", - "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "bundleDependencies": [ "jsonschema", "semver" @@ -1348,9 +1348,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.186.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", - "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1366,9 +1366,9 @@ ], "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^40.7.0", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.0", @@ -4461,9 +4461,9 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "@aws-cdk/cloud-assembly-schema": { - "version": "40.7.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", - "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "requires": { "jsonschema": "~1.4.1", "semver": "^7.7.1" @@ -5480,13 +5480,13 @@ } }, "aws-cdk-lib": { - "version": "2.186.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", - "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^40.7.0", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.0", diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json index d9e6f496..05bcdc49 100644 --- a/examples/http-axum/cdk/package.json +++ b/examples/http-axum/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.3.3" }, "dependencies": { - "aws-cdk-lib": "^2.186.0", + "aws-cdk-lib": "^2.193.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From 26d49f1e6b771cd93ba6d0eb97a3f3300f944c75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:36:47 +0100 Subject: [PATCH 350/394] Bump aws-cdk-lib in /examples/advanced-appconfig-feature-flags/cdk (#978) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.159.1 to 2.193.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.193.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.193.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../cdk/package-lock.json | 140 ++++++++---------- .../cdk/package.json | 2 +- 2 files changed, 66 insertions(+), 76 deletions(-) diff --git a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json index 61c9a537..8f30ffef 100644 --- a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json +++ b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "2.159.1", + "aws-cdk-lib": "2.193.0", "cargo-lambda-cdk": "^0.0.22", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -40,14 +40,10 @@ } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.203", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", - "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.233", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.233.tgz", + "integrity": "sha512-OH5ZN1F/0wwOUwzVUSvE0/syUOi44H9the6IG16anlSptfeQ1fvduJazZAKRuJLtautPbiqxllyOrtWh6LhX8A==", + "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { "version": "2.1.0", @@ -55,19 +51,20 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "36.3.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", - "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "bundleDependencies": [ "jsonschema", "semver" ], + "license": "Apache-2.0", "dependencies": { - "jsonschema": "^1.4.1", - "semver": "^7.6.3" + "jsonschema": "~1.4.1", + "semver": "^7.7.1" }, "engines": { - "node": ">= 18.18.0" + "node": ">= 14.15.0" } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { @@ -79,7 +76,7 @@ } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "inBundle": true, "license": "ISC", "bin": { @@ -1288,9 +1285,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.159.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", - "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1304,21 +1301,21 @@ "yaml", "mime-types" ], + "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", + "fs-extra": "^11.3.0", "ignore": "^5.3.2", - "jsonschema": "^1.4.1", + "jsonschema": "^1.5.0", "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "engines": { @@ -1432,12 +1429,22 @@ "license": "MIT" }, "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.1", + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "inBundle": true, - "license": "MIT" + "license": "BSD-3-Clause" }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -1487,7 +1494,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "inBundle": true, "license": "MIT", "engines": { @@ -1546,7 +1553,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "inBundle": true, "license": "ISC", "bin": { @@ -1597,7 +1604,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.2", + "version": "6.9.0", "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -1619,13 +1626,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-cdk-lib/node_modules/uri-js": { - "version": "4.4.1", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/aws-cdk-lib/node_modules/yaml": { "version": "1.10.2", "inBundle": true, @@ -3835,6 +3835,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -4379,14 +4380,9 @@ } }, "@aws-cdk/asset-awscli-v1": { - "version": "2.2.203", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", - "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" - }, - "@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.233", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.233.tgz", + "integrity": "sha512-OH5ZN1F/0wwOUwzVUSvE0/syUOi44H9the6IG16anlSptfeQ1fvduJazZAKRuJLtautPbiqxllyOrtWh6LhX8A==" }, "@aws-cdk/asset-node-proxy-agent-v6": { "version": "2.1.0", @@ -4394,12 +4390,12 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "@aws-cdk/cloud-assembly-schema": { - "version": "36.3.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", - "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "requires": { - "jsonschema": "^1.4.1", - "semver": "^7.6.3" + "jsonschema": "~1.4.1", + "semver": "^7.7.1" }, "dependencies": { "jsonschema": { @@ -4407,7 +4403,7 @@ "bundled": true }, "semver": { - "version": "7.6.3", + "version": "7.7.1", "bundled": true } } @@ -5358,24 +5354,23 @@ } }, "aws-cdk-lib": { - "version": "2.159.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", - "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", + "fs-extra": "^11.3.0", "ignore": "^5.3.2", - "jsonschema": "^1.4.1", + "jsonschema": "^1.5.0", "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "dependencies": { @@ -5448,11 +5443,11 @@ "bundled": true }, "fast-uri": { - "version": "3.0.1", + "version": "3.0.6", "bundled": true }, "fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "bundled": true, "requires": { "graceful-fs": "^4.2.0", @@ -5485,7 +5480,7 @@ } }, "jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true }, "lodash.truncate": { @@ -5519,7 +5514,7 @@ "bundled": true }, "semver": { - "version": "7.6.3", + "version": "7.7.1", "bundled": true }, "slice-ansi": { @@ -5548,7 +5543,7 @@ } }, "table": { - "version": "6.8.2", + "version": "6.9.0", "bundled": true, "requires": { "ajv": "^8.0.1", @@ -5562,12 +5557,6 @@ "version": "2.0.1", "bundled": true }, - "uri-js": { - "version": "4.4.1", - "requires": { - "punycode": "^2.1.0" - } - }, "yaml": { "version": "1.10.2", "bundled": true @@ -7200,7 +7189,8 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true }, "shebang-command": { "version": "2.0.0", diff --git a/examples/advanced-appconfig-feature-flags/cdk/package.json b/examples/advanced-appconfig-feature-flags/cdk/package.json index 83cb9552..688a31a0 100644 --- a/examples/advanced-appconfig-feature-flags/cdk/package.json +++ b/examples/advanced-appconfig-feature-flags/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.4.5" }, "dependencies": { - "aws-cdk-lib": "2.159.1", + "aws-cdk-lib": "2.193.0", "cargo-lambda-cdk": "^0.0.22", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From 5457a77dd1512fb693059c4ea298b54b4eb0cd7b Mon Sep 17 00:00:00 2001 From: Falk Woldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:42:28 +0200 Subject: [PATCH 351/394] Fix typos (#952) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a93d85c..882c9a79 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ when a function invocation fails, AWS Lambda expects you to return an object tha } ``` -The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the converstion of several general errors types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. +The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the conversion of several general error types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. ### Implement your own Diagnostic From 4c56306eee6bdf4669372ab696d445656715fd15 Mon Sep 17 00:00:00 2001 From: braveocheretovych Date: Wed, 30 Apr 2025 15:48:08 +0300 Subject: [PATCH 352/394] docs: replaced the link to the shield (#968) * Update README.md * Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 882c9a79..71d8e57b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rust Runtime for AWS Lambda -[![Build Status](https://github.com/awslabs/aws-lambda-rust-runtime/workflows/Rust/badge.svg)](https://github.com/awslabs/aws-lambda-rust-runtime/actions) +[![Build Status](https://github.com/awslabs/aws-lambda-rust-runtime/actions/workflows/check-examples.yml/badge.svg)](https://github.com/awslabs/aws-lambda-rust-runtime/actions) This package makes it easy to run AWS Lambda Functions written in Rust. This workspace includes multiple crates: From c511046a3305bccf71fea3fad15d65826e00db23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 01:45:45 +0100 Subject: [PATCH 353/394] Update opentelemetry-semantic-conventions requirement from 0.27 to 0.29 (#970) * Update opentelemetry-semantic-conventions requirement from 0.27 to 0.29 Updates the requirements on [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-rust) to permit the latest version. - [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases) - [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-semantic-conventions-0.27.0...opentelemetry-semantic-conventions-0.29.0) --- updated-dependencies: - dependency-name: opentelemetry-semantic-conventions dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: otel example * bump: MSRV to 1.75.0 * bump: MSRV to 1.81.0 * fix: cargo fmt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maxime David --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/layers/otel.rs | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 6894e331..172bcdd8 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index cb23c289..0f151f43 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index dfc59ee8..8720af17 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index 71d8e57b..40fa7a13 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.71.1, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.81.0, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index b4a7ad3d..5cf67ae2 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -41,7 +41,7 @@ hyper-util = { workspace = true, features = [ ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.27", optional = true, features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index f50f36f7..42b507f8 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, future::Future, pin::Pin, task}; use crate::LambdaInvocation; -use opentelemetry_semantic_conventions::trace as traceconv; +use opentelemetry_semantic_conventions::attribute; use pin_project::pin_project; use tower::{Layer, Service}; use tracing::{field, instrument::Instrumented, Instrument}; @@ -76,9 +76,9 @@ where "Lambda function invocation", "otel.name" = req.context.env_config.function_name, "otel.kind" = field::Empty, - { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, - { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, - { traceconv::FAAS_COLDSTART } = self.coldstart + { attribute::FAAS_TRIGGER } = &self.otel_attribute_trigger, + { attribute::FAAS_INVOCATION_ID } = req.context.request_id, + { attribute::FAAS_COLDSTART } = self.coldstart ); // After the first execution, we can set 'coldstart' to false From c78164e88c153abf4f2aa794c52e772ebda57997 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:08:52 -0700 Subject: [PATCH 354/394] feat(tracing, lambda-runtime): add support for custom writer with default tracing subscriber, add turnkey graceful shutdown helper behind 'graceful-shutdown' feature flag (#982) * feat(tracing): add `init_default_subscriber_with_writer()` * feat(lambda_runtime): add `spawn_graceful_shutdown_handler`, behind `graceful-shutdown` feature flag * chore(runtime-api-client/docs): add docs.rs metadata indicating that tracing module requires tracing feature --- README.md | 46 +++++++++ lambda-integration-tests/src/helloworld.rs | 1 + lambda-runtime-api-client/src/lib.rs | 1 + lambda-runtime-api-client/src/tracing.rs | 35 ++++++- lambda-runtime/Cargo.toml | 8 ++ lambda-runtime/src/lib.rs | 108 +++++++++++++++++++++ 6 files changed, 197 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40fa7a13..981633c9 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,52 @@ async fn handler(_event: LambdaEvent) -> Result<(), Diagnostic> { You can see more examples on how to use these error crates in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-error-crates-integration). +### Graceful shutdown + +`lambda_runtime` offers a helper to simplify configuring graceful shutdown signal handling, `spawn_graceful_shutdown_handler()`. This requires the `graceful-shutdown` feature flag and only supports Unix systems. + +You can use it by passing a `FnOnce` closure that returns an async block. That async block will be executed +when the function receives a `SIGTERM` or `SIGKILL`. + +Note that this helper is opinionated in a number of ways. Most notably: +1. It spawns a task to drive your signal handlers +2. It registers a 'no-op' extension in order to enable graceful shutdown signals +3. It panics on unrecoverable errors + +If you prefer to fine-tune the behavior, refer to the implementation of `spawn_graceful_shutdown_handler()` as a starting point for your own. + +For more information on graceful shutdown handling in AWS Lambda, see: [aws-samples/graceful-shutdown-with-aws-lambda](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda). + +Complete example (cleaning up a non-blocking tracing writer): + +```rust,no_run +use lambda_runtime::{service_fn, LambdaEvent, Error}; +use serde_json::{json, Value}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let func = service_fn(func); + + let (writer, log_guard) = tracing_appender::non_blocking(std::io::stdout()); + lambda_runtime::tracing::init_default_subscriber_with_writer(writer); + + let shutdown_hook = || async move { + std::mem::drop(log_guard); + }; + lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); + + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(event: LambdaEvent) -> Result { + let (event, _context) = event.into_parts(); + let first_name = event["firstName"].as_str().unwrap_or("world"); + + Ok(json!({ "message": format!("Hello, {}!", first_name) })) +} +``` + ## Building and deploying your Lambda functions If you already have Cargo Lambda installed in your machine, run the next command to build your function: diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index 9989da43..92fc7d80 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -8,6 +8,7 @@ use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; async fn main() -> Result<(), Error> { tracing::init_default_subscriber(); let func = service_fn(func); + lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}); lambda_runtime::run(func).await?; Ok(()) } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 00c00e4c..78b51db1 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -22,6 +22,7 @@ pub use error::*; pub mod body; #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub mod tracing; /// API client to interact with the AWS Lambda Runtime API. diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 09f41991..79216cf7 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -14,12 +14,16 @@ pub use tracing::*; /// Re-export the `tracing-subscriber` crate to build your own subscribers. pub use tracing_subscriber as subscriber; +use tracing_subscriber::fmt::MakeWriter; const DEFAULT_LOG_LEVEL: &str = "INFO"; /// Initialize `tracing-subscriber` with default logging options. /// -/// This function uses environment variables set with [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// The default subscriber writes logs to STDOUT in the current context. +/// If you want to customize the writer, see [`init_default_subscriber_with_writer()`]. +/// +/// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. /// /// This subscriber sets the logging level based on environment variables: @@ -31,6 +35,32 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; /// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, /// otherwise they will be formatted with the default tracing format. pub fn init_default_subscriber() { + init_default_subscriber_with_writer(std::io::stdout); +} + +/// Initialize `tracing-subscriber` with default logging options, and a custom writer. +/// +/// You might want to avoid writing to STDOUT in the local context via [`init_default_subscriber()`], if you have a high-throughput Lambdas that involve +/// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). +/// In that case, you might prefer to use [tracing_appender::NonBlocking] instead - particularly if your Lambda is fairly long-running and stays warm. +/// Though, note that you are then responsible +/// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. +/// +/// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// if they're configured for your function. +/// +/// This subscriber sets the logging level based on environment variables: +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes precedence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. +/// - if none of those two variables are set, use `INFO` as the logging level. +/// +/// The logging format can also be changed based on Lambda's advanced logging controls. +/// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, +/// otherwise they will be formatted with the default tracing format. +pub fn init_default_subscriber_with_writer(writer: Writer) +where + Writer: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, +{ let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); let log_level = @@ -43,7 +73,8 @@ pub fn init_default_subscriber() { EnvFilter::builder() .with_default_directive(log_level.into()) .from_env_lossy(), - ); + ) + .with_writer(writer); if log_format.eq_ignore_ascii_case("json") { collector.json().init() diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 5cf67ae2..fb68aa76 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -20,6 +20,10 @@ opentelemetry = ["opentelemetry-semantic-conventions"] # enables access to the O anyhow = ["dep:anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info eyre = ["dep:eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info miette = ["dep:miette"] # enables From for Diagnostic for miette error types, see README.md for more info +# TODO: remove tokio/rt and rt-multi-thread from non-feature-flagged dependencies in new breaking version, since they are unused: +# as well as default features +# https://github.com/awslabs/aws-lambda-rust-runtime/issues/984 +graceful-shutdown = ["tokio/rt", "tokio/signal", "dep:lambda-extension"] [dependencies] anyhow = { version = "1.0.86", optional = true } @@ -39,6 +43,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } +lambda-extension = { version = "0.11.0", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } @@ -67,4 +72,7 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } +# Self dependency to enable the graceful-shutdown feature for tests +lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } +tracing-appender = "0.2" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 76d0562a..e1b15a25 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -32,6 +32,7 @@ pub mod streaming; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub use lambda_runtime_api_client::tracing; /// Types available to a Lambda function. @@ -123,3 +124,110 @@ where let runtime = Runtime::new(handler).layer(layers::TracingLayer::new()); runtime.run().await } + +/// Spawns a task that will be execute a provided async closure when the process +/// receives unix graceful shutdown signals. If the closure takes longer than 500ms +/// to execute, an unhandled `SIGKILL` signal might be received. +/// +/// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. +/// +/// This function must be called prior to [lambda_runtime::run()]. +/// +/// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. +/// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name +/// can not be reused by other registered extensions. This is necessary in order to receive graceful shutdown signals. +/// +/// This extension is cheap to run because it receives no events, but is not zero cost. If you have another extension +/// registered already, you might prefer to manually construct your own graceful shutdown handling without the dummy extension. +/// +/// For more information on general AWS Lambda graceful shutdown handling, see: +/// https://github.com/aws-samples/graceful-shutdown-with-aws-lambda +/// +/// # Panics +/// +/// This function panics if: +/// - this function is called after [lambda_runtime::run()] +/// - this function is called outside of a context that has access to the tokio i/o +/// - the no-op extension cannot be registered +/// - either signal listener panics [tokio::signal::unix](https://docs.rs/tokio/latest/tokio/signal/unix/fn.signal.html#errors) +/// +/// # Example +/// ```no_run +/// use lambda_runtime::{Error, service_fn, LambdaEvent}; +/// use serde_json::Value; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let func = service_fn(func); +/// +/// let (writer, log_guard) = tracing_appender::non_blocking(std::io::stdout()); +/// lambda_runtime::tracing::init_default_subscriber_with_writer(writer); +/// +/// let shutdown_hook = || async move { +/// std::mem::drop(log_guard); +/// }; +/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); +/// +/// lambda_runtime::run(func).await?; +/// Ok(()) +/// } +/// +/// async fn func(event: LambdaEvent) -> Result { +/// Ok(event.payload) +/// } +/// ``` +#[cfg(all(unix, feature = "graceful-shutdown"))] +#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] +pub fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) +where + Fut: Future + Send + 'static, +{ + tokio::task::spawn(async move { + // You need an extension registered with the Lambda orchestrator in order for your process + // to receive a SIGTERM for graceful shutdown. + // + // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. + // + // This extension is cheap to run since after it connects to the lambda orchestration, the connection + // will just wait forever for data to come, which never comes, so it won't cause wakes. + let extension = lambda_extension::Extension::new() + // Don't subscribe to any event types + .with_events(&[]) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await + .expect("could not register no-op extension for graceful shutdown"); + + let graceful_shutdown_future = async move { + let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()).unwrap(); + let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap(); + tokio::select! { + _sigint = sigint.recv() => { + eprintln!("[runtime] SIGINT received"); + eprintln!("[runtime] Graceful shutdown in progress ..."); + shutdown_hook().await; + eprintln!("[runtime] Graceful shutdown completed"); + std::process::exit(0); + }, + _sigterm = sigterm.recv()=> { + eprintln!("[runtime] SIGTERM received"); + eprintln!("[runtime] Graceful shutdown in progress ..."); + shutdown_hook().await; + eprintln!("[runtime] Graceful shutdown completed"); + std::process::exit(0); + }, + } + }; + + // TODO: add biased! to always poll the signal handling future first, once supported: + // https://github.com/tokio-rs/tokio/issues/7304 + let _: (_, ()) = tokio::join!(graceful_shutdown_future, async { + // we suppress extension errors because we don't actually mind if it crashes, + // all we need to do is kick off the run so that lambda exits the init phase + let _ = extension.run().await; + }); + }); +} From 86da85806f6feb206224b551c3469d450582ab3e Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:09:09 -0700 Subject: [PATCH 355/394] chore: add msrv in Cargo manifests (#987) --- lambda-events/Cargo.toml | 1 + lambda-extension/Cargo.toml | 1 + lambda-http/Cargo.toml | 1 + lambda-integration-tests/Cargo.toml | 1 + lambda-runtime-api-client/Cargo.toml | 1 + lambda-runtime/Cargo.toml | 1 + 6 files changed, 6 insertions(+) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b8d34055..4e9b4f79 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "aws_lambda_events" version = "0.16.0" +rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 16b6dace..37c5ee2d 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -2,6 +2,7 @@ name = "lambda-extension" version = "0.11.0" edition = "2021" +rust-version = "1.81.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 46198dca..4f5ce78c 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -6,6 +6,7 @@ authors = [ "Harold Sun ", ] edition = "2021" +rust-version = "1.81.0" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index ee44a969..d7c91088 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -3,6 +3,7 @@ name = "aws_lambda_rust_integration_tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" +rust-version = "1.81.0" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 57fc4bca..d027f0cd 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -2,6 +2,7 @@ name = "lambda_runtime_api_client" version = "0.11.1" edition = "2021" +rust-version = "1.81.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index fb68aa76..68aa713a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -7,6 +7,7 @@ authors = [ ] description = "AWS Lambda Runtime" edition = "2021" +rust-version = "1.81.0" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] From a5989949101086ca379644b1ada293d5d171e61f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:23:04 -0700 Subject: [PATCH 356/394] fix(docs): examples/extension-internal-flush: don't refer to a standalone layer arn for internal extension deploy, add testing information (#980) --- examples/extension-internal-flush/README.md | 18 +++++++++++++++++- examples/extension-internal-flush/src/main.rs | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/examples/extension-internal-flush/README.md b/examples/extension-internal-flush/README.md index 553f7a3d..c6408676 100644 --- a/examples/extension-internal-flush/README.md +++ b/examples/extension-internal-flush/README.md @@ -18,9 +18,10 @@ the Lambda service returns the response to the caller immediately. Extensions ma without introducing an observable delay. ## Build & Deploy +Two deploy options for internal extensions: 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the extension with `cargo lambda build --release` +2. Build a function with the internal extension embedded with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` The last command will give you an ARN for the extension layer that you can use in your functions. @@ -28,3 +29,18 @@ The last command will give you an ARN for the extension layer that you can use i ## Build for ARM 64 Build the extension with `cargo lambda build --release --arm64` + + +## Test your Function + +From your local: +``` +cargo lambda watch +# in new terminal window +cargo lambda invoke --data-ascii '{"Records":[{"messageId":"MessageID_1","receiptHandle":"MessageReceiptHandle","body":"{\"a\":\"Test\",\"b\":123}"}]}' +``` + +If you have deployed to AWS using the commands in the 'Build & Deploy' section, you can also invoke your function remotely: +``` +cargo lambda invoke extension-internal-flush --remote --data-ascii '{"Records":[{"messageId":"MessageID_1","receiptHandle":"MessageReceiptHandle","body":"{\"a\":\"Test\",\"b\":123}"}]}' +``` \ No newline at end of file diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index ff1d10da..c728030b 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -101,6 +101,8 @@ async fn main() -> Result<(), Error> { let handler = Arc::new(EventHandler::new(request_done_sender)); + // TODO: add biased! to always poll the handler future first, once supported: + // https://github.com/tokio-rs/tokio/issues/7304 tokio::try_join!( lambda_runtime::run(service_fn(|event| { let handler = handler.clone(); From 56a23bf66345257267a9934c8d2ba1a74efd7c2f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 08:09:10 -0700 Subject: [PATCH 357/394] fix(integ tests): enable graceful-shutdown feature flag; chore(ci/cd): build integration test crate as part of PR validation (#988) * fix(integ tests): enable graceful-shutdown feature flag * chore(ci/cd): build integration test crate as part of PR validation --- .github/workflows/build-integration-test.yml | 37 ++++++++++++++++++++ lambda-integration-tests/Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-integration-test.yml diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml new file mode 100644 index 00000000..3400b052 --- /dev/null +++ b/.github/workflows/build-integration-test.yml @@ -0,0 +1,37 @@ +name: Build integration tests + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + - 'lambda-extension/**' + - 'Cargo.toml' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + - 'lambda-extension/**' + - 'Cargo.toml' + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.81.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build Integration tests + uses: ./.github/actions/rust-build + with: + package: lambda_integration_tests + toolchain: ${{ matrix.toolchain}} diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index d7c91088..555840f3 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["AWS", "Lambda", "API"] readme = "../README.md" [dependencies] -lambda_runtime = { path = "../lambda-runtime" } +lambda_runtime = { path = "../lambda-runtime", features = ["tracing", "graceful-shutdown"] } aws_lambda_events = { path = "../lambda-events" } serde_json = "1.0.121" tokio = { version = "1", features = ["full"] } From 57cd659a77fa827791ed196781133a054c21f029 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 08:43:00 -0700 Subject: [PATCH 358/394] fix(ci/cd): pin cargo-lambda back to 1.8.1 temporarily (#989) --- .github/workflows/run-integration-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index f1e30d09..a492e50d 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,6 +18,8 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 + # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed + tag: v1.8.1 - name: install Zig toolchain uses: korandoru/setup-zig@v1 with: From 661cc2642e3071ce133cdb43327aa44df0781df4 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Fri, 16 May 2025 09:07:17 -0700 Subject: [PATCH 359/394] fix(lambda-runtime,lambda-integration-tests): make spawn_graceful_shutdown_handler() async, await the extension being registered before spawning background extension handler task (#992) --- lambda-integration-tests/src/helloworld.rs | 2 +- lambda-runtime/Cargo.toml | 3 ++ lambda-runtime/src/lib.rs | 42 +++++++++++----------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index 92fc7d80..b40cd0c6 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -8,7 +8,7 @@ use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; async fn main() -> Result<(), Error> { tracing::init_default_subscriber(); let func = service_fn(func); - lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}); + lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}).await; lambda_runtime::run(func).await?; Ok(()) } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 68aa713a..4c4cab2a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -73,6 +73,9 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } +# pin back to pre-1.2.1 to avoid breaking rust MSRV of 1.81: +# https://github.com/hsivonen/idna_adapter/commit/f948802e3a2ae936eec51886eefbd7d536a28791 +idna_adapter = "=1.2.0" # Self dependency to enable the graceful-shutdown feature for tests lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e1b15a25..5598d104 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -131,7 +131,7 @@ where /// /// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. /// -/// This function must be called prior to [lambda_runtime::run()]. +/// This function's returned future must be resolved prior to [lambda_runtime::run()]. /// /// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. /// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name @@ -166,7 +166,7 @@ where /// let shutdown_hook = || async move { /// std::mem::drop(log_guard); /// }; -/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); +/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook).await; /// /// lambda_runtime::run(func).await?; /// Ok(()) @@ -178,29 +178,29 @@ where /// ``` #[cfg(all(unix, feature = "graceful-shutdown"))] #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] -pub fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) +pub async fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) where Fut: Future + Send + 'static, { - tokio::task::spawn(async move { - // You need an extension registered with the Lambda orchestrator in order for your process - // to receive a SIGTERM for graceful shutdown. - // - // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. - // - // This extension is cheap to run since after it connects to the lambda orchestration, the connection - // will just wait forever for data to come, which never comes, so it won't cause wakes. - let extension = lambda_extension::Extension::new() - // Don't subscribe to any event types - .with_events(&[]) - // Internal extension names MUST be unique within a given Lambda function. - .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") - // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init - // phase and begins the Invoke phase. - .register() - .await - .expect("could not register no-op extension for graceful shutdown"); + // You need an extension registered with the Lambda orchestrator in order for your process + // to receive a SIGTERM for graceful shutdown. + // + // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. + // + // This extension is cheap to run since after it connects to the lambda orchestration, the connection + // will just wait forever for data to come, which never comes, so it won't cause wakes. + let extension = lambda_extension::Extension::new() + // Don't subscribe to any event types + .with_events(&[]) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await + .expect("could not register no-op extension for graceful shutdown"); + tokio::task::spawn(async move { let graceful_shutdown_future = async move { let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()).unwrap(); let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap(); From 00fef2c50c6ac722b00a49fe9bc3fa0d85db7894 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sat, 17 May 2025 00:32:29 +0800 Subject: [PATCH 360/394] Release runtime 0.14.0 with graceful shutdown hook (#991) * Release runtime 0.14.0 with graceful shutdown hook lambda-runtime-api-clietn 0.12.0 lambda-runtime 0.14.0 lambda-http 0.15.0 lambda-extesnion 0.12.0 * Update lambda-extension version in lambda-runtime * Fix integration tests --- .github/workflows/build-integration-test.yml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 6 +++--- lambda-integration-tests/Cargo.toml | 2 +- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 3400b052..6f7ad592 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -33,5 +33,5 @@ jobs: - name: Build Integration tests uses: ./.github/actions/rust-build with: - package: lambda_integration_tests + package: lambda-integration-tests toolchain: ${{ matrix.toolchain}} diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 37c5ee2d..e9ff826b 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.11.0" +version = "0.12.0" edition = "2021" rust-version = "1.81.0" authors = [ @@ -26,7 +26,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4f5ce78c..904d26af 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.14.0" +version = "0.15.0" authors = [ "David Calavera ", "Harold Sun ", @@ -39,7 +39,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.13.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -58,7 +58,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 555840f3..f0bdb292 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "aws_lambda_rust_integration_tests" +name = "lambda-integration-tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index d027f0cd..65c5b130 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.11.1" +version = "0.12.0" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 4c4cab2a..7ffca008 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.13.0" +version = "0.14.0" authors = [ "David Calavera ", "Harold Sun ", @@ -44,8 +44,8 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda-extension = { version = "0.11.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 489b2359d8fd7cb7296001fce729f60f7bdbb372 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 27 May 2025 03:47:57 -0700 Subject: [PATCH 361/394] CI/CD fixes (#994) * chore(lambda_runtime): fix clippy by boxing large service future variant that models LambdaInvocation->LambdaEvent processing failures * fix(ci/cd): build but do not execute tests in `build integration tests` step --- .github/actions/rust-build/action.yml | 5 +++++ .github/workflows/build-integration-test.yml | 3 +++ lambda-runtime/src/layers/api_response.rs | 16 ++++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/actions/rust-build/action.yml b/.github/actions/rust-build/action.yml index 85b5c0e8..6c393d1c 100644 --- a/.github/actions/rust-build/action.yml +++ b/.github/actions/rust-build/action.yml @@ -7,6 +7,10 @@ inputs: toolchain: required: true description: "the Rust toolchain to use" + run-tests: + required: true + default: true + description: "whether to run tests in addition to building" runs: using: "composite" @@ -22,5 +26,6 @@ runs: run: cargo build --all-features --verbose --package ${{ inputs.package }} - name: Run tests + if: ${{ inputs.run-tests == 'true' }} shell: bash run: cargo test --all-features --verbose --package ${{ inputs.package }} diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 6f7ad592..c0d43e25 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -35,3 +35,6 @@ jobs: with: package: lambda-integration-tests toolchain: ${{ matrix.toolchain}} + # the tests will generally fail in ci since they make a network call to a real endpoint, + # this step is just designed to make sure they build successfully + run-tests: false diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index e744cde1..453f8b4c 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -85,7 +85,7 @@ where #[cfg(debug_assertions)] if req.parts.status.is_server_error() { error!("Lambda Runtime server returned an unexpected error"); - return RuntimeApiResponseFuture::Ready(Some(Err(req.parts.status.to_string().into()))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(req.parts.status.to_string().into())))); } // Utility closure to propagate potential error from conditionally executed trace @@ -98,22 +98,23 @@ where }; if let Err(err) = trace_fn() { error!(error = ?err, "Failed to parse raw JSON event received from Lambda. The handler will not be called. Log at TRACE level to see the payload."); - return RuntimeApiResponseFuture::Ready(Some(Err(err))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(err)))); }; let request_id = req.context.request_id.clone(); let lambda_event = match deserializer::deserialize::(&req.body, req.context) { Ok(lambda_event) => lambda_event, Err(err) => match build_event_error_request(&request_id, err) { - Ok(request) => return RuntimeApiResponseFuture::Ready(Some(Ok(request))), + Ok(request) => return RuntimeApiResponseFuture::Ready(Box::new(Some(Ok(request)))), Err(err) => { error!(error = ?err, "failed to build error response for Lambda Runtime API"); - return RuntimeApiResponseFuture::Ready(Some(Err(err))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(err)))); } }, }; - // Once the handler input has been generated successfully, the + // Once the handler input has been generated successfully, pass it through to inner services + // allowing processing both before reaching the handler function and after the handler completes. let fut = self.inner.call(lambda_event); RuntimeApiResponseFuture::Future(fut, request_id, PhantomData) } @@ -141,7 +142,10 @@ pub enum RuntimeApiResponseFuture, ), - Ready(Option, BoxError>>), + /// This variant is used in case the invocation fails to be processed into an event. + /// We box it to avoid bloating the size of the more likely variant, which is + /// the future that drives event processing. + Ready(Box, BoxError>>>), } impl Future From 5d3e91624bafb7e5c035422eafefc51b9f4855c6 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 27 May 2025 19:54:56 -0700 Subject: [PATCH 362/394] feat(ci): build and check rustdocs for warnings; fix(docs): add missing cfg flag to allow displaying features on doc.rs, fix various warnings (#998) * feat(ci): check rustdocs on build * fix(docs): enable `#![cfg_attr(docsrs, feature(doc_cfg))]` to allow rustdoc feature display * fix(docs): fix assorted rustdoc warnings --- .github/workflows/check-docs.yml | 39 +++++++++++++++++++ .../src/main.rs | 2 +- examples/basic-error-handling/src/main.rs | 2 +- examples/http-basic-lambda/src/main.rs | 2 +- examples/http-dynamodb/src/main.rs | 2 +- examples/http-query-parameters/src/main.rs | 2 +- .../src/custom_serde/float_unix_epoch.rs | 1 - lambda-events/src/custom_serde/headers.rs | 2 +- lambda-events/src/event/appsync/mod.rs | 2 +- .../src/event/cloudformation/provider.rs | 2 +- .../src/event/cloudwatch_alarms/mod.rs | 2 +- .../src/event/cloudwatch_events/mod.rs | 2 +- lambda-events/src/event/codebuild/mod.rs | 2 +- lambda-events/src/event/codedeploy/mod.rs | 2 +- .../src/event/codepipeline_cloudwatch/mod.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 8 ++-- lambda-events/src/event/eventbridge/mod.rs | 2 +- lambda-events/src/event/iot/mod.rs | 4 +- lambda-events/src/event/kinesis/event.rs | 2 +- lambda-events/src/event/s3/object_lambda.rs | 2 +- lambda-events/src/event/sqs/mod.rs | 2 +- lambda-events/src/lib.rs | 1 + lambda-events/src/time_window.rs | 12 +++--- lambda-extension/src/lib.rs | 1 + lambda-extension/src/logs.rs | 2 +- lambda-http/src/lib.rs | 3 +- lambda-http/src/response.rs | 2 +- lambda-runtime-api-client/src/body/channel.rs | 4 +- lambda-runtime-api-client/src/body/mod.rs | 2 +- lambda-runtime-api-client/src/body/sender.rs | 2 +- lambda-runtime-api-client/src/body/watch.rs | 2 +- lambda-runtime-api-client/src/error.rs | 2 +- lambda-runtime-api-client/src/lib.rs | 1 + lambda-runtime-api-client/src/tracing.rs | 2 +- lambda-runtime/src/layers/otel.rs | 2 +- lambda-runtime/src/lib.rs | 7 ++-- 36 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/check-docs.yml diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml new file mode 100644 index 00000000..4e26c31c --- /dev/null +++ b/.github/workflows/check-docs.yml @@ -0,0 +1,39 @@ +name: Check rustdocs +# this is its own workflow since we to to use unstable +# to have the docs.rs display of feature flags + +on: + push: + paths: + - 'lambda-runtime/**' + - 'lambda-runtime-api-client/**' + - 'lambda-http/**' + - 'lambda-events/**' + - 'lambda-extension/**' + - 'Cargo.toml' + + pull_request: + paths: + - 'lambda-runtime/**' + - 'lambda-runtime-api-client/**' + - 'lambda-http/**' + - 'lambda-events/**' + - 'lambda-extension/**' + - 'Cargo.toml' + +jobs: + build-runtime: + runs-on: ubuntu-latest + + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + + - name: Check documentation + shell: bash + env: + RUSTFLAGS: --cfg docsrs + RUSTDOCFLAGS: --cfg docsrs -Dwarnings + run: cargo doc --no-deps --document-private-items --all-features diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 42bb2253..be4eb66f 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -44,7 +44,7 @@ async fn main() -> Result<(), Error> { /// Important note: your lambda sqs trigger *needs* to be configured with partial batch response support /// with the ` ReportBatchItemFailures` flag set to true, otherwise failed message will be dropped, /// for more details see: -/// https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting +/// /// /// /// Note that if you are looking for parallel processing (multithread) instead of concurrent processing, diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 3bc76936..12c954a9 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,4 +1,4 @@ -/// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda +/// See for more info on Rust runtime for AWS Lambda use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::json; diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index d0e41561..9db6b275 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -3,7 +3,7 @@ use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; /// This is the main body for the function. /// Write your code inside it. /// There are some code examples in the Runtime repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn function_handler(_event: Request) -> Result, Error> { // Extract some useful information from the request diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index e5cbb2a3..2bf3fb9d 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -15,7 +15,7 @@ pub struct Item { /// This is the main body for the function. /// Write your code inside it. /// You can see more examples in Runtime's repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn handle_request(db_client: &Client, event: Request) -> Result, Error> { // Extract some useful information from the request let body = event.body(); diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 1f7110a5..ef9cf658 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -3,7 +3,7 @@ use lambda_http::{run, service_fn, tracing, Error, IntoResponse, Request, Reques /// This is the main body for the function. /// Write your code inside it. /// You can see more examples in Runtime's repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn function_handler(event: Request) -> Result { // Extract some useful information from the request Ok( diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 805c672f..f4907a1f 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -57,7 +57,6 @@ where struct SecondsFloatTimestampVisitor; /// Serialize a UTC datetime into an float number of seconds since the epoch -/// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 44884649..ea52c88e 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -5,7 +5,7 @@ use serde::{ }; use std::{borrow::Cow, fmt}; -/// Serialize a http::HeaderMap into a serde str => Vec map +/// Serialize a http::HeaderMap into a serde str => `Vec` map pub(crate) fn serialize_multi_value_headers(headers: &HeaderMap, serializer: S) -> Result where S: Serializer, diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 63f9ac74..32bf9f79 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -/// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use map[string]string, json.RawMessage, interface{}, etc.. +/// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index a1594eb4..df5ba80a 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -2,7 +2,7 @@ //! //! Note that they are similar (but not the same) as the events in the `super` module. //! -//! See https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html for details. +//! See for details. use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index d99f3c94..30df5a42 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -11,7 +11,7 @@ use serde_json::Value; /// `CloudWatchAlarm` is the generic outer structure of an event triggered by a CloudWatch Alarm. /// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. /// For examples of events that come via CloudWatch Alarms, -/// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#Lambda-action-payload +/// see #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarm diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 6384406e..2af343de 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -20,7 +20,7 @@ pub mod tag; pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. -/// For examples of events that come via CloudWatch Events, see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html +/// For examples of events that come via CloudWatch Events, see #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index d4970f5a..ad7775d2 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -11,7 +11,7 @@ pub type CodeBuildPhaseStatus = String; pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: -/// https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref +/// #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index d51bf8aa..b0a25c5b 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: -/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#acd_event_types +/// #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 22db26b1..6e394c17 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -8,7 +8,7 @@ pub type CodePipelineState = String; pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: -/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#codepipeline_event_type +/// #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2a3d7558..2b43f4a9 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -110,7 +110,7 @@ impl fmt::Display for KeyType { } /// The `Event` stream event handled to Lambda -/// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update +/// #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] @@ -118,7 +118,7 @@ pub struct Event { } /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows -/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// ref. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { @@ -215,8 +215,8 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX - /// epoch time (http://www.epochconverter.com/) format. Might not be present in - /// the record: https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 + /// epoch time () format. Might not be present in + /// the record: #[serde(rename = "ApproximateCreationDateTime")] #[serde(with = "float_unix_epoch")] #[serde(default)] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 7756e0e4..5ed14840 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -5,7 +5,7 @@ use serde_json::Value; /// Parse EventBridge events. /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// -/// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. +/// See for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 3835b515..07352120 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -3,7 +3,7 @@ use http::HeaderMap; use serde::{Deserialize, Serialize}; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. -/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +/// See #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { @@ -58,7 +58,7 @@ pub struct IoTCoreConnectionMetadata { } /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. -/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +/// See #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index fac80e07..5557ea6b 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -12,7 +12,7 @@ pub struct KinesisEvent { } /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows -/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +/// ref. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 738cd72c..1cd7b934 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_headers, serialize_headers}; /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas -/// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html +/// See: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3ObjectLambdaEvent

diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 9dd69f66..563dda1a 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -126,7 +126,7 @@ pub struct SqsApiEvent { pub messages: Vec, } -/// Alternative to SqsApiEvent to be used alongside SqsApiMessageObj when you need to +/// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to /// deserialize a nested object into a struct of type T within the SQS Message rather /// than just using the raw SQS Message string #[serde_with::serde_as] diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index e21cdc13..8165a7c2 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,4 +1,5 @@ #![deny(rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "http")] pub use http; #[cfg(feature = "query_map")] diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index edc9beb5..424050ab 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `Window` is the object that captures the time window for the records in the event when using the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Window { @@ -24,8 +24,8 @@ impl Default for Window { } /// `TimeWindowProperties` is the object that captures properties that relate to the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowProperties { @@ -51,8 +51,8 @@ pub struct TimeWindowProperties { } /// `TimeWindowEventResponseProperties` is the object that captures response properties that relate to the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponseProperties { diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 81c16337..a27635b8 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![allow(clippy::multiple_crate_versions, clippy::type_complexity)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! This module includes utilities to create Lambda Runtime Extensions. //! diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index c3b0cda2..541dedc2 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -12,7 +12,7 @@ use tracing::{error, trace}; use crate::{Error, ExtensionError}; /// Payload received from the Lambda Logs API -/// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-msg +/// See: #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct LambdaLog { /// Time when the log was generated diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 92cd5dae..cea99750 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -1,4 +1,5 @@ #![warn(missing_docs, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //#![deny(warnings)] //! Enriches the `lambda` crate with [`http`](https://github.com/hyperium/http) //! 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. @@ -188,7 +189,7 @@ where /// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). /// /// This takes care of transforming the LambdaEvent into a [`Request`] and then -/// converting the result into a [`LambdaResponse`]. +/// converting the result into a `LambdaResponse`. pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> where S: Service, diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e8528fdf..fa8953f2 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -153,7 +153,7 @@ impl LambdaResponse { /// /// Types that implement this trait can be used as return types for handler functions. pub trait IntoResponse { - /// Transform into a Response Future + /// Transform into a `Response` Future fn into_response(self) -> ResponseFuture; } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index 27574655..f1e094c3 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! use std::{ pin::Pin, @@ -31,7 +31,7 @@ impl DecodedLength { } } - /// Converts to an Option representing a Known or Unknown length. + /// Converts to an `Option` representing a Known or Unknown length. pub(crate) fn into_opt(self) -> Option { match self { DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None, diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs index 46735682..13bfcaa0 100644 --- a/lambda-runtime-api-client/src/body/mod.rs +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -1,5 +1,5 @@ //! HTTP body utilities. Extracted from Axum under MIT license. -//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +//! use crate::{BoxError, Error}; use bytes::Bytes; diff --git a/lambda-runtime-api-client/src/body/sender.rs b/lambda-runtime-api-client/src/body/sender.rs index 0e008454..14c1d918 100644 --- a/lambda-runtime-api-client/src/body/sender.rs +++ b/lambda-runtime-api-client/src/body/sender.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! use crate::Error; use std::task::{Context, Poll}; diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs index a5f8ae41..f31f4f27 100644 --- a/lambda-runtime-api-client/src/body/watch.rs +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! //! An SPSC broadcast channel. //! diff --git a/lambda-runtime-api-client/src/error.rs b/lambda-runtime-api-client/src/error.rs index dbb87b64..d8ff30b2 100644 --- a/lambda-runtime-api-client/src/error.rs +++ b/lambda-runtime-api-client/src/error.rs @@ -1,5 +1,5 @@ //! Extracted from Axum under MIT license. -//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +//! use std::{error::Error as StdError, fmt}; pub use tower::BoxError; /// Errors that can happen when using axum. diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 78b51db1..3df616ab 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] #![allow(clippy::multiple_crate_versions)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 79216cf7..097e8dcf 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -42,7 +42,7 @@ pub fn init_default_subscriber() { /// /// You might want to avoid writing to STDOUT in the local context via [`init_default_subscriber()`], if you have a high-throughput Lambdas that involve /// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). -/// In that case, you might prefer to use [tracing_appender::NonBlocking] instead - particularly if your Lambda is fairly long-running and stays warm. +/// In that case, you might prefer to use [tracing_appender::NonBlocking](https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.NonBlocking.html) instead - particularly if your Lambda is fairly long-running and stays warm. /// Though, note that you are then responsible /// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. /// diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index 42b507f8..5e96dfed 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -131,7 +131,7 @@ where } /// Represent the possible values for the OpenTelemetry `faas.trigger` attribute. -/// See https://opentelemetry.io/docs/specs/semconv/attributes-registry/faas/ for more details. +/// See for more details. #[derive(Default, Clone, Copy)] #[non_exhaustive] pub enum OpenTelemetryFaasTrigger { diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 5598d104..fe7f2aa6 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![allow(clippy::multiple_crate_versions)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! The mechanism available for defining a Lambda function is as follows: //! @@ -131,7 +132,7 @@ where /// /// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. /// -/// This function's returned future must be resolved prior to [lambda_runtime::run()]. +/// This function's returned future must be resolved prior to `lambda_runtime::run()`. /// /// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. /// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name @@ -141,12 +142,12 @@ where /// registered already, you might prefer to manually construct your own graceful shutdown handling without the dummy extension. /// /// For more information on general AWS Lambda graceful shutdown handling, see: -/// https://github.com/aws-samples/graceful-shutdown-with-aws-lambda +/// /// /// # Panics /// /// This function panics if: -/// - this function is called after [lambda_runtime::run()] +/// - this function is called after `lambda_runtime::run()` /// - this function is called outside of a context that has access to the tokio i/o /// - the no-op extension cannot be registered /// - either signal listener panics [tokio::signal::unix](https://docs.rs/tokio/latest/tokio/signal/unix/fn.signal.html#errors) From 2862581a5be7677afbea9d806ce5f6f05c6d97ce Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 28 May 2025 11:24:16 +0800 Subject: [PATCH 363/394] Bump version for runtime and runtime-api-client (#999) lambda_runtime 0.14.1 lambda_runtime_api_client 0.12.1 --- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 65c5b130..123dcc17 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.0" +version = "0.12.1" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 7ffca008..ce1d6876 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.0" +version = "0.14.1" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client", default-features = false } +lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 1a7c6969732f31cbbafe8fd1dec7a96a993bdb24 Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Thu, 29 May 2025 07:33:37 +0800 Subject: [PATCH 364/394] feat: derive Deserialize, Clone, PartialEq, Eq for MetadataPrelude (#956) --- lambda-runtime/src/types.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index ee09978f..5e5f487a 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -176,7 +176,7 @@ impl LambdaEvent { } /// Metadata prelude for a stream response. -#[derive(Debug, Default, Serialize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MetadataPrelude { #[serde(with = "http_serde::status_code")] @@ -478,4 +478,22 @@ mod test { let _ = invoke_request_id(&headers); } + + #[test] + fn serde_metadata_prelude() { + let metadata_prelude = MetadataPrelude { + status_code: StatusCode::OK, + headers: { + let mut headers = HeaderMap::new(); + headers.insert("key", "val".parse().unwrap()); + headers + }, + cookies: vec!["cookie".to_string()], + }; + + let serialized = serde_json::to_string(&metadata_prelude).unwrap(); + let deserialized: MetadataPrelude = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(metadata_prelude, deserialized); + } } From 24125e2b2220af017a8ba8886b7d15af2f392e13 Mon Sep 17 00:00:00 2001 From: Raul Escobar <74686538+raulescobar-g@users.noreply.github.com> Date: Thu, 29 May 2025 00:11:42 -0500 Subject: [PATCH 365/394] Add type hint to LambdaRequest's Deserialize impl to avoid compiler recursive loop (#960) --- lambda-http/src/deserializer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 4c0ad519..4a09ff9a 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -20,7 +20,7 @@ impl<'de> Deserialize<'de> for LambdaRequest { where D: serde::Deserializer<'de>, { - let raw_value: Box = Box::deserialize(deserializer)?; + let raw_value: Box = Box::::deserialize(deserializer)?; let data = raw_value.get(); #[cfg(feature = "apigw_rest")] From 34b9c04fd6af13000f213a7b81cfd8b7c1c3d414 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 29 May 2025 20:28:57 -0700 Subject: [PATCH 366/394] fix(docs): enable all features in docs.rs build (#1000) * fix(docs): enable all features in docs.rs build * fix(docs): await the graceful shutdown future in the README example * fix(docs): add missing docs.rs feature tags in various places --- README.md | 2 +- lambda-events/Cargo.toml | 3 ++ lambda-events/src/custom_serde/mod.rs | 1 + lambda-events/src/encodings/mod.rs | 2 ++ lambda-events/src/event/mod.rs | 41 ++++++++++++++++++++++++ lambda-events/src/lib.rs | 45 +++++++++++++++++++++++++++ lambda-extension/Cargo.toml | 3 ++ lambda-http/Cargo.toml | 3 ++ lambda-runtime-api-client/Cargo.toml | 3 ++ lambda-runtime/Cargo.toml | 3 ++ lambda-runtime/src/diagnostic.rs | 3 ++ lambda-runtime/src/layers/mod.rs | 1 + lambda-runtime/src/lib.rs | 2 +- 13 files changed, 110 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 981633c9..89f40f92 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ async fn main() -> Result<(), Error> { let shutdown_hook = || async move { std::mem::drop(log_guard); }; - lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); + lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook).await; lambda_runtime::run(func).await?; Ok(()) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 4e9b4f79..b68b94e8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -124,3 +124,6 @@ sqs = ["serde_with"] streams = [] documentdb = [] eventbridge = ["chrono", "serde_with"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 729dee3d..aca3cd6c 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; #[cfg(feature = "codebuild")] pub(crate) mod codebuild_time; #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub type CodeBuildNumber = f32; #[cfg(any( diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index 23399664..f7520c30 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -6,10 +6,12 @@ mod time; use crate::custom_serde::{deserialize_base64, serialize_base64}; #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] pub use self::time::*; #[cfg(feature = "http")] mod http; #[cfg(feature = "http")] +#[cfg_attr(docsrs, doc(cfg(feature = "http")))] pub use self::http::*; pub type Error = Box; diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index d63acc4d..275450fd 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -1,162 +1,203 @@ /// AWS Lambda event definitions for activemq. #[cfg(feature = "activemq")] +#[cfg_attr(docsrs, doc(cfg(feature = "activemq")))] pub mod activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] +#[cfg_attr(docsrs, doc(cfg(feature = "alb")))] pub mod alb; /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] +#[cfg_attr(docsrs, doc(cfg(feature = "apigw")))] pub mod apigw; /// AWS Lambda event definitions for appsync. #[cfg(feature = "appsync")] +#[cfg_attr(docsrs, doc(cfg(feature = "appsync")))] pub mod appsync; /// AWS Lambda event definitions for autoscaling. #[cfg(feature = "autoscaling")] +#[cfg_attr(docsrs, doc(cfg(feature = "autoscaling")))] pub mod autoscaling; /// AWS Lambda event definitions for agent for amazon bedrock #[cfg(feature = "bedrock_agent_runtime")] +#[cfg_attr(docsrs, doc(cfg(feature = "bedrock_agent_runtime")))] pub mod bedrock_agent_runtime; /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] +#[cfg_attr(docsrs, doc(cfg(feature = "chime_bot")))] pub mod chime_bot; /// AWS Lambda event definitions for clientvpn. #[cfg(feature = "clientvpn")] +#[cfg_attr(docsrs, doc(cfg(feature = "clientvpn")))] pub mod clientvpn; /// AWS Lambda event definitions for cloudformation. #[cfg(feature = "cloudformation")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudformation")))] pub mod cloudformation; /// AWS Lambda event definitions for CloudWatch alarms. #[cfg(feature = "cloudwatch_alarms")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_alarms")))] pub mod cloudwatch_alarms; /// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_events")))] pub mod cloudwatch_events; /// AWS Lambda event definitions for cloudwatch_logs. #[cfg(feature = "cloudwatch_logs")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_logs")))] pub mod cloudwatch_logs; /// AWS Lambda event definitions for code_commit. #[cfg(feature = "code_commit")] +#[cfg_attr(docsrs, doc(cfg(feature = "code_commit")))] pub mod code_commit; /// AWS Lambda event definitions for codebuild. #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub mod codebuild; /// AWS Lambda event definitions for codedeploy. #[cfg(feature = "codedeploy")] +#[cfg_attr(docsrs, doc(cfg(feature = "codedeploy")))] pub mod codedeploy; /// AWS Lambda event definitions for codepipeline_cloudwatch. #[cfg(feature = "codepipeline_cloudwatch")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_cloudwatch")))] pub mod codepipeline_cloudwatch; /// AWS Lambda event definitions for codepipeline_job. #[cfg(feature = "codepipeline_job")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_job")))] pub mod codepipeline_job; /// AWS Lambda event definitions for cognito. #[cfg(feature = "cognito")] +#[cfg_attr(docsrs, doc(cfg(feature = "cognito")))] pub mod cognito; /// AWS Lambda event definitions for config. #[cfg(feature = "config")] +#[cfg_attr(docsrs, doc(cfg(feature = "config")))] pub mod config; /// AWS Lambda event definitions for connect. #[cfg(feature = "connect")] +#[cfg_attr(docsrs, doc(cfg(feature = "connect")))] pub mod connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] +#[cfg_attr(docsrs, doc(cfg(feature = "dynamodb")))] pub mod dynamodb; /// AWS Lambda event definitions for ecr_scan. #[cfg(feature = "ecr_scan")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecr_scan")))] pub mod ecr_scan; /// AWS Lambda event definitions for firehose. #[cfg(feature = "firehose")] +#[cfg_attr(docsrs, doc(cfg(feature = "firehose")))] pub mod firehose; /// AWS Lambda event definitions for iam. #[cfg(feature = "iam")] +#[cfg_attr(docsrs, doc(cfg(feature = "iam")))] pub mod iam; /// AWS Lambda event definitions for iot. #[cfg(feature = "iot")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot")))] pub mod iot; /// AWS Lambda event definitions for iot_1_click. #[cfg(feature = "iot_1_click")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_1_click")))] pub mod iot_1_click; /// AWS Lambda event definitions for iot_button. #[cfg(feature = "iot_button")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_button")))] pub mod iot_button; /// AWS Lambda event definitions for iot_deprecated. #[cfg(feature = "iot_deprecated")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_deprecated")))] pub mod iot_deprecated; /// AWS Lambda event definitions for kafka. #[cfg(feature = "kafka")] +#[cfg_attr(docsrs, doc(cfg(feature = "kafka")))] pub mod kafka; /// AWS Lambda event definitions for kinesis. #[cfg(feature = "kinesis")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis")))] pub mod kinesis; /// AWS Lambda event definitions for lambda_function_urls. #[cfg(feature = "lambda_function_urls")] +#[cfg_attr(docsrs, doc(cfg(feature = "lambda_function_urls")))] pub mod lambda_function_urls; /// AWS Lambda event definitions for lex. #[cfg(feature = "lex")] +#[cfg_attr(docsrs, doc(cfg(feature = "lex")))] pub mod lex; /// AWS Lambda event definitions for rabbitmq. #[cfg(feature = "rabbitmq")] +#[cfg_attr(docsrs, doc(cfg(feature = "rabbitmq")))] pub mod rabbitmq; /// AWS Lambda event definitions for s3. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub mod s3; /// AWS Lambda event definitions for secretsmanager. #[cfg(feature = "secretsmanager")] +#[cfg_attr(docsrs, doc(cfg(feature = "secretsmanager")))] pub mod secretsmanager; /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] +#[cfg_attr(docsrs, doc(cfg(feature = "ses")))] pub mod ses; /// AWS Lambda event definitions for SNS. #[cfg(feature = "sns")] +#[cfg_attr(docsrs, doc(cfg(feature = "sns")))] pub mod sns; /// AWS Lambda event definitions for SQS. #[cfg(feature = "sqs")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqs")))] pub mod sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] +#[cfg_attr(docsrs, doc(cfg(feature = "streams")))] pub mod streams; // AWS Lambda event definitions for DocumentDB #[cfg(feature = "documentdb")] +#[cfg_attr(docsrs, doc(cfg(feature = "documentdb")))] pub mod documentdb; /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] +#[cfg_attr(docsrs, doc(cfg(feature = "eventbridge")))] pub mod eventbridge; diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 8165a7c2..d35dbd76 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,14 +1,17 @@ #![deny(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "http")] +#[cfg_attr(docsrs, doc(cfg(feature = "http")))] pub use http; #[cfg(feature = "query_map")] +#[cfg_attr(docsrs, doc(cfg(feature = "query_map")))] pub use query_map; mod custom_serde; /// Encodings used in AWS Lambda json event values. pub mod encodings; #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] pub mod time_window; /// AWS Lambda event definitions. @@ -16,168 +19,210 @@ pub mod event; /// AWS Lambda event definitions for activemq. #[cfg(feature = "activemq")] +#[cfg_attr(docsrs, doc(cfg(feature = "activemq")))] pub use event::activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] +#[cfg_attr(docsrs, doc(cfg(feature = "alb")))] pub use event::alb; /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] +#[cfg_attr(docsrs, doc(cfg(feature = "apigw")))] pub use event::apigw; /// AWS Lambda event definitions for appsync. #[cfg(feature = "appsync")] +#[cfg_attr(docsrs, doc(cfg(feature = "appsync")))] pub use event::appsync; /// AWS Lambda event definitions for autoscaling. #[cfg(feature = "autoscaling")] +#[cfg_attr(docsrs, doc(cfg(feature = "autoscaling")))] pub use event::autoscaling; /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] +#[cfg_attr(docsrs, doc(cfg(feature = "chime_bot")))] pub use event::chime_bot; /// AWS Lambda event definitions for clientvpn. #[cfg(feature = "clientvpn")] +#[cfg_attr(docsrs, doc(cfg(feature = "clientvpn")))] pub use event::clientvpn; /// AWS Lambda event definitions for cloudformation #[cfg(feature = "cloudformation")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudformation")))] pub use event::cloudformation; /// AWS Lambda event definitions for CloudWatch alarms. #[cfg(feature = "cloudwatch_alarms")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_alarms")))] pub use event::cloudwatch_alarms; /// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_events")))] pub use event::cloudwatch_events; /// AWS Lambda event definitions for cloudwatch_logs. #[cfg(feature = "cloudwatch_logs")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_logs")))] pub use event::cloudwatch_logs; /// AWS Lambda event definitions for code_commit. #[cfg(feature = "code_commit")] +#[cfg_attr(docsrs, doc(cfg(feature = "code_commit")))] pub use event::code_commit; /// AWS Lambda event definitions for codebuild. #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub use event::codebuild; /// AWS Lambda event definitions for codedeploy. #[cfg(feature = "codedeploy")] +#[cfg_attr(docsrs, doc(cfg(feature = "codedeploy")))] pub use event::codedeploy; /// AWS Lambda event definitions for codepipeline_cloudwatch. #[cfg(feature = "codepipeline_cloudwatch")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_cloudwatch")))] pub use event::codepipeline_cloudwatch; /// AWS Lambda event definitions for codepipeline_job. #[cfg(feature = "codepipeline_job")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_job")))] pub use event::codepipeline_job; /// AWS Lambda event definitions for cognito. #[cfg(feature = "cognito")] +#[cfg_attr(docsrs, doc(cfg(feature = "cognito")))] pub use event::cognito; /// AWS Lambda event definitions for config. #[cfg(feature = "config")] +#[cfg_attr(docsrs, doc(cfg(feature = "config")))] pub use event::config; /// AWS Lambda event definitions for connect. #[cfg(feature = "connect")] +#[cfg_attr(docsrs, doc(cfg(feature = "connect")))] pub use event::connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] +#[cfg_attr(docsrs, doc(cfg(feature = "dynamodb")))] pub use event::dynamodb; /// AWS Lambda event definitions for ecr_scan. #[cfg(feature = "ecr_scan")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecr_scan")))] pub use event::ecr_scan; /// AWS Lambda event definitions for firehose. #[cfg(feature = "firehose")] +#[cfg_attr(docsrs, doc(cfg(feature = "firehose")))] pub use event::firehose; /// AWS Lambda event definitions for iam. #[cfg(feature = "iam")] +#[cfg_attr(docsrs, doc(cfg(feature = "iam")))] pub use event::iam; /// AWS Lambda event definitions for iot. #[cfg(feature = "iot")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot")))] pub use event::iot; /// AWS Lambda event definitions for iot_1_click. #[cfg(feature = "iot_1_click")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_1_click")))] pub use event::iot_1_click; /// AWS Lambda event definitions for iot_button. #[cfg(feature = "iot_button")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_button")))] pub use event::iot_button; /// AWS Lambda event definitions for iot_deprecated. #[cfg(feature = "iot_deprecated")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_deprecated")))] pub use event::iot_deprecated; /// AWS Lambda event definitions for kafka. #[cfg(feature = "kafka")] +#[cfg_attr(docsrs, doc(cfg(feature = "kafka")))] pub use event::kafka; /// AWS Lambda event definitions for kinesis. #[cfg(feature = "kinesis")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis")))] pub use event::kinesis; /// AWS Lambda event definitions for kinesis_analytics. #[cfg(feature = "kinesis_analytics")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis_analytics")))] pub use event::kinesis::analytics as kinesis_analytics; /// AWS Lambda event definitions for lambda_function_urls. #[cfg(feature = "lambda_function_urls")] +#[cfg_attr(docsrs, doc(cfg(feature = "lambda_function_urls")))] pub use event::lambda_function_urls; /// AWS Lambda event definitions for lex. #[cfg(feature = "lex")] +#[cfg_attr(docsrs, doc(cfg(feature = "lex")))] pub use event::lex; /// AWS Lambda event definitions for rabbitmq. #[cfg(feature = "rabbitmq")] +#[cfg_attr(docsrs, doc(cfg(feature = "rabbitmq")))] pub use event::rabbitmq; /// AWS Lambda event definitions for s3. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub use event::s3; /// AWS Lambda event definitions for s3_batch_job. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub use event::s3::batch_job as s3_batch_job; /// AWS Lambda event definitions for secretsmanager. #[cfg(feature = "secretsmanager")] +#[cfg_attr(docsrs, doc(cfg(feature = "secretsmanager")))] pub use event::secretsmanager; /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] +#[cfg_attr(docsrs, doc(cfg(feature = "ses")))] pub use event::ses; /// AWS Lambda event definitions for SNS. #[cfg(feature = "sns")] +#[cfg_attr(docsrs, doc(cfg(feature = "sns")))] pub use event::sns; /// AWS Lambda event definitions for SQS. #[cfg(feature = "sqs")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqs")))] pub use event::sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] +#[cfg_attr(docsrs, doc(cfg(feature = "streams")))] pub use event::streams; /// AWS Lambda event definitions for documentdb. #[cfg(feature = "documentdb")] +#[cfg_attr(docsrs, doc(cfg(feature = "documentdb")))] pub use event::documentdb; /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] +#[cfg_attr(docsrs, doc(cfg(feature = "eventbridge")))] pub use event::eventbridge; diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index e9ff826b..b9a8e8c7 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -38,3 +38,6 @@ tokio = { version = "1.0", features = [ tokio-stream = "0.1.2" tower = { workspace = true, features = ["make", "util"] } tracing = { version = "0.1", features = ["log"] } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 904d26af..572978ee 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -62,3 +62,6 @@ lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api- log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 123dcc17..d24dac80 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -37,3 +37,6 @@ tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } tracing = { version = "0.1", features = ["log"], optional = true } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index ce1d6876..06e55b4b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -80,3 +80,6 @@ idna_adapter = "=1.2.0" lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } tracing-appender = "0.2" + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index c03ce284..60917e31 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -119,6 +119,7 @@ impl From for Diagnostic { } #[cfg(feature = "anyhow")] +#[cfg_attr(docsrs, doc(cfg(feature = "anyhow")))] impl From for Diagnostic { fn from(value: anyhow::Error) -> Diagnostic { Diagnostic { @@ -129,6 +130,7 @@ impl From for Diagnostic { } #[cfg(feature = "eyre")] +#[cfg_attr(docsrs, doc(cfg(feature = "eyre")))] impl From for Diagnostic { fn from(value: eyre::Report) -> Diagnostic { Diagnostic { @@ -139,6 +141,7 @@ impl From for Diagnostic { } #[cfg(feature = "miette")] +#[cfg_attr(docsrs, doc(cfg(feature = "miette")))] impl From for Diagnostic { fn from(value: miette::Report) -> Diagnostic { Diagnostic { diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 1f07f199..a05b6c67 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -14,4 +14,5 @@ pub use trace::TracingLayer; #[cfg(feature = "opentelemetry")] mod otel; #[cfg(feature = "opentelemetry")] +#[cfg_attr(docsrs, doc(cfg(feature = "opentelemetry")))] pub use otel::{OpenTelemetryFaasTrigger, OpenTelemetryLayer}; diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index fe7f2aa6..e1dd408f 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -178,7 +178,7 @@ where /// } /// ``` #[cfg(all(unix, feature = "graceful-shutdown"))] -#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] +#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "graceful-shutdown"))))] pub async fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) where Fut: Future + Send + 'static, From 2b3bcb7f20b58cd1e85597c2896997db0f20d131 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Jun 2025 14:05:36 +0800 Subject: [PATCH 367/394] Bump all packages versions to release doc updates (#1002) * Bump all packages versions to release doc updates * bump lambda_runtime version --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b68b94e8..481f77ba 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.16.0" +version = "0.16.1" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index b9a8e8c7..4754ffbe 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.12.0" +version = "0.12.1" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 572978ee..38b65a56 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.0" +version = "0.15.1" authors = [ "David Calavera ", "Harold Sun ", @@ -39,7 +39,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.14.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -58,7 +58,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index d24dac80..640560e0 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.1" +version = "0.12.2" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 06e55b4b..6950e1ba 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.1" +version = "0.14.2" authors = [ "David Calavera ", "Harold Sun ", @@ -44,8 +44,8 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 62a36b04ddfd67000d407d06d0da60f394ca4b36 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:16:57 -0700 Subject: [PATCH 368/394] feat(ci): add cargo-semver-checks workflows (#1003) --- .github/workflows/build-events.yml | 20 ++++++++++++++++++-- .github/workflows/build-extension.yml | 23 +++++++++++++++++++---- .github/workflows/build-runtime.yml | 21 ++++++++++++++++++--- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 172bcdd8..372375b5 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -22,7 +22,6 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build events uses: ./.github/actions/rust-build with: @@ -34,6 +33,23 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - name: Test individual event features run: make check-event-features + semver: + name: semver + needs: [build, check-event-features] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `aws_lambda_events` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: aws_lambda_events + feature-group: default-features + - name: Check `aws_lambda_events` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: aws_lambda_events + feature-group: all-features diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 0f151f43..2daaa40d 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -1,4 +1,4 @@ -name: Check Lambda Runtime +name: Check Lambda Extension on: push: @@ -28,16 +28,31 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build Runtime API Client uses: ./.github/actions/rust-build with: package: lambda_runtime_api_client toolchain: ${{ matrix.toolchain}} - - - name: Build Extensions runtime uses: ./.github/actions/rust-build with: package: lambda-extension toolchain: ${{ matrix.toolchain}} + semver: + name: semver + needs: build-runtime + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `lambda-extension` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda-extension + feature-group: default-features + - name: Check `lambda-extension` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda-extension + feature-group: all-features diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 8720af17..c5cf2d08 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -27,21 +27,36 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build Runtime API Client uses: ./.github/actions/rust-build with: package: lambda_runtime_api_client toolchain: ${{ matrix.toolchain}} - - name: Build Functions runtime uses: ./.github/actions/rust-build with: package: lambda_runtime toolchain: ${{ matrix.toolchain}} - - name: Build HTTP layer uses: ./.github/actions/rust-build with: package: lambda_http toolchain: ${{ matrix.toolchain}} + semver: + name: semver + needs: build-runtime + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `lambda_runtime_api_client`, `lambda_runtime`, lambda_http` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda_runtime_api_client, lambda_runtime, lambda_http + feature-group: default-features + - name: Check `lambda_runtime_api_client`, `lambda_runtime`, lambda_http` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda_runtime_api_client, lambda_runtime, lambda_http + feature-group: all-features From 6747fd3c65c6f6285c82af7b611f680cc6fcd83e Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 17:10:37 +0000 Subject: [PATCH 369/394] fix(ci): cut run-integration-test.yml over to "mlugg/setup-zig@v2" --- .github/workflows/run-integration-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index a492e50d..d9a196f6 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -21,9 +21,9 @@ jobs: # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed tag: v1.8.1 - name: install Zig toolchain - uses: korandoru/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - zig-version: 0.10.0 + version: 0.10.0 - name: install SAM uses: aws-actions/setup-sam@v2 with: From a301d72d4bb7120201475239065ae52cb869dfce Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 17:59:33 +0000 Subject: [PATCH 370/394] chore: clippy fixes on inline string formatting --- lambda-events/src/custom_serde/codebuild_time.rs | 4 ++-- lambda-events/src/custom_serde/float_unix_epoch.rs | 7 +++---- lambda-events/src/encodings/time.rs | 8 ++++---- lambda-events/src/event/cloudwatch_alarms/mod.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 10 +++++----- lambda-extension/src/extension.rs | 4 ++-- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 92b0f796..8d90203f 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -14,13 +14,13 @@ impl Visitor<'_> for TimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) + write!(formatter, "valid codebuild time: {CODEBUILD_TIME_FORMAT}") } fn visit_str(self, val: &str) -> Result { NaiveDateTime::parse_from_str(val, CODEBUILD_TIME_FORMAT) .map(|naive| naive.and_utc()) - .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) + .map_err(|e| DeError::custom(format!("Parse error {e} for {val}"))) } } diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index f4907a1f..164b4c7e 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -14,7 +14,7 @@ fn ne_timestamp(ts: T) -> SerdeError { impl fmt::Debug for SerdeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) + write!(f, "ChronoSerdeError({self})") } } @@ -22,7 +22,7 @@ impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SerdeError::NonExistent { ref timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) + write!(f, "value is not a legal timestamp: {timestamp}") } SerdeError::Ambiguous { ref timestamp, @@ -30,8 +30,7 @@ impl fmt::Display for SerdeError { ref max, } => write!( f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max + "value is an ambiguous timestamp: {timestamp}, could be either of {min}, {max}", ), } } diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index d4903360..df22ef24 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -125,7 +125,7 @@ where let whole_seconds = seconds + (milliseconds as i64 / 1000); let subsec_millis = milliseconds % 1000; if milliseconds > 0 { - let combined = format!("{}.{:03}", whole_seconds, subsec_millis); + let combined = format!("{whole_seconds}.{subsec_millis:03}"); serializer.serialize_str(&combined) } else { serializer.serialize_str(&whole_seconds.to_string()) @@ -159,7 +159,7 @@ where { let seconds = f64::deserialize(deserializer)?; TimeDelta::try_seconds(seconds as i64) - .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{}`", seconds))) + .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{seconds}`"))) } fn serialize_duration_minutes(duration: &TimeDelta, serializer: S) -> Result @@ -177,7 +177,7 @@ where { let minutes = f64::deserialize(deserializer)?; TimeDelta::try_minutes(minutes as i64) - .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{}`", minutes))) + .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{minutes}`"))) } fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> @@ -199,7 +199,7 @@ where }; // We need to do this due to floating point issues. - let input_as_string = format!("{}", input); + let input_as_string = input.to_string(); let parts: Result, _> = input_as_string .split('.') .map(|x| x.parse::().map_err(DeError::custom)) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 30df5a42..720236c2 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -224,7 +224,7 @@ impl Serialize for CloudWatchAlarmStateReasonData { Self::Composite(m) => serde_json::to_string(m), Self::Generic(m) => serde_json::to_string(m), }; - let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {}", e)))?; + let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {e}")))?; serializer.serialize_str(&s) } diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2b43f4a9..91380f82 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -27,7 +27,7 @@ impl fmt::Display for StreamViewType { StreamViewType::NewAndOldImages => "NEW_AND_OLD_IMAGES", StreamViewType::KeysOnly => "KEYS_ONLY", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -48,7 +48,7 @@ impl fmt::Display for StreamStatus { StreamStatus::Disabling => "DISABLING", StreamStatus::Disabled => "DISABLED", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -69,7 +69,7 @@ impl fmt::Display for SharedIteratorType { SharedIteratorType::AtSequenceNumber => "AT_SEQUENCE_NUMBER", SharedIteratorType::AfterSequenceNumber => "AFTER_SEQUENCE_NUMBER", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -88,7 +88,7 @@ impl fmt::Display for OperationType { OperationType::Modify => "MODIFY", OperationType::Remove => "REMOVE", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -105,7 +105,7 @@ impl fmt::Display for KeyType { KeyType::Hash => "HASH", KeyType::Range => "RANGE", }; - write!(f, "{}", val) + write!(f, "{val}") } } diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 15e0befd..e7d83847 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -259,7 +259,7 @@ where let io = TokioIo::new(tcp); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { - println!("Error serving connection: {:?}", err); + println!("Error serving connection: {err:?}"); } }); } @@ -305,7 +305,7 @@ where let io = TokioIo::new(tcp); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { - println!("Error serving connection: {:?}", err); + println!("Error serving connection: {err:?}"); } }); } From 2278cea4134c9ca4529c6ef05d8768e9f4caec11 Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 19:26:38 +0000 Subject: [PATCH 371/394] chore(ci): update TODO link for pinned cargo-lambda --- .github/workflows/run-integration-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index d9a196f6..03065d9e 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,7 +18,7 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 - # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed + # TODO: unpin once https://github.com/awslabs/aws-lambda-rust-runtime/issues/1006 is fixed tag: v1.8.1 - name: install Zig toolchain uses: mlugg/setup-zig@v2 From f8b9a2acf472c7c6b23aa13aaded85bef53ae1ea Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Mon, 7 Jul 2025 07:39:07 -0700 Subject: [PATCH 372/394] chore(lambda-runtime): slightly optimize graceful shutdown helper by using new tokio::try_join biased; api (#1007) --- examples/extension-internal-flush/Cargo.toml | 2 +- examples/extension-internal-flush/src/main.rs | 6 ++++-- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 8 +++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml index daadd0eb..70e27ebf 100644 --- a/examples/extension-internal-flush/Cargo.toml +++ b/examples/extension-internal-flush/Cargo.toml @@ -9,4 +9,4 @@ aws_lambda_events = { path = "../../lambda-events" } lambda-extension = { path = "../../lambda-extension" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" -tokio = { version = "1", features = ["macros", "sync"] } +tokio = { version = "1.46", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index c728030b..9a515ff8 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -101,9 +101,11 @@ async fn main() -> Result<(), Error> { let handler = Arc::new(EventHandler::new(request_done_sender)); - // TODO: add biased! to always poll the handler future first, once supported: - // https://github.com/tokio-rs/tokio/issues/7304 tokio::try_join!( + // always poll the handler function first before the flush extension, + // this results in a smaller future due to not needing to track which was polled first + // each time, and also a tiny latency savings + biased; lambda_runtime::run(service_fn(|event| { let handler = handler.clone(); async move { handler.invoke(event).await } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6950e1ba..c4787d56 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -52,7 +52,7 @@ pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" -tokio = { version = "1.0", features = [ +tokio = { version = "1.46", features = [ "macros", "io-util", "sync", diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e1dd408f..cbcd0a9e 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -223,9 +223,11 @@ where } }; - // TODO: add biased! to always poll the signal handling future first, once supported: - // https://github.com/tokio-rs/tokio/issues/7304 - let _: (_, ()) = tokio::join!(graceful_shutdown_future, async { + let _: (_, ()) = tokio::join!( + // we always poll the graceful shutdown future first, + // which results in a smaller future due to lack of bookkeeping of which was last polled + biased; + graceful_shutdown_future, async { // we suppress extension errors because we don't actually mind if it crashes, // all we need to do is kick off the run so that lambda exits the init phase let _ = extension.run().await; From 03833014cef303dab64bf9585c370bd382ce14c6 Mon Sep 17 00:00:00 2001 From: Spencer Stolworthy Date: Thu, 10 Jul 2025 08:08:20 -0700 Subject: [PATCH 373/394] fix(lambda-events): derive Default on KinesisEvent (#1008) --- lambda-events/src/event/kinesis/event.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 5557ea6b..97f4b708 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -4,7 +4,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] @@ -109,4 +109,11 @@ mod test { let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "kinesis")] + fn default_kinesis_event() { + let event = KinesisEvent::default(); + assert_eq!(event.records, vec![]); + } } From b927092162a88267a9ac6efb57f3f2cc624ef117 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Thu, 10 Jul 2025 17:20:27 +0200 Subject: [PATCH 374/394] Update all dependencies and fix examples (#959) * add copy/clone trait impl * update all deps and fix examples * remove unused deps * add feature flag for aws-config * update axum * revert derive * update * remove ';' * use aws_config::load_from_env().await; * remove comments * revert * rev * rev * bump ex * update * update ureq * space * fmt * clippy * update otel * fmt --- .../Cargo.toml | 8 ++--- .../src/main.rs | 4 +-- .../consumer/Cargo.toml | 4 +-- .../pizza_lib/Cargo.toml | 2 +- .../producer/Cargo.toml | 9 +++-- .../producer/src/main.rs | 2 +- .../Cargo.toml | 2 -- .../src/main.rs | 4 +-- .../Cargo.toml | 4 +-- .../Cargo.toml | 2 +- .../src/main.rs | 2 +- examples/basic-error-handling/Cargo.toml | 6 ++-- examples/basic-error-handling/src/main.rs | 8 ++--- examples/basic-error-thiserror/Cargo.toml | 2 +- examples/basic-error-thiserror/src/main.rs | 3 +- .../basic-lambda-external-runtime/Cargo.toml | 11 +++--- .../basic-lambda-external-runtime/src/main.rs | 4 +-- examples/basic-lambda/Cargo.toml | 5 +-- examples/basic-lambda/src/main.rs | 2 +- .../Cargo.toml | 19 +++++----- .../src/main.rs | 27 ++++++-------- .../src/s3.rs | 32 ++++++++++------- examples/basic-s3-thumbnail/Cargo.toml | 14 +++----- examples/basic-s3-thumbnail/src/main.rs | 29 ++++++--------- examples/basic-s3-thumbnail/src/s3.rs | 6 ++-- examples/basic-sdk/Cargo.toml | 9 +++-- examples/basic-sdk/src/main.rs | 7 ++-- examples/basic-shared-resource/Cargo.toml | 2 +- examples/basic-sqs/Cargo.toml | 2 +- examples/basic-streaming-response/src/main.rs | 2 +- examples/extension-basic/Cargo.toml | 1 - examples/extension-combined/Cargo.toml | 1 - examples/extension-custom-events/Cargo.toml | 1 - examples/extension-custom-service/Cargo.toml | 1 - examples/extension-internal-flush/Cargo.toml | 2 +- examples/extension-internal-flush/src/main.rs | 6 ++-- examples/extension-logs-basic/Cargo.toml | 1 - .../extension-logs-custom-service/Cargo.toml | 1 - .../Cargo.toml | 6 ++-- .../src/main.rs | 9 +++-- examples/extension-telemetry-basic/Cargo.toml | 1 - .../http-axum-apigw-authorizer/Cargo.toml | 4 +-- .../http-axum-apigw-authorizer/src/main.rs | 3 -- examples/http-axum-diesel-ssl/Cargo.toml | 22 ++++++------ examples/http-axum-diesel-ssl/src/main.rs | 35 +++++++++---------- examples/http-axum-diesel/Cargo.toml | 10 +++--- examples/http-axum-diesel/src/main.rs | 8 +++-- examples/http-axum-middleware/Cargo.toml | 3 +- examples/http-axum-middleware/src/main.rs | 3 +- examples/http-axum/Cargo.toml | 5 ++- examples/http-axum/src/main.rs | 5 ++- examples/http-basic-lambda/Cargo.toml | 1 - examples/http-cors/Cargo.toml | 3 +- examples/http-cors/src/main.rs | 2 +- examples/http-dynamodb/Cargo.toml | 13 ++++--- examples/http-dynamodb/src/main.rs | 23 ++++-------- examples/http-query-parameters/Cargo.toml | 1 - examples/http-query-parameters/src/main.rs | 2 +- examples/http-raw-path/Cargo.toml | 1 - examples/http-shared-resource/Cargo.toml | 1 - examples/http-tower-trace/Cargo.toml | 3 +- examples/http-tower-trace/src/main.rs | 9 +++-- examples/lambda-rds-iam-auth/Cargo.toml | 14 ++++---- examples/lambda-rds-iam-auth/src/main.rs | 35 +++++++------------ examples/opentelemetry-tracing/Cargo.toml | 10 +++--- examples/opentelemetry-tracing/src/main.rs | 10 +++--- lambda-extension/Cargo.toml | 1 - lambda-http/Cargo.toml | 6 ++-- lambda-integration-tests/Cargo.toml | 1 - lambda-runtime-api-client/Cargo.toml | 2 -- lambda-runtime/Cargo.toml | 8 ----- 71 files changed, 211 insertions(+), 296 deletions(-) diff --git a/examples/advanced-appconfig-feature-flags/Cargo.toml b/examples/advanced-appconfig-feature-flags/Cargo.toml index 52ebb843..51b708ec 100644 --- a/examples/advanced-appconfig-feature-flags/Cargo.toml +++ b/examples/advanced-appconfig-feature-flags/Cargo.toml @@ -15,10 +15,10 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -async-trait = "0.1.68" -lambda_runtime = "0.13" -reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } +async-trait = "0.1.88" +lambda_runtime = { path = "../../lambda-runtime" } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0" +thiserror = "2.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-appconfig-feature-flags/src/main.rs b/examples/advanced-appconfig-feature-flags/src/main.rs index b7d5e515..87ec54fa 100644 --- a/examples/advanced-appconfig-feature-flags/src/main.rs +++ b/examples/advanced-appconfig-feature-flags/src/main.rs @@ -35,9 +35,9 @@ async fn function_handler( // Use the feature flag let msg = if config.spanish_response { - format!("{}, in spanish.", quote) + format!("{quote}, in spanish.") } else { - format!("{}.", quote) + format!("{quote}.") }; // Return `Response` (it will be serialized to JSON automatically by the runtime) diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml index 69ec04a0..e82dc1d3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -5,9 +5,7 @@ edition = "2021" [dependencies] #aws dependencies -aws-sdk-config = "0.35.0" -aws-sdk-sqs = "0.35.0" -aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } +aws_lambda_events = { path = "../../../lambda-events", features = ["sqs"], default-features = false } #lambda runtime lambda_runtime = { path = "../../../lambda-runtime" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml index 76631bbd..2dd69db1 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -serde = { version = "1.0.191", features = ["derive"] } +serde = { version = "1.0.219", features = ["derive"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml index 83aa48ab..2772f650 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -8,14 +8,13 @@ env = { "QUEUE_URL" = "https://changeMe" } [dependencies] #aws dependencies -aws-config = "0.57.1" -aws-sdk-config = "0.35.0" -aws-sdk-sqs = "0.35.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-sqs = "1.74.0" #lambda runtime lambda_runtime = { path = "../../../lambda-runtime" } -serde_json = "1.0.108" +serde_json = "1.0.140" tokio = { version = "1", features = ["macros"] } #shared lib -pizza_lib = { path = "../pizza_lib" } \ No newline at end of file +pizza_lib = { path = "../pizza_lib" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs index 2a70dce3..6a2883f3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -20,7 +20,7 @@ async fn main() -> Result<(), Error> { // read the queue url from the environment let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); // build the config from environment variables (fed by AWS Lambda) - let config = aws_config::from_env().load().await; + let config = aws_config::load_from_env().await; // create our SQS Manager let sqs_manager = SQSManager::new(aws_sdk_sqs::Client::new(&config), queue_url); let sqs_manager_ref = &sqs_manager; diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 95050b9a..f02e4efb 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -5,8 +5,6 @@ edition = "2021" [dependencies] serde = "^1" -serde_derive = "^1" -serde_with = { version = "^2", features = ["json"], optional = true } serde_json = "^1" aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index be4eb66f..6cea2f93 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -55,7 +55,7 @@ where D: DeserializeOwned, R: Future>, { - run(service_fn(|e| batch_handler(|d| f(d), e))).await + run(service_fn(|e| batch_handler(&f, e))).await } /// Helper function to lift the user provided `f` function from message to batch of messages. @@ -123,7 +123,7 @@ mod test { } #[tokio::test] - async fn test() -> () { + async fn test() { let msg_to_fail: SqsMessageObj = serde_json::from_str( r#"{ "messageId": "1", diff --git a/examples/basic-cognito-post-confirmation/Cargo.toml b/examples/basic-cognito-post-confirmation/Cargo.toml index 7d2e7ab4..93369e51 100644 --- a/examples/basic-cognito-post-confirmation/Cargo.toml +++ b/examples/basic-cognito-post-confirmation/Cargo.toml @@ -15,8 +15,8 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws-config = "1.5.0" -aws-sdk-ses = "1.28.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-ses = "1.77.0" aws_lambda_events = { path = "../../lambda-events", default-features = false, features = ["cognito"] } lambda_runtime = { path = "../../lambda-runtime" } diff --git a/examples/basic-error-error-crates-integration/Cargo.toml b/examples/basic-error-error-crates-integration/Cargo.toml index 741ec713..24fbc8dc 100644 --- a/examples/basic-error-error-crates-integration/Cargo.toml +++ b/examples/basic-error-error-crates-integration/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" anyhow = "1" eyre = "0.6.12" lambda_runtime = { path = "../../lambda-runtime", features = ["anyhow", "eyre", "miette"] } -miette = "7.2.0" +miette = "7.6.0" serde = "1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-error-crates-integration/src/main.rs b/examples/basic-error-error-crates-integration/src/main.rs index f4048584..176bd54b 100644 --- a/examples/basic-error-error-crates-integration/src/main.rs +++ b/examples/basic-error-error-crates-integration/src/main.rs @@ -28,7 +28,7 @@ fn miette_error() -> miette::Result<()> { /// Transform an anyhow::Error, eyre::Report, or miette::Report into a lambda_runtime::Diagnostic. /// It does it by enabling the feature `anyhow`, `eyre` or `miette` in the runtime dependency. -/// Those features enable the implementation of `From for Diagnostic` +/// Those features enable the implementation of `From for Diagnostic` /// for `anyhow::Error`, `eyre::Report`, and `miette::Report`. async fn function_handler(event: LambdaEvent) -> Result<(), Diagnostic> { match event.payload.error_type { diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index 1039a139..a0267f97 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" -serde_json = "1.0.81" -simple-error = "0.2.3" +serde = "1.0.219" +serde_json = "1.0.140" +simple-error = "0.3.1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 12c954a9..85e97428 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -43,7 +43,7 @@ impl std::fmt::Display for CustomError { /// Display the error struct as a JSON string fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let err_as_json = json!(self).to_string(); - write!(f, "{}", err_as_json) + write!(f, "{err_as_json}") } } @@ -66,7 +66,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result match event.event_type { EventType::SimpleError => { // generate a simple text message error using `simple_error` crate - return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))); + Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))) } EventType::CustomError => { // generate a custom error using our own structure @@ -75,7 +75,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result req_id: ctx.request_id, msg: "A custom error as requested!".into(), }; - return Err(Box::new(cust_err)); + Err(Box::new(cust_err)) } EventType::ExternalError => { // try to open a non-existent file to get an error and propagate it with `?` @@ -94,7 +94,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result msg: "OK".into(), }; - return Ok(resp); + Ok(resp) } } } diff --git a/examples/basic-error-thiserror/Cargo.toml b/examples/basic-error-thiserror/Cargo.toml index d7c7d725..f2b0b449 100644 --- a/examples/basic-error-thiserror/Cargo.toml +++ b/examples/basic-error-thiserror/Cargo.toml @@ -18,5 +18,5 @@ edition = "2021" lambda_runtime = { path = "../../lambda-runtime" } serde = "1" -thiserror = "1.0.61" +thiserror = "2.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-thiserror/src/main.rs b/examples/basic-error-thiserror/src/main.rs index 403309bf..2c01b833 100644 --- a/examples/basic-error-thiserror/src/main.rs +++ b/examples/basic-error-thiserror/src/main.rs @@ -1,6 +1,5 @@ use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent}; use serde::Deserialize; -use thiserror; #[derive(Deserialize)] struct Request {} @@ -21,7 +20,7 @@ impl From for Diagnostic { }; Diagnostic { error_type: error_type.into(), - error_message: error_message.into(), + error_message, } } } diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index d6d023d8..40f24d81 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -4,11 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -async-channel = "1.8.0" -futures-lite = "1.13.0" +async-channel = "2.5.0" +futures-lite = "2.6.0" lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.163" -tokio = "1.28.2" - -[dev-dependencies] -tokio-test = "0.4.2" +serde = "1.0.219" +tokio = "1.46.1" diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index bd3b4e6c..87891ebc 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -53,7 +53,7 @@ fn main() -> Result<(), io::Error> { my_runtime(move || future::block_on(app_runtime_task(lambda_rx.clone(), shutdown_tx.clone()))); // Block the main thread until a shutdown signal is received. - future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) + future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::other(format!("{err:?}"))) } pub(crate) async fn my_handler(event: LambdaEvent) -> Result { @@ -63,7 +63,7 @@ pub(crate) async fn my_handler(event: LambdaEvent) -> Result) -> Result, _size: u32) -> Vec { mod tests { use super::*; use async_trait::async_trait; - use aws_lambda_events::s3::object_lambda::Configuration; - use aws_lambda_events::s3::object_lambda::HeadObjectContext; - use aws_lambda_events::s3::object_lambda::ListObjectsContext; - use aws_lambda_events::s3::object_lambda::ListObjectsV2Context; - use aws_lambda_events::s3::object_lambda::UserIdentity; - use aws_lambda_events::s3::object_lambda::UserRequest; - use aws_lambda_events::serde_json::json; + use aws_lambda_events::s3::object_lambda::{ + Configuration, HeadObjectContext, ListObjectsContext, ListObjectsV2Context, UserIdentity, UserRequest, + }; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; - use s3::GetFile; - use s3::SendFile; + use s3::{GetFile, SendFile}; + use serde_json::json; #[tokio::test] async fn response_is_good() { mock! { FakeS3Client {} - #[async_trait] impl GetFile for FakeS3Client { - pub fn get_file(&self, url: String) -> Result, Box>; + fn get_file(&self, url: String) -> Result, Box>; } #[async_trait] impl SendFile for FakeS3Client { - pub async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; } } @@ -118,9 +113,7 @@ mod tests { .returning(|_1| Ok("IMAGE".into())); mock.expect_send_file() - .withf(|r, t, by| { - return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes(); - }) + .withf(|r, t, by| r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes()) .returning(|_1, _2, _3| Ok("File sent.".to_string())); let payload = get_s3_event(); @@ -133,7 +126,7 @@ mod tests { } fn get_s3_event() -> S3ObjectLambdaEvent { - return S3ObjectLambdaEvent { + S3ObjectLambdaEvent { x_amz_request_id: ("ID".to_string()), head_object_context: (Some(HeadObjectContext::default())), list_objects_context: (Some(ListObjectsContext::default())), @@ -151,6 +144,6 @@ mod tests { supporting_access_point_arn: ("SAPRN".to_string()), payload: (json!(null)), }), - }; + } } } diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs index daba3739..69b46ec2 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -1,6 +1,8 @@ use async_trait::async_trait; -use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; -use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use aws_sdk_s3::{ + error::SdkError, operation::write_get_object_response::WriteGetObjectResponseError, primitives::ByteStream, + Client as S3Client, +}; use lambda_runtime::tracing; use std::{error, io::Read}; @@ -17,12 +19,17 @@ impl GetFile for S3Client { fn get_file(&self, url: String) -> Result, Box> { tracing::info!("get file url {}", url); - let resp = ureq::get(&url).call()?; - let len: usize = resp.header("Content-Length").unwrap().parse()?; + let mut res = ureq::get(&url).call()?; + let len: usize = res + .headers() + .get("Content-Length") + .and_then(|h| h.to_str().ok()) + .and_then(|s| s.parse().ok()) + .unwrap(); let mut bytes: Vec = Vec::with_capacity(len); - std::io::Read::take(resp.into_reader(), 10_000_000).read_to_end(&mut bytes)?; + std::io::Read::take(res.body_mut().as_reader(), 10_000_000).read_to_end(&mut bytes)?; tracing::info!("got {} bytes", bytes.len()); @@ -46,9 +53,8 @@ impl SendFile for S3Client { .send() .await; - if write.is_err() { - let sdk_error = write.err().unwrap(); - check_error(sdk_error); + if let Err(err) = write { + check_error(err); Err("WriteGetObjectResponse creation error".into()) } else { Ok("File sent.".to_string()) @@ -65,16 +71,16 @@ fn check_error(error: SdkError) { tracing::info!("DispatchFailure"); if err.is_io() { tracing::info!("IO error"); - }; + } if err.is_timeout() { tracing::info!("Timeout error"); - }; + } if err.is_user() { tracing::info!("User error"); - }; - if err.is_other().is_some() { + } + if err.is_other() { tracing::info!("Other error"); - }; + } } SdkError::ResponseError(_err) => tracing::info!("ResponseError"), SdkError::TimeoutError(_err) => tracing::info!("TimeoutError"), diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 4b9ef3da..fdbd79be 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -17,17 +17,13 @@ edition = "2021" [dependencies] aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1" tokio = { version = "1", features = ["macros"] } -aws-config = "0.55" -aws-smithy-http = "0.55.3" -aws-sdk-s3 = "0.28" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.96.0" thumbnailer = "0.5.1" -mime = "0.3.16" -async-trait = "0.1.68" -webp = "=0.2.1" +mime = "0.3.17" +async-trait = "0.1.88" [dev-dependencies] -mockall = "0.11" -tokio-test = "0.4" +mockall = "0.13.1" chrono = "0.4" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index 3eb5bfe9..d09da116 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -139,16 +139,11 @@ mod tests { use super::*; use async_trait::async_trait; //use aws_lambda_events::chrono::DateTime; - use aws_lambda_events::s3::S3Bucket; - use aws_lambda_events::s3::S3Entity; - use aws_lambda_events::s3::S3Object; - use aws_lambda_events::s3::S3RequestParameters; - use aws_lambda_events::s3::S3UserIdentity; + use aws_lambda_events::s3::{S3Bucket, S3Entity, S3Object, S3RequestParameters, S3UserIdentity}; use aws_sdk_s3::operation::get_object::GetObjectError; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; - use s3::GetFile; - use s3::PutFile; + use s3::{GetFile, PutFile}; #[tokio::test] async fn response_is_good() { @@ -163,11 +158,11 @@ mod tests { #[async_trait] impl GetFile for FakeS3Client { - pub async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; + async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; } #[async_trait] impl PutFile for FakeS3Client { - pub async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; + async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; } } @@ -178,23 +173,21 @@ mod tests { .returning(|_1, _2| Ok("IMAGE".into())); mock.expect_put_file() - .withf(|bu, ke, by| { - return bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes()); - }) + .withf(|bu, ke, by| bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes())) .return_const(Ok("Done".to_string())); let payload = get_s3_event("ObjectCreated", bucket, key); let event = LambdaEvent { payload, context }; - let result = function_handler(event, 10, &mock).await.unwrap(); + function_handler(event, 10, &mock).await.unwrap(); - assert_eq!((), result); + assert_eq!((), ()); } fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { - return S3Event { + S3Event { records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), - }; + } } fn get_s3_event_record(event_name: &str, bucket_name: &str, object_key: &str) -> S3EventRecord { @@ -218,7 +211,7 @@ mod tests { }), }; - return S3EventRecord { + S3EventRecord { event_version: (Some(String::default())), event_source: (Some(String::default())), aws_region: (Some(String::default())), @@ -232,6 +225,6 @@ mod tests { }), response_elements: (HashMap::new()), s3: (s3_entity), - }; + } } } diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 0dd8629d..1a759371 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -1,7 +1,5 @@ use async_trait::async_trait; -use aws_sdk_s3::operation::get_object::GetObjectError; -use aws_sdk_s3::Client as S3Client; -use aws_smithy_http::byte_stream::ByteStream; +use aws_sdk_s3::{operation::get_object::GetObjectError, primitives::ByteStream, Client as S3Client}; use lambda_runtime::tracing; #[async_trait] @@ -45,7 +43,7 @@ impl PutFile for S3Client { let result = self.put_object().bucket(bucket).key(key).body(bytes).send().await; match result { - Ok(_) => Ok(format!("Uploaded a file with key {} into {}", key, bucket)), + Ok(_) => Ok(format!("Uploaded a file with key {key} into {bucket}")), Err(err) => Err(err.into_service_error().meta().message().unwrap().to_string()), } } diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 454a970f..6e680aaf 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -7,12 +7,11 @@ edition = "2021" [dependencies] async-trait = "0.1" -aws-config = "0.54" -aws-sdk-s3 = "0.24" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.96.0" lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } [dev-dependencies] -mockall = "0.11.3" -tokio-test = "0.4.2" \ No newline at end of file +mockall = "0.13.1" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index d49c84e1..a021f4c2 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use aws_sdk_s3::{output::ListObjectsV2Output, Client as S3Client}; +use aws_sdk_s3::{operation::list_objects_v2::ListObjectsV2Output, Client as S3Client}; use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; @@ -52,8 +52,7 @@ async fn my_handler(event: LambdaEvent, client: &T) -> let objects_rsp = client.list_objects(&bucket).await?; let objects: Vec<_> = objects_rsp .contents() - .ok_or("missing objects in list-objects-v2 response")? - .into_iter() + .iter() .filter_map(|o| o.key().map(|k| k.to_string())) .collect(); @@ -71,7 +70,7 @@ async fn my_handler(event: LambdaEvent, client: &T) -> #[cfg(test)] mod tests { use super::*; - use aws_sdk_s3::model::Object; + use aws_sdk_s3::types::Object; use lambda_runtime::{Context, LambdaEvent}; use mockall::predicate::eq; diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index 2aad5886..514bef32 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 0df7d8e2..36efee36 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -17,5 +17,5 @@ edition = "2021" [dependencies] aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 8533c8e3..fe112b80 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -7,7 +7,7 @@ use serde_json::Value; use std::{thread, time::Duration}; async fn func(_event: LambdaEvent) -> Result, Error> { - let messages = vec!["Hello", "world", "from", "Lambda!"]; + let messages = ["Hello", "world", "from", "Lambda!"]; let (mut tx, rx) = channel(); diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index 48e2ed51..d7cf1f13 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index 2a745c7b..93aacca1 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index c2f813c3..dfef4c4b 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index b51eae8e..8d0e4575 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml index 70e27ebf..1a0747dd 100644 --- a/examples/extension-internal-flush/Cargo.toml +++ b/examples/extension-internal-flush/Cargo.toml @@ -8,5 +8,5 @@ anyhow = "1" aws_lambda_events = { path = "../../lambda-events" } lambda-extension = { path = "../../lambda-extension" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1.46", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index 9a515ff8..d20213e1 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -2,8 +2,10 @@ use anyhow::anyhow; use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; use lambda_extension::{service_fn, tracing, Error, Extension, NextEvent}; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use tokio::sync::Mutex; +use tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex, +}; use std::sync::Arc; diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index dccc1ec0..230ebc7e 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index 1b1eea0a..421fe9ff 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index c6675e5a..84f7ac96 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -tokio = { version = "1.17.0", features = ["full"] } -aws-config = "0.13.0" -aws-sdk-firehose = "0.13.0" +tokio = { version = "1.46.1", features = ["full"] } +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-firehose = "1.82.0" diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 7871ce52..c9d8a2e4 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -1,4 +1,4 @@ -use aws_sdk_firehose::{model::Record, types::Blob, Client}; +use aws_sdk_firehose::{primitives::Blob, types::Record, Client}; use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{future::Future, pin::Pin, task::Poll}; @@ -31,7 +31,12 @@ impl Service> for FirehoseLogsProcessor { for log in logs { match log.record { LambdaLogRecord::Function(record) => { - records.push(Record::builder().data(Blob::new(record.as_bytes())).build()) + match Record::builder().data(Blob::new(record.as_bytes())).build() { + Ok(rec) => records.push(rec), + Err(e) => { + return Box::pin(async move { Err(e.into()) }); + } + } } _ => unreachable!(), } diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index 1b8b1ba4..a0fb6b87 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml index c757aa94..44c50167 100644 --- a/examples/http-axum-apigw-authorizer/Cargo.toml +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs index 513a6cd8..8adb9024 100644 --- a/examples/http-axum-apigw-authorizer/src/main.rs +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -1,5 +1,4 @@ use axum::{ - async_trait, extract::{FromRequest, Request}, http::StatusCode, response::Json, @@ -13,7 +12,6 @@ use std::{collections::HashMap, env::set_var}; struct AuthorizerField(String); struct AuthorizerFields(HashMap); -#[async_trait] impl FromRequest for AuthorizerField where S: Send + Sync, @@ -30,7 +28,6 @@ where } } -#[async_trait] impl FromRequest for AuthorizerFields where S: Send + Sync, diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index 69366957..d21df0b9 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -4,16 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" -bb8 = "0.8.0" -diesel = "2.0.3" -diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +axum = "0.8" +diesel = "2.2.11" +diesel-async = { version = "0.6.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.159" -futures-util = "0.3.21" -rustls = "0.20.8" -rustls-native-certs = "0.6.2" -tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt-multi-thread"] } -tokio-postgres = "0.7.7" -tokio-postgres-rustls = "0.9.0" \ No newline at end of file +serde = "1.0.219" +futures-util = "0.3.31" +rustls = "0.23.28" +rustls-native-certs = "0.8.1" +tokio = { version = "1.46.1", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio-postgres = "0.7.13" +tokio-postgres-rustls = "0.13.0" diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs index b340b44d..395c1843 100755 --- a/examples/http-axum-diesel-ssl/src/main.rs +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -1,19 +1,18 @@ -use diesel::{ConnectionError, ConnectionResult}; -use futures_util::future::BoxFuture; -use futures_util::FutureExt; -use std::time::Duration; - use axum::{ extract::{Path, State}, response::Json, routing::get, Router, }; -use bb8::Pool; -use diesel::prelude::*; -use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use diesel::{prelude::*, ConnectionError, ConnectionResult}; +use diesel_async::{ + pooled_connection::{bb8::Pool, AsyncDieselConnectionManager, ManagerConfig}, + AsyncPgConnection, RunQueryDsl, +}; +use futures_util::{future::BoxFuture, FutureExt}; use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; +use std::time::Duration; table! { posts (id) { @@ -40,7 +39,7 @@ struct NewPost { published: bool, } -type AsyncPool = Pool>; +type AsyncPool = Pool; type ServerError = (StatusCode, String); async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { @@ -104,7 +103,10 @@ async fn main() -> Result<(), Error> { // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); - let mgr = AsyncDieselConnectionManager::::new_with_setup(db_url, establish_connection); + let mut config = ManagerConfig::default(); + config.custom_setup = Box::new(establish_connection); + + let mgr = AsyncDieselConnectionManager::::new_with_config(db_url, config); let pool = Pool::builder() .max_size(10) @@ -129,19 +131,15 @@ fn establish_connection(config: &str) -> BoxFuture BoxFuture rustls::RootCertStore { let mut roots = rustls::RootCertStore::empty(); let certs = rustls_native_certs::load_native_certs().expect("Certs not loadable!"); - let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect(); - roots.add_parsable_certificates(&certs); + roots.add_parsable_certificates(certs); roots } diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 39fc813e..bed36762 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -4,11 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" -bb8 = "0.8.0" -diesel = "2.0.3" -diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +axum = "0.8" +diesel = "2.2.11" +diesel-async = { version = "0.6.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.159" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index b7247be4..348d7535 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -4,9 +4,11 @@ use axum::{ routing::get, Router, }; -use bb8::Pool; use diesel::prelude::*; -use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use diesel_async::{ + pooled_connection::{bb8::Pool, AsyncDieselConnectionManager}, + AsyncPgConnection, RunQueryDsl, +}; use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; @@ -35,7 +37,7 @@ struct NewPost { published: bool, } -type AsyncPool = Pool>; +type AsyncPool = Pool; type ServerError = (StatusCode, String); async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml index 228fc0ae..f3966941 100644 --- a/examples/http-axum-middleware/Cargo.toml +++ b/examples/http-axum-middleware/Cargo.toml @@ -4,10 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http", default-features = false, features = [ "apigw_rest", "tracing" ] } -lambda_runtime = { path = "../../lambda-runtime" } serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs index b1e92811..e335c270 100644 --- a/examples/http-axum-middleware/src/main.rs +++ b/examples/http-axum-middleware/src/main.rs @@ -10,8 +10,7 @@ //! ``` use axum::{response::Json, routing::post, Router}; -use lambda_http::request::RequestContext::ApiGatewayV1; -use lambda_http::{run, tracing, Error}; +use lambda_http::{request::RequestContext::ApiGatewayV1, run, tracing, Error}; use serde_json::{json, Value}; // Sample middleware that logs the request id diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 7ab3c0ec..7664e7a7 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -4,9 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.196" +serde = "1.0.219" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index dcd5d154..3b00fc57 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,10 +6,9 @@ //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. -use axum::extract::Query; -use axum::http::StatusCode; use axum::{ - extract::Path, + extract::{Path, Query}, + http::StatusCode, response::Json, routing::{get, post}, Router, diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index c7a51507..2f252389 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index b8e51031..b9c9efa5 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -5,6 +5,5 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.5", features = ["cors"] } +tower-http = { version = "0.6", features = ["cors"] } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index ffac0a9e..4c2c54b4 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -28,7 +28,7 @@ async fn func(event: Request) -> Result, Error> { .query_string_parameters_ref() .and_then(|params| params.first("first_name")) { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + Some(first_name) => format!("Hello, {first_name}!").into_response().await, None => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index f2b8db98..d347c346 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -4,11 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -simple-error = "0.3.0" -serde_json = "1.0.107" -serde = { version = "1.0.189", features = ["derive"] } -serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} +serde_json = "1.0.140" +serde = { version = "1.0.219", features = ["derive"] } +serde_dynamo = { version = "4.2.14", features = ["aws-sdk-dynamodb+1"] } lambda_http = { path = "../../lambda-http" } -aws-sdk-dynamodb = "0.33.0" -aws-config = "0.56.1" -tokio = { version = "1.33.0", features = ["macros"] } +aws-sdk-dynamodb = "1.82.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +tokio = { version = "1.46.1", features = ["macros"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 2bf3fb9d..0d37693f 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,15 +1,15 @@ use aws_sdk_dynamodb::Client; use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; use serde::{Deserialize, Serialize}; -use serde_dynamo::to_attribute_value; +use serde_dynamo::to_item; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { - pub p_type: String, + pub account_type: String, pub age: String, pub username: String, - pub first: String, - pub last: String, + pub first_name: String, + pub last_name: String, } /// This is the main body for the function. @@ -71,20 +71,9 @@ async fn main() -> Result<(), Error> { // Add an item to a table. // snippet-start:[dynamodb.rust.add-item] pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Error> { - let user_av = to_attribute_value(item.username)?; - let type_av = to_attribute_value(item.p_type)?; - let age_av = to_attribute_value(item.age)?; - let first_av = to_attribute_value(item.first)?; - let last_av = to_attribute_value(item.last)?; + let item = to_item(item)?; - let request = client - .put_item() - .table_name(table) - .item("username", user_av) - .item("account_type", type_av) - .item("age", age_av) - .item("first_name", first_av) - .item("last_name", last_av); + let request = client.put_item().table_name(table).set_item(Some(item)); tracing::info!("adding item to DynamoDB"); diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 2cadda95..18f8e6cf 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index ef9cf658..c974e633 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -11,7 +11,7 @@ async fn function_handler(event: Request) -> Result { .query_string_parameters_ref() .and_then(|params| params.first("first_name")) { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + Some(first_name) => format!("Hello, {first_name}!").into_response().await, None => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index f6c56526..d1c5ccb8 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 923ceecc..8f5a0e94 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 36389f0e..cf1f223b 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -5,6 +5,5 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.5", features = ["trace"] } +tower-http = { version = "0.6", features = ["trace"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index df2b9007..7dd0c1d6 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -1,6 +1,9 @@ -use lambda_http::tracing::{self, Level}; -use lambda_http::{run, tower::ServiceBuilder, Error}; -use lambda_http::{Request, Response}; +use lambda_http::{ + run, + tower::ServiceBuilder, + tracing::{self, Level}, + Error, Request, Response, +}; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; async fn handler(_req: Request) -> Result, Error> { diff --git a/examples/lambda-rds-iam-auth/Cargo.toml b/examples/lambda-rds-iam-auth/Cargo.toml index a1e212ae..68b0fe92 100644 --- a/examples/lambda-rds-iam-auth/Cargo.toml +++ b/examples/lambda-rds-iam-auth/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde_json = "1.0.120" -aws-config = "1.0.1" -aws-credential-types = "1.0.1" -aws-sigv4 = "1.0.1" -url = "2.5.0" -tokio = { version = "1.25.0", features = ["full"] } -sqlx = { version = "0.7.4", features = ["tls-rustls", "postgres", "runtime-tokio"] } +serde_json = "1.0.140" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-credential-types = "1.2.3" +aws-sigv4 = "1.3.3" +url = "2.5.4" +tokio = { version = "1.46.1", features = ["full"] } +sqlx = { version = "0.8.6", features = ["tls-rustls", "postgres", "runtime-tokio"] } diff --git a/examples/lambda-rds-iam-auth/src/main.rs b/examples/lambda-rds-iam-auth/src/main.rs index 32cf3580..fbf8394f 100644 --- a/examples/lambda-rds-iam-auth/src/main.rs +++ b/examples/lambda-rds-iam-auth/src/main.rs @@ -7,17 +7,15 @@ use aws_sigv4::{ use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use serde_json::{json, Value}; use sqlx::postgres::PgConnectOptions; -use std::env; -use std::time::{Duration, SystemTime}; +use std::{ + env, + time::{Duration, SystemTime}, +}; const RDS_CERTS: &[u8] = include_bytes!("global-bundle.pem"); -async fn generate_rds_iam_token( - db_hostname: &str, - port: u16, - db_username: &str, -) -> Result { - let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await; +async fn generate_rds_iam_token(db_hostname: &str, port: u16, db_username: &str) -> Result { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; let credentials = config .credentials_provider() @@ -40,23 +38,16 @@ async fn generate_rds_iam_token( .settings(signing_settings) .build()?; - let url = format!( - "https://{db_hostname}:{port}/?Action=connect&DBUser={db_user}", - db_hostname = db_hostname, - port = port, - db_user = db_username - ); + let url = format!("https://{db_hostname}:{port}/?Action=connect&DBUser={db_username}"); let signable_request = - SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])) - .expect("signable request"); + SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])).expect("signable request"); - let (signing_instructions, _signature) = - sign(signable_request, &signing_params.into())?.into_parts(); + let (signing_instructions, _signature) = sign(signable_request, &signing_params.into())?.into_parts(); let mut url = url::Url::parse(&url).unwrap(); for (name, value) in signing_instructions.params() { - url.query_pairs_mut().append_pair(name, &value); + url.query_pairs_mut().append_pair(name, value); } let response = url.to_string().split_off("https://".len()); @@ -89,9 +80,7 @@ async fn handler(_event: LambdaEvent) -> Result { .ssl_root_cert_from_pem(RDS_CERTS.to_vec()) .ssl_mode(sqlx::postgres::PgSslMode::Require); - let pool = sqlx::postgres::PgPoolOptions::new() - .connect_with(opts) - .await?; + let pool = sqlx::postgres::PgPoolOptions::new().connect_with(opts).await?; let result: i32 = sqlx::query_scalar("SELECT $1 + $2") .bind(3) @@ -99,7 +88,7 @@ async fn handler(_event: LambdaEvent) -> Result { .fetch_one(&pool) .await?; - println!("Result: {:?}", result); + println!("Result: {result:?}"); Ok(json!({ "statusCode": 200, diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index 98108d39..c9b2a9cb 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -5,14 +5,12 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime", features = ["opentelemetry"] } -opentelemetry-semantic-conventions = "0.14" -opentelemetry = "0.22" -opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"] } -opentelemetry-stdout = { version = "0.3", features = ["trace"] } -pin-project = "1" +opentelemetry = "0.30" +opentelemetry_sdk = { version = "0.30", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.30", features = ["trace"] } serde_json = "1.0" tokio = "1" tower = "0.5" tracing = "0.1" -tracing-opentelemetry = "0.23" +tracing-opentelemetry = "0.31" tracing-subscriber = "0.3" diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 062f5a11..a75aa016 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -4,7 +4,7 @@ use lambda_runtime::{ LambdaEvent, Runtime, }; use opentelemetry::trace::TracerProvider; -use opentelemetry_sdk::{runtime, trace}; +use opentelemetry_sdk::trace; use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; @@ -18,8 +18,8 @@ async fn echo(event: LambdaEvent) -> Result Result<(), BoxError> { // Set up OpenTelemetry tracer provider that writes spans to stdout for debugging purposes let exporter = opentelemetry_stdout::SpanExporter::default(); - let tracer_provider = trace::TracerProvider::builder() - .with_batch_exporter(exporter, runtime::Tokio) + let tracer_provider = trace::SdkTracerProvider::builder() + .with_batch_exporter(exporter) .build(); // Set up link between OpenTelemetry and tracing crate @@ -34,7 +34,9 @@ async fn main() -> Result<(), BoxError> { // Create a tracing span for each Lambda invocation OtelLayer::new(|| { // Make sure that the trace is exported before the Lambda runtime is frozen - tracer_provider.force_flush(); + if let Err(err) = tracer_provider.force_flush() { + eprintln!("Error flushing traces: {err:#?}"); + } }) // Set the "faas.trigger" attribute of the span to "pubsub" .with_trigger(OpenTelemetryFaasTrigger::PubSub), diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 4754ffbe..d178520a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -20,7 +20,6 @@ tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" -bytes = { workspace = true } chrono = { workspace = true, features = ["serde"] } http = { workspace = true } http-body-util = { workspace = true } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 38b65a56..4498e275 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -30,10 +30,8 @@ eyre = ["lambda_runtime/eyre"] # enables From for Diagnostic for eyre error t miette = ["lambda_runtime/miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] -base64 = { workspace = true } bytes = { workspace = true } encoding_rs = "0.8" -futures = { workspace = true } futures-util = { workspace = true } http = { workspace = true } http-body = { workspace = true } @@ -56,8 +54,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] -axum-core = "0.4.3" -axum-extra = { version = "0.9.2", features = ["query"] } +axum-core = "0.5.0" +axum-extra = { version = "0.10.0", features = ["query"] } lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index f0bdb292..0a9836c4 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -20,7 +20,6 @@ serde = { version = "1.0.204", features = ["derive"] } [dev-dependencies] reqwest = { version = "0.12.5", features = ["blocking"] } -openssl = { version = "0.10", features = ["vendored"] } [[bin]] name = "helloworld" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 640560e0..cc2289af 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -33,8 +33,6 @@ hyper-util = { workspace = true, features = [ "tokio", ] } tower = { workspace = true, features = ["util"] } -tower-service = { workspace = true } -tokio = { version = "1.0", features = ["io-util"] } tracing = { version = "0.1", features = ["log"], optional = true } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index c4787d56..6e0dde73 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -34,16 +34,9 @@ bytes = { workspace = true } eyre = { version = "0.6.12", optional = true } futures = { workspace = true } http = { workspace = true } -http-body = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } hyper = { workspace = true, features = ["http1", "client"] } -hyper-util = { workspace = true, features = [ - "client", - "client-legacy", - "http1", - "tokio", -] } lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } @@ -60,7 +53,6 @@ tokio = { version = "1.46", features = [ ] } tokio-stream = "0.1.2" tower = { workspace = true, features = ["util"] } -tower-layer = { workspace = true } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] From fd6f5288f3a508a0302fe8a1ed3008d5df63d443 Mon Sep 17 00:00:00 2001 From: George Lewis <33588728+George-lewis@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:41:54 -0400 Subject: [PATCH 375/394] Capture `String` Panic Messages (#962) --- lambda-runtime/src/layers/panic.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 4b92e3c8..257a8f39 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -99,6 +99,8 @@ impl CatchPanicFuture<'_, F> { fn build_panic_diagnostic(err: &Box) -> Diagnostic { let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") + } else if let Some(msg) = err.downcast_ref::() { + format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; From 35860b04ac0fa6ec4e152299e20d9a94c6d3db7a Mon Sep 17 00:00:00 2001 From: Spencer Stolworthy Date: Thu, 10 Jul 2025 08:57:30 -0700 Subject: [PATCH 376/394] fix(lambda-events): impl Default for all Kinesis Event structs (#1011) --- lambda-events/src/encodings/time.rs | 2 +- lambda-events/src/event/kinesis/event.rs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index df22ef24..d0d9526e 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -29,7 +29,7 @@ impl DerefMut for MillisecondTimestamp { } /// Timestamp with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondTimestamp( #[serde(deserialize_with = "deserialize_seconds")] #[serde(serialize_with = "serialize_seconds")] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 97f4b708..a67da850 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -13,7 +13,7 @@ pub struct KinesisEvent { /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { #[serde(rename = "KinesisEvent")] @@ -34,7 +34,7 @@ pub struct KinesisTimeWindowEventResponse { // pub batch_item_failures: Vec, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventRecord { /// nolint: stylecheck @@ -59,7 +59,7 @@ pub struct KinesisEventRecord { pub kinesis: KinesisRecord, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisRecord { pub approximate_arrival_timestamp: SecondTimestamp, @@ -110,10 +110,16 @@ mod test { assert_eq!(parsed, reparsed); } + /// `cargo lambda init` autogenerates code that relies on `Default` being implemented for event structs. + /// + /// This test validates that `Default` is implemented for each KinesisEvent struct. #[test] #[cfg(feature = "kinesis")] - fn default_kinesis_event() { - let event = KinesisEvent::default(); - assert_eq!(event.records, vec![]); + fn test_ensure_default_implemented_for_structs() { + let _kinesis_event = KinesisEvent::default(); + let _kinesis_time_window_event = KinesisTimeWindowEvent::default(); + let _kinesis_event_record = KinesisEventRecord::default(); + let _kinesis_record = KinesisRecord::default(); + let _kinesis_encryption_type = KinesisEncryptionType::default(); } } From fd0354eac86c84cb0e0792d7a8cd9e2f1d60cd94 Mon Sep 17 00:00:00 2001 From: Bryson M <43580701+Bryson14@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:50:52 -0700 Subject: [PATCH 377/394] Add alternative to pip3 install of cargo lambda (#981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running `pip3 install .....` it shows this error about system vs user installation: ``` rror: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install. If you wish to install a non-Debian-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make sure you have python3-full installed. If you wish to install a non-Debian packaged Python application, it may be easiest to use pipx install xyz, which will manage a virtual environment for you. Make sure you have pipx installed. See /usr/share/doc/python3.12/README.venv for more information. ``` Another way to manage executable pip packages is with uv which has been very popular and helpful in the python community. ``` --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 89f40f92..5426c0c9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,11 @@ Or PiP on any system with Python 3 installed: ```bash pip3 install cargo-lambda ``` +Alternative, install the pip package as an executable using [uv](https://docs.astral.sh/uv/) + +```bash +uv tool install cargo-lambda +``` See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). From ee2e0d295a1c95e27cf474086839c7c2c76fb00d Mon Sep 17 00:00:00 2001 From: Ikuma Yamashita Date: Sat, 12 Jul 2025 06:45:25 +0900 Subject: [PATCH 378/394] Add support for Direct Lambda Resolver event format (#1015) * feat(event): add AppSyncResolverEvent structure and example payload * fix: remove `groups` field from AppSyncCognitoIdentity to avoid semver break * fix: apply serde annotations and generic types to align with existing conventions * refactor: rename AppSyncResolverEvent to AppSyncDirectResolverEvent * fix: update doc comment to reflect AppSyncDirectResolverEvent rename --- lambda-events/src/event/appsync/mod.rs | 116 ++++++++++++++++++ .../example-appsync-direct-resolver.json | 64 ++++++++++ 2 files changed, 180 insertions(+) create mode 100644 lambda-events/src/fixtures/example-appsync-direct-resolver.json diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 32bf9f79..4aa62e5b 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -117,6 +117,112 @@ where pub ttl_override: Option, } +/// `AppSyncDirectResolverEvent` represents the default payload structure sent by AWS AppSync +/// when using **Direct Lambda Resolvers** (i.e., when both request and response mapping +/// templates are disabled). +/// +/// This structure includes the full AppSync **Context object**, as described in the +/// [AppSync Direct Lambda resolver reference](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). +/// +/// It is recommended when working without VTL templates and relying on the standard +/// AppSync-to-Lambda event format. +/// +/// See also: +/// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncDirectResolverEvent +where + TArguments: Serialize + DeserializeOwned, + TSource: Serialize + DeserializeOwned, + TStash: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub arguments: Option, + pub identity: Option, + #[serde(bound = "")] + pub source: Option, + pub request: AppSyncRequest, + pub info: AppSyncInfo, + #[serde(default)] + pub prev: Option, + #[serde(bound = "")] + pub stash: TStash, +} + +/// `AppSyncRequest` contains request-related metadata for a resolver invocation, +/// including client-sent headers and optional custom domain name. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub headers: HashMap>, + #[serde(default)] + pub domain_name: Option, +} + +/// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncInfo +where + T: Serialize + DeserializeOwned, +{ + #[serde(default)] + pub selection_set_list: Vec, + #[serde(rename = "selectionSetGraphQL")] + pub selection_set_graphql: String, + pub parent_type_name: String, + pub field_name: String, + #[serde(bound = "")] + pub variables: T, +} + +/// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncPrevResult +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub result: T, +} + +/// `AppSyncIdentity` represents the identity of the caller as determined by the +/// configured AppSync authorization mechanism (IAM, Cognito, OIDC, or Lambda). +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(untagged, rename_all = "camelCase")] +pub enum AppSyncIdentity { + IAM(AppSyncIamIdentity), + Cognito(AppSyncCognitoIdentity), + OIDC(AppSyncIdentityOIDC), + Lambda(AppSyncIdentityLambda), +} + +/// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncIdentityOIDC +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub claims: T, + pub issuer: String, + pub sub: String, +} + +/// `AppSyncIdentityLambda` represents identity information when using AWS Lambda +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncIdentityLambda +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub resolver_context: T, +} + #[cfg(test)] mod test { use super::*; @@ -160,4 +266,14 @@ mod test { let reparsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_direct_resolver() { + let data = include_bytes!("../../fixtures/example-appsync-direct-resolver.json"); + let parsed: AppSyncDirectResolverEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncDirectResolverEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-appsync-direct-resolver.json b/lambda-events/src/fixtures/example-appsync-direct-resolver.json new file mode 100644 index 00000000..9a804876 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-direct-resolver.json @@ -0,0 +1,64 @@ +{ + "arguments": { + "input": "foo" + }, + "identity": { + "sourceIp": [ + "x.x.x.x" + ], + "userArn": "arn:aws:iam::123456789012:user/appsync", + "accountId": "666666666666", + "user": "AIDAAAAAAAAAAAAAAAAAA" + }, + "info": { + "fieldName": "greet", + "parentTypeName": "Query", + "selectionSetGraphQL": "", + "selectionSetList": [], + "variables": { + "inputVar": "foo" + } + }, + "prev": null, + "request": { + "domainName": null, + "headers": { + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.9,ja;q=0.8,en-GB;q=0.7", + "cloudfront-forwarded-proto": "https", + "cloudfront-is-desktop-viewer": "true", + "cloudfront-is-mobile-viewer": "false", + "cloudfront-is-smarttv-viewer": "false", + "cloudfront-is-tablet-viewer": "false", + "cloudfront-viewer-asn": "17676", + "cloudfront-viewer-country": "JP", + "content-length": "40", + "content-type": "application/json", + "host": "2ojpkjk2ejb57l7stgad5o4qiq.appsync-api.ap-northeast-1.amazonaws.com", + "origin": "https://ap-northeast-1.console.aws.amazon.com", + "priority": "u=1, i", + "referer": "https://ap-northeast-1.console.aws.amazon.com/", + "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "\"Windows\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "cross-site", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0", + "via": "2.0 ee337d4db5c7ebfdc8ec0798a1ede776.cloudfront.net (CloudFront)", + "x-amz-cf-id": "O3ZflUCq6_TzxjouyYB3zg7-kl7Ze-gXbniM2jJ3hAOfDFpPMGRu3Q==", + "x-amz-user-agent": "AWS-Console-AppSync/", + "x-amzn-appsync-is-vpce-request": "false", + "x-amzn-remote-ip": "x.x.x.x", + "x-amzn-requestid": "7ada8740-bbf4-49e8-bf45-f10b3d67159b", + "x-amzn-trace-id": "Root=1-68713e21-7a03739120ad60703e794b22", + "x-api-key": "***", + "x-forwarded-for": "***", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + } + }, + "source": null, + "stash": {} +} \ No newline at end of file From 0cdd565fa0c4886243bdf54ba7b1e3c5bf692721 Mon Sep 17 00:00:00 2001 From: Craig Disselkoen Date: Thu, 17 Jul 2025 09:37:37 -0700 Subject: [PATCH 379/394] add catchall feature to resolve #1017 (#1018) * add catchall feature to resolve #1017 Signed-off-by: Craig Disselkoen * switch from HashMap to serde_json::Map Signed-off-by: Craig Disselkoen * add cfg_attr for docsrs Signed-off-by: Craig Disselkoen * bump lambda_events version to 0.17 Signed-off-by: Craig Disselkoen * tweak comment wording Signed-off-by: Craig Disselkoen --------- Signed-off-by: Craig Disselkoen --- lambda-events/Cargo.toml | 6 +- lambda-events/src/event/activemq/mod.rs | 23 ++ lambda-events/src/event/alb/mod.rs | 30 ++ lambda-events/src/event/apigw/mod.rs | 235 +++++++++++++ lambda-events/src/event/appsync/mod.rs | 84 +++++ lambda-events/src/event/autoscaling/mod.rs | 7 + .../src/event/bedrock_agent_runtime/mod.rs | 44 +++ lambda-events/src/event/chime_bot/mod.rs | 30 ++ lambda-events/src/event/clientvpn/mod.rs | 16 + lambda-events/src/event/cloudformation/mod.rs | 28 ++ .../src/event/cloudformation/provider.rs | 17 + .../src/event/cloudwatch_alarms/mod.rs | 91 ++++++ .../src/event/cloudwatch_events/cloudtrail.rs | 49 +++ .../src/event/cloudwatch_events/codedeploy.rs | 23 ++ .../event/cloudwatch_events/codepipeline.rs | 30 ++ .../src/event/cloudwatch_events/ec2.rs | 9 + .../src/event/cloudwatch_events/emr.rs | 30 ++ .../src/event/cloudwatch_events/gamelift.rs | 86 +++++ .../src/event/cloudwatch_events/glue.rs | 58 ++++ .../src/event/cloudwatch_events/health.rs | 23 ++ .../src/event/cloudwatch_events/kms.rs | 9 + .../src/event/cloudwatch_events/macie.rs | 129 ++++++++ .../src/event/cloudwatch_events/mod.rs | 7 + .../src/event/cloudwatch_events/opsworks.rs | 30 ++ .../src/event/cloudwatch_events/signin.rs | 28 ++ .../src/event/cloudwatch_events/sms.rs | 9 + .../src/event/cloudwatch_events/ssm.rs | 100 ++++++ .../src/event/cloudwatch_events/tag.rs | 9 + .../event/cloudwatch_events/trustedadvisor.rs | 9 + .../src/event/cloudwatch_logs/mod.rs | 25 ++ lambda-events/src/event/code_commit/mod.rs | 33 ++ lambda-events/src/event/codebuild/mod.rs | 63 ++++ lambda-events/src/event/codedeploy/mod.rs | 23 ++ .../src/event/codepipeline_cloudwatch/mod.rs | 23 ++ .../src/event/codepipeline_job/mod.rs | 79 +++++ lambda-events/src/event/cognito/mod.rs | 309 +++++++++++++++++- lambda-events/src/event/config/mod.rs | 9 + lambda-events/src/event/connect/mod.rs | 37 +++ .../event/documentdb/events/commom_types.rs | 42 +++ .../event/documentdb/events/delete_event.rs | 9 + .../documentdb/events/drop_database_event.rs | 9 + .../src/event/documentdb/events/drop_event.rs | 9 + .../event/documentdb/events/insert_event.rs | 9 + .../documentdb/events/invalidate_event.rs | 9 + .../event/documentdb/events/rename_event.rs | 9 + .../event/documentdb/events/replace_event.rs | 9 + .../event/documentdb/events/update_event.rs | 23 ++ lambda-events/src/event/documentdb/mod.rs | 16 + lambda-events/src/event/dynamodb/mod.rs | 44 +++ lambda-events/src/event/ecr_scan/mod.rs | 23 ++ lambda-events/src/event/eventbridge/mod.rs | 7 + lambda-events/src/event/firehose/mod.rs | 44 +++ lambda-events/src/event/iam/mod.rs | 18 + lambda-events/src/event/iot/mod.rs | 51 +++ lambda-events/src/event/iot_1_click/mod.rs | 37 +++ lambda-events/src/event/iot_button/mod.rs | 9 + lambda-events/src/event/iot_deprecated/mod.rs | 16 + lambda-events/src/event/kafka/mod.rs | 16 + lambda-events/src/event/kinesis/analytics.rs | 30 ++ lambda-events/src/event/kinesis/event.rs | 37 +++ .../src/event/lambda_function_urls/mod.rs | 44 +++ lambda-events/src/event/lex/mod.rs | 65 ++++ lambda-events/src/event/rabbitmq/mod.rs | 21 ++ lambda-events/src/event/s3/batch_job.rs | 37 +++ lambda-events/src/event/s3/event.rs | 51 +++ lambda-events/src/event/s3/object_lambda.rs | 70 ++++ lambda-events/src/event/secretsmanager/mod.rs | 9 + lambda-events/src/event/ses/mod.rs | 72 ++++ lambda-events/src/event/sns/mod.rs | 100 ++++++ lambda-events/src/event/sqs/mod.rs | 79 +++++ lambda-events/src/event/streams/mod.rs | 44 +++ .../example-apigw-request-catch-all.json | 137 ++++++++ lambda-http/Cargo.toml | 3 +- lambda-http/src/response.rs | 12 + lambda-integration-tests/Cargo.toml | 3 + lambda-integration-tests/src/authorizer.rs | 6 + lambda-integration-tests/src/helloworld.rs | 2 + 77 files changed, 3074 insertions(+), 7 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-request-catch-all.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 481f77ba..cd2d86f2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.16.1" +version = "0.17.0" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ @@ -125,5 +125,7 @@ streams = [] documentdb = [] eventbridge = ["chrono", "serde_with"] +catch-all-fields = [] + [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 89cfda1c..d9283bea 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -11,6 +13,13 @@ pub struct ActiveMqEvent { #[serde(default)] pub event_source_arn: Option, pub messages: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -41,6 +50,13 @@ pub struct ActiveMqMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub properties: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -48,6 +64,13 @@ pub struct ActiveMqMessage { pub struct ActiveMqDestination { #[serde(default)] pub physical_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 3b4ce9d5..55e427e2 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -7,6 +7,8 @@ use crate::{ use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -30,6 +32,13 @@ pub struct AlbTargetGroupRequest { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub body: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda @@ -37,6 +46,13 @@ pub struct AlbTargetGroupRequest { #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequestContext { pub elb: ElbContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ElbContext` contains the information to identify the ARN invoking the lambda @@ -46,6 +62,13 @@ pub struct ElbContext { /// nolint: stylecheck #[serde(default)] pub target_group_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request @@ -65,6 +88,13 @@ pub struct AlbTargetGroupResponse { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index e0aa1e8c..c7a6175b 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -47,6 +47,13 @@ pub struct ApiGatewayProxyRequest { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request @@ -64,6 +71,13 @@ pub struct ApiGatewayProxyResponse { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the @@ -110,6 +124,13 @@ pub struct ApiGatewayProxyRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway @@ -160,6 +181,13 @@ pub struct ApiGatewayV2httpRequest { pub body: Option, #[serde(default)] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. @@ -192,6 +220,13 @@ pub struct ApiGatewayV2httpRequestContext { pub http: ApiGatewayV2httpRequestContextHttpDescription, #[serde(skip_serializing_if = "Option::is_none")] pub authentication: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. @@ -209,6 +244,13 @@ pub struct ApiGatewayRequestAuthorizer { pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub iam: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. @@ -220,6 +262,13 @@ pub struct ApiGatewayRequestAuthorizerJwtDescription { pub claims: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub scopes: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. @@ -240,6 +289,13 @@ pub struct ApiGatewayRequestAuthorizerIamDescription { pub user_arn: Option, #[serde(default)] pub user_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. @@ -251,6 +307,13 @@ pub struct ApiGatewayRequestAuthorizerCognitoIdentity { pub identity_id: Option, #[serde(default)] pub identity_pool_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. @@ -267,6 +330,13 @@ pub struct ApiGatewayV2httpRequestContextHttpDescription { pub source_ip: Option, #[serde(default)] pub user_agent: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request @@ -285,6 +355,13 @@ pub struct ApiGatewayV2httpResponse { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub cookies: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestIdentity` contains identity information for the request caller. @@ -318,6 +395,13 @@ pub struct ApiGatewayRequestIdentity { pub user_agent: Option, #[serde(default)] pub user: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy @@ -357,6 +441,13 @@ pub struct ApiGatewayWebsocketProxyRequest { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify @@ -421,6 +512,13 @@ pub struct ApiGatewayWebsocketProxyRequestContext { pub disconnect_status_code: Option, #[serde(default)] pub disconnect_reason: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. @@ -435,6 +533,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { pub source_ip: Option, #[serde(default)] pub client_cert: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. @@ -452,6 +557,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { #[serde(rename = "subjectDN")] pub subject_dn: Option, pub validity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. @@ -462,6 +574,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit pub not_after: Option, #[serde(default)] pub not_before: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. @@ -470,6 +589,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit pub struct ApiGatewayV2httpRequestContextAuthentication { #[serde(default)] pub client_cert: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. @@ -487,6 +613,13 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { #[serde(rename = "subjectDN")] pub subject_dn: Option, pub validity: ApiGatewayV2httpRequestContextAuthenticationClientCertValidity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. @@ -497,6 +630,13 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { pub not_after: Option, #[serde(default)] pub not_before: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -520,6 +660,13 @@ pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -555,6 +702,13 @@ pub struct ApiGatewayV2CustomAuthorizerV1Request { #[serde(default)] pub stage_variables: HashMap, pub request_context: ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -590,6 +744,13 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerContext` represents the expected format of an API Gateway custom authorizer response. @@ -602,6 +763,13 @@ pub struct ApiGatewayCustomAuthorizerContext { pub num_key: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub bool_key: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. @@ -630,6 +798,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. @@ -643,6 +818,13 @@ pub struct ApiGatewayCustomAuthorizerRequest { /// nolint: stylecheck #[serde(default)] pub method_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. @@ -680,6 +862,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { #[serde(default)] pub stage_variables: HashMap, pub request_context: ApiGatewayCustomAuthorizerRequestTypeRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. @@ -696,6 +885,13 @@ where #[serde(bound = "", default)] pub context: T1, pub usage_identifier_key: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. @@ -709,6 +905,13 @@ where pub is_authorized: bool, #[serde(bound = "", default)] pub context: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -723,6 +926,13 @@ where pub policy_document: ApiGatewayCustomAuthorizerPolicy, #[serde(bound = "", default)] pub context: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy @@ -732,6 +942,13 @@ pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(default)] pub version: Option, pub statement: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } fn default_http_method() -> Method { @@ -876,6 +1093,24 @@ mod test { assert!(output.contains(r#""headername":"headerValue2""#)); } + #[test] + #[cfg(all(feature = "apigw", feature = "catch-all-fields"))] + fn example_apigw_request_catch_all() { + use serde_json::json; + + let data = include_bytes!("../../fixtures/example-apigw-request-catch-all.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert_eq!(parsed.other.get("otherField"), Some(&json!("foobar"))); + assert_eq!( + parsed.request_context.identity.other.get("otherField"), + Some(&json!(2345)) + ); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_restapi_openapi_request() { diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 4aa62e5b..ed68915e 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -17,6 +17,13 @@ where pub operation: AppSyncOperation, #[serde(bound = "")] pub payload: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. @@ -38,6 +45,13 @@ pub struct AppSyncIamIdentity { pub username: Option, #[serde(default)] pub user_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. @@ -61,6 +75,13 @@ where pub source_ip: Vec, #[serde(default)] pub default_auth_strategy: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type AppSyncOperation = String; @@ -72,6 +93,13 @@ pub struct AppSyncLambdaAuthorizerRequest { #[serde(default)] pub authorization_token: Option, pub request_context: AppSyncLambdaAuthorizerRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered @@ -98,6 +126,13 @@ where #[serde(default)] #[serde(bound = "")] pub variables: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. @@ -115,6 +150,13 @@ where pub resolver_context: HashMap, pub denied_fields: Option>, pub ttl_override: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncDirectResolverEvent` represents the default payload structure sent by AWS AppSync @@ -147,6 +189,13 @@ where pub prev: Option, #[serde(bound = "")] pub stash: TStash, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncRequest` contains request-related metadata for a resolver invocation, @@ -160,6 +209,13 @@ pub struct AppSyncRequest { pub headers: HashMap>, #[serde(default)] pub domain_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. @@ -177,6 +233,13 @@ where pub field_name: String, #[serde(bound = "")] pub variables: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. @@ -187,6 +250,13 @@ where { #[serde(bound = "")] pub result: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIdentity` represents the identity of the caller as determined by the @@ -210,6 +280,13 @@ where pub claims: T, pub issuer: String, pub sub: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda @@ -221,6 +298,13 @@ where { #[serde(bound = "")] pub resolver_context: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 601e8774..1aeea5a2 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -41,6 +41,13 @@ where #[serde(default)] #[serde(bound = "")] pub detail: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index c1425b85..e7b4e69c 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from Agents for Amazon Bedrock. @@ -29,6 +31,13 @@ pub struct AgentEvent { pub session_attributes: HashMap, /// Contains prompt attributes and their values. pub prompt_session_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -36,6 +45,13 @@ pub struct AgentEvent { pub struct RequestBody { /// Contains the request body and its properties pub content: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -43,6 +59,13 @@ pub struct RequestBody { pub struct Content { /// The content of the request body pub properties: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -54,6 +77,13 @@ pub struct Property { pub r#type: String, /// The value of the parameter pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -65,6 +95,13 @@ pub struct Parameter { pub r#type: String, /// The value of the parameter pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -78,6 +115,13 @@ pub struct Agent { pub alias: String, /// The version of the agent. pub version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index 6581ed2c..d4bcd5f6 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -17,6 +19,13 @@ pub struct ChimeBotEvent { pub event_timestamp: DateTime, #[serde(rename = "Message")] pub message: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +37,13 @@ pub struct ChimeBotEventSender { #[serde(default)] #[serde(rename = "SenderIdType")] pub sender_id_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -39,6 +55,13 @@ pub struct ChimeBotEventDiscussion { #[serde(default)] #[serde(rename = "DiscussionType")] pub discussion_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -50,4 +73,11 @@ pub struct ChimeBotEventInboundHttpsEndpoint { #[serde(default)] #[serde(rename = "Url")] pub url: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 163abe72..e99d7c8c 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -29,6 +31,13 @@ pub struct ClientVpnConnectionHandlerRequest { #[serde(default)] #[serde(rename = "schema-version")] pub schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -43,6 +52,13 @@ pub struct ClientVpnConnectionHandlerResponse { #[serde(default)] #[serde(rename = "schema-version")] pub schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 44156b97..4134c144 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -35,6 +35,13 @@ where pub logical_resource_id: String, #[serde(bound = "")] pub resource_properties: P2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -57,6 +64,13 @@ where pub resource_properties: P2, #[serde(bound = "")] pub old_resource_properties: P1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -76,6 +90,13 @@ where pub physical_resource_id: String, #[serde(bound = "")] pub resource_properties: P2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -89,6 +110,13 @@ pub struct CloudFormationCustomResourceResponse { pub logical_resource_id: String, pub no_echo: bool, pub data: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index df5ba80a..786d6075 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -30,6 +30,7 @@ where { #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -46,6 +47,7 @@ where #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -58,6 +60,7 @@ where #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -72,6 +75,13 @@ where pub resource_type: String, pub request_id: String, pub stack_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] @@ -84,6 +94,13 @@ where #[serde(bound = "")] pub data: D, pub no_echo: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 720236c2..c3efebe3 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -33,6 +33,13 @@ where #[serde(default, bound = "")] pub alarm_data: CloudWatchAlarmData, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. @@ -59,6 +66,13 @@ where pub previous_state: Option>, #[serde(bound = "")] pub configuration: C, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -76,6 +90,13 @@ where pub timestamp: DateTime, pub actions_suppressed_by: Option, pub actions_suppressed_reason: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -85,6 +106,13 @@ pub struct CloudWatchMetricAlarmConfiguration { pub description: Option, #[serde(default)] pub metrics: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -94,6 +122,13 @@ pub struct CloudWatchMetricDefinition { #[serde(default)] pub return_data: bool, pub metric_stat: CloudWatchMetricStatDefinition, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -105,6 +140,13 @@ pub struct CloudWatchMetricStatDefinition { pub stat: Option, pub period: u16, pub metric: CloudWatchMetricStatMetricDefinition, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -114,6 +156,13 @@ pub struct CloudWatchMetricStatMetricDefinition { pub namespace: Option, pub name: String, pub dimensions: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -123,6 +172,13 @@ pub struct CloudWatchCompositeAlarmConfiguration { pub actions_suppressor: String, pub actions_suppressor_wait_period: u16, pub actions_suppressor_extension_period: u16, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -169,6 +225,13 @@ pub struct CloudWatchAlarmStateReasonDataMetric { pub threshold: f64, #[serde(default)] pub evaluated_datapoints: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -181,6 +244,13 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub value: Option, #[serde(default)] pub threshold: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -188,6 +258,13 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub struct ClodWatchAlarmStateReasonDataComposite { #[serde(default)] pub triggering_alarms: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -195,6 +272,13 @@ pub struct ClodWatchAlarmStateReasonDataComposite { pub struct CloudWatchAlarmStateTriggeringAlarm { pub arn: String, pub state: CloudWatchAlarmStateTriggeringAlarmState, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -203,6 +287,13 @@ pub struct CloudWatchAlarmStateTriggeringAlarmState { pub timestamp: DateTime, #[serde(default)] pub value: CloudWatchAlarmStateValue, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } impl<'de> Deserialize<'de> for CloudWatchAlarmStateReasonData { diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index fc332306..08b7500a 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -23,6 +23,13 @@ pub struct AWSAPICall { #[serde(rename = "eventID")] pub event_id: String, pub event_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,6 +40,13 @@ pub struct SessionIssuer { pub principal_id: String, pub arn: String, pub account_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -40,6 +54,13 @@ pub struct SessionIssuer { pub struct WebIdFederationData { pub federated_provider: Option, pub attributes: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -47,6 +68,13 @@ pub struct WebIdFederationData { pub struct Attributes { pub mfa_authenticated: String, pub creation_date: DateTime, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +85,13 @@ pub struct SessionContext { pub attributes: Attributes, pub source_identity: Option, pub ec2_role_delivery: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -64,6 +99,13 @@ pub struct SessionContext { pub struct OnBehalfOf { pub user_id: String, pub identity_store_arn: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } // https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html @@ -79,6 +121,13 @@ pub struct UserIdentity { pub session_context: Option, pub user_name: Option, pub on_behalf_of: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index 1bd44297..b52528ca 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct StateChangeNotification { pub deployment_id: String, pub state: String, pub deployment_group: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -21,6 +30,13 @@ pub struct DeploymentStateChangeNotification { pub deployment_id: String, pub instance_group_id: String, pub deployment_group: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -31,4 +47,11 @@ pub struct InstanceStateChangeNotification { pub state: String, #[serde(rename = "execution-id")] pub execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index ce5fa47c..e99798e7 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct PipelineExecutionStateChange { pub state: String, #[serde(rename = "execution-id")] pub execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct StageExecutionStateChange { pub execution_id: String, pub stage: String, pub state: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -34,6 +50,13 @@ pub struct ActionExecutionStateChange { pub region: String, #[serde(rename = "type")] pub type_field: ActionExecutionStateChangeType, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -43,4 +66,11 @@ pub struct ActionExecutionStateChangeType { pub category: String, pub provider: String, pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c8eb7834..c77fab4f 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -6,4 +8,11 @@ pub struct InstanceStateChange { #[serde(rename = "instance-id")] pub instance_id: String, pub state: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 87fb8085..4aed4818 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct AutoScalingPolicyStateChange { pub state: String, pub message: String, pub scaling_resource_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct ClusterStateChange { pub cluster_id: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -34,6 +50,13 @@ pub struct InstanceGroupStateChange { pub running_instance_count: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -46,4 +69,11 @@ pub struct StepStatusChange { pub cluster_id: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index fb5c50a7..009f1056 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; @@ -9,6 +11,13 @@ pub struct MatchmakingSearching { pub estimated_wait_millis: String, pub r#type: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -17,6 +26,13 @@ pub struct Ticket { pub ticket_id: String, pub start_time: String, pub players: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -27,12 +43,26 @@ pub struct Player { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub accepted: bool, pub player_session_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GameSessionInfo { pub players: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -45,6 +75,13 @@ pub struct PotentialMatchCreated { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -53,6 +90,13 @@ pub struct RuleEvaluationMetric { pub rule_name: String, pub passed_count: i64, pub failed_count: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -62,6 +106,13 @@ pub struct AcceptMatch { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -72,6 +123,13 @@ pub struct AcceptMatchCompleted { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -81,6 +139,13 @@ pub struct MatchmakingSucceeded { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,6 +157,13 @@ pub struct MatchmakingTimedOut { pub r#type: String, pub message: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -103,6 +175,13 @@ pub struct MatchmakingCancelled { pub r#type: String, pub message: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -115,4 +194,11 @@ pub struct MatchmakingFailed { pub message: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index 08f05929..21669098 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct JobRunStateChange { pub state: String, pub job_run_id: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -18,6 +27,13 @@ pub struct CrawlerStarted { pub start_time: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -38,6 +54,13 @@ pub struct CrawlerSucceeded { pub state: String, pub partitions_created: String, pub cloud_watch_log_link: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -49,6 +72,13 @@ pub struct CrawlerFailed { pub cloud_watch_log_link: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -61,6 +91,13 @@ pub struct JobRunStatus { pub job_run_id: String, pub message: String, pub started_on: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -68,6 +105,13 @@ pub struct JobRunStatus { pub struct NotificationCondition { #[serde(rename = "NotifyDelayAfter")] pub notify_delay_after: f64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -77,6 +121,13 @@ pub struct DataCatalogTableStateChange { pub changed_partitions: Vec, pub type_of_change: String, pub table_name: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -85,4 +136,11 @@ pub struct DataCatalogDatabaseStateChange { pub database_name: String, pub type_of_change: String, pub changed_tables: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index 2a6a82e3..c6d8cca1 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -12,6 +14,13 @@ pub struct Event { pub end_time: String, pub event_description: Vec, pub affected_entities: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct Event { pub struct EventDescription { pub language: String, pub latest_description: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -26,4 +42,11 @@ pub struct EventDescription { pub struct Entity { pub entity_value: String, pub tags: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index 74a76e70..2a0041de 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -1,8 +1,17 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CMKEvent { #[serde(rename = "key-id")] pub key_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index 37d18d7a..a652b8b2 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -1,4 +1,7 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -18,6 +21,13 @@ pub struct Alert { pub created_at: String, pub actor: String, pub summary: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type BucketScanAlert = Alert; @@ -35,6 +45,13 @@ pub struct Trigger { pub created_at: String, pub description: String, pub risk: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +74,13 @@ pub struct BucketScanSummary { #[serde(rename = "Events")] pub events: HashMap, pub recipient_account_id: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -68,6 +92,13 @@ pub struct Ip { pub n34_205_153_2: i64, #[serde(rename = "72.21.196.70")] pub n72_21_196_70: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -76,6 +107,13 @@ pub struct TimeRange { pub count: i64, pub start: String, pub end: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -83,6 +121,13 @@ pub struct TimeRange { pub struct Location { #[serde(rename = "us-east-1")] pub us_east_1: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,6 +137,13 @@ pub struct ActionInfo { #[serde(rename = "ISP")] pub isp: HashMap, pub error_code: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -109,6 +161,13 @@ pub struct BucketWritableSummary { pub event_count: i64, #[serde(rename = "Timestamps")] pub timestamps: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -116,6 +175,13 @@ pub struct BucketWritableSummary { pub struct Bucket { #[serde(rename = "secret-bucket-name")] pub secret_bucket_name: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -123,6 +189,13 @@ pub struct Bucket { pub struct Acl { #[serde(rename = "secret-bucket-name")] pub secret_bucket_name: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -132,6 +205,13 @@ pub struct SecretBucketName { pub owner: Owner, #[serde(rename = "Grants")] pub grants: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -141,6 +221,13 @@ pub struct Owner { pub display_name: String, #[serde(rename = "ID")] pub id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -150,6 +237,13 @@ pub struct Grant { pub grantee: Grantee, #[serde(rename = "Permission")] pub permission: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -158,6 +252,13 @@ pub struct Grantee { pub r#type: String, #[serde(rename = "URI")] pub uri: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -179,6 +280,13 @@ pub struct BucketContainsHighRiskObjectSummary { pub owner: HashMap, #[serde(rename = "Timestamps")] pub timestamps: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -197,6 +305,13 @@ pub struct AlertUpdated { pub created_at: String, pub actor: String, pub trigger: UpdatedTrigger, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -205,6 +320,13 @@ pub struct UpdatedTrigger { #[serde(rename = "alert-type")] pub alert_type: String, pub features: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -218,4 +340,11 @@ pub struct FeatureInfo { #[serde(rename = "excession_times")] pub excession_times: Vec, pub risk: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 2af343de..fd680c01 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -46,4 +46,11 @@ where pub resources: Vec, #[serde(bound = "")] pub detail: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index d1c192e5..30818648 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -15,6 +17,13 @@ pub struct InstanceStateChange { #[serde(rename = "ec2-instance-id")] pub ec2_instance_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -26,6 +35,13 @@ pub struct CommandStateChange { pub instance_id: String, pub r#type: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -40,6 +56,13 @@ pub struct DeploymentStateChange { pub deployment_id: String, pub command: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -51,4 +74,11 @@ pub struct Alert { pub instance_id: String, pub r#type: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 1cd73e6e..2eb1ea5e 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -19,6 +19,13 @@ pub struct SignIn { #[serde(rename = "eventID")] pub event_id: String, pub event_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -28,6 +35,13 @@ pub struct UserIdentity { pub principal_id: String, pub arn: String, pub account_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -35,6 +49,13 @@ pub struct UserIdentity { pub struct ResponseElements { #[serde(rename = "ConsoleLogin")] pub console_login: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -46,4 +67,11 @@ pub struct AdditionalEventData { pub mobile_version: String, #[serde(rename = "MFAUsed")] pub mfaused: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 7d161822..6447887a 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -11,4 +13,11 @@ pub struct JobStateChange { #[serde(rename = "ami-id")] pub ami_id: Option, pub version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index fa6ffc3b..53a48e10 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -22,6 +24,13 @@ pub struct EC2AutomationStepStatusChange { pub step_name: String, #[serde(rename = "Action")] pub action: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -43,6 +52,13 @@ pub struct EC2AutomationExecutionStatusChange { pub time: f64, #[serde(rename = "ExecutedBy")] pub executed_by: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -51,6 +67,13 @@ pub struct StateChange { pub state: String, pub at_time: String, pub next_transition_time: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -69,6 +92,13 @@ pub struct ConfigurationComplianceStateChange { #[serde(rename = "patch-baseline-id")] pub patch_baseline_id: Option, pub serverity: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -79,6 +109,13 @@ pub struct MaintenanceWindowTargetRegistration { #[serde(rename = "window-id")] pub window_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -93,6 +130,13 @@ pub struct MaintenanceWindowExecutionStateChange { #[serde(rename = "window-execution-id")] pub window_execution_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -109,6 +153,13 @@ pub struct MaintenanceWindowTaskExecutionStateChange { #[serde(rename = "window-execution-id")] pub window_execution_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -129,6 +180,13 @@ pub struct MaintenanceWindowTaskTargetInvocationStateChange { pub status: String, #[serde(rename = "owner-information")] pub owner_information: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -137,6 +195,13 @@ pub struct MaintenanceWindowStateChange { #[serde(rename = "window-id")] pub window_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -146,6 +211,13 @@ pub struct ParameterStoreStateChange { pub name: String, pub r#type: String, pub description: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -161,6 +233,13 @@ pub struct EC2CommandStatusChange { #[serde(rename = "requested-date-time")] pub requested_date_time: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -175,6 +254,13 @@ pub struct EC2CommandInvocationStatusChange { #[serde(rename = "requested-date-time")] pub requested_date_time: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -204,6 +290,13 @@ pub struct EC2StateManagerAssociationStateChange { pub schedule_expression: String, #[serde(rename = "association-cwe-version")] pub association_cwe_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -235,4 +328,11 @@ pub struct EC2StateManagerInstanceAssociationStateChange { pub output_url: String, #[serde(rename = "instance-association-cwe-version")] pub instance_association_cwe_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index d5bc9681..08fbe24c 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use serde::{Deserialize, Serialize}; @@ -12,4 +14,11 @@ pub struct TagChangeOnResource { pub resource_type: String, pub version: i64, pub tags: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index 6a7e25d3..19f2aba0 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -12,4 +14,11 @@ pub struct CheckItemRefreshNotification { #[serde(rename = "resource_id")] pub resource_id: String, pub uuid: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 0c9ad4a8..1a485a06 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -3,6 +3,10 @@ use serde::{ ser::{Error as SeError, SerializeStruct}, Deserialize, Deserializer, Serialize, Serializer, }; +#[cfg(feature = "catch-all-fields")] +#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] +use serde_json::Value; + use std::{fmt, io::BufReader}; /// `LogsEvent` represents the raw event sent by CloudWatch @@ -11,6 +15,13 @@ pub struct LogsEvent { /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer #[serde(rename = "awslogs")] pub aws_logs: AwsLogs, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event @@ -36,6 +47,13 @@ pub struct LogData { pub message_type: String, /// Entries in the log batch pub log_events: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LogEntry` represents a log entry from cloudwatch logs @@ -47,6 +65,13 @@ pub struct LogEntry { pub timestamp: i64, /// Message published in the application log pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } impl<'de> Deserialize<'de> for AwsLogs { diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 7d5edfaa..81d85ef8 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; @@ -9,6 +11,13 @@ use crate::custom_serde::deserialize_nullish_boolean; pub struct CodeCommitEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type CodeCommitEventTime = DateTime; @@ -44,6 +53,13 @@ pub struct CodeCommitRecord { pub aws_region: Option, pub event_total_parts: u64, pub custom_data: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record @@ -51,6 +67,16 @@ pub struct CodeCommitRecord { #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { pub references: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeCommitReference` represents a Reference object in a CodeCommit object @@ -63,6 +89,13 @@ pub struct CodeCommitReference { pub ref_: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub created: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index ad7775d2..ac0a50de 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -44,6 +44,13 @@ pub struct CodeBuildEvent { /// Detail contains information specific to a build state-change or /// build phase-change event. pub detail: CodeBuildEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEventDetail` represents the all details related to the code build event @@ -84,6 +91,13 @@ pub struct CodeBuildEventDetail { #[serde(default)] #[serde(with = "codebuild_time::optional_time")] pub completed_phase_end: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event @@ -109,6 +123,13 @@ pub struct CodeBuildEventAdditionalInformation { pub source_version: Option, pub logs: CodeBuildLogs, pub phases: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildArtifact` represents the artifact provided to build @@ -123,6 +144,13 @@ pub struct CodeBuildArtifact { pub sha256_sum: Option, #[serde(default)] pub location: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEnvironment` represents the environment for a build @@ -140,6 +168,13 @@ pub struct CodeBuildEnvironment { pub type_: Option, #[serde(rename = "environment-variables")] pub environment_variables: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build @@ -155,6 +190,13 @@ pub struct CodeBuildEnvironmentVariable { /// Value is the value of the environment variable. #[serde(default)] pub value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildSource` represent the code source will be build @@ -165,6 +207,13 @@ pub struct CodeBuildSource { pub location: Option, #[serde(default)] pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildLogs` gives the log details of a code build @@ -180,6 +229,13 @@ pub struct CodeBuildLogs { #[serde(default)] #[serde(rename = "deep-link")] pub deep_link: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildPhase` represents the phase of a build and its details @@ -206,6 +262,13 @@ where pub phase_type: CodeBuildPhaseType, #[serde(rename = "phase-status")] pub phase_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type CodeBuildTime = DateTime; diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index b0a25c5b..ec6657b6 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; pub type CodeDeployDeploymentState = String; @@ -38,6 +40,13 @@ pub struct CodeDeployEvent { pub resources: Vec, /// Detail contains information specific to a deployment event. pub detail: CodeDeployEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -63,6 +72,13 @@ pub struct CodeDeployEventDetail { /// DeploymentGroup is the name of the deployment group. #[serde(default)] pub deployment_group: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] @@ -70,6 +86,13 @@ pub struct CodeDeployEventDetail { pub struct CodeDeployLifecycleEvent { pub deployment_id: String, pub lifecycle_event_hook_execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 6e394c17..1863e8aa 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; pub type CodePipelineStageState = String; @@ -42,6 +44,13 @@ pub struct CodePipelineCloudWatchEvent { pub resources: Vec, /// Detail contains information specific to a deployment event. pub detail: CodePipelineEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -62,6 +71,13 @@ pub struct CodePipelineEventDetail { #[serde(default)] pub region: Option, pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -75,6 +91,13 @@ pub struct CodePipelineEventDetailType { pub provider: Option, /// From published EventBridge schema registry this is always int64 not string as documented pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 888e77b7..befb4c4c 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -6,6 +8,13 @@ use serde::{Deserialize, Serialize}; pub struct CodePipelineJobEvent { #[serde(rename = "CodePipeline.job")] pub code_pipeline_job: CodePipelineJob, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineJob` represents a job from an AWS CodePipeline event @@ -17,6 +26,13 @@ pub struct CodePipelineJob { #[serde(default)] pub account_id: Option, pub data: CodePipelineData, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineData` represents a job from an AWS CodePipeline event @@ -30,6 +46,13 @@ pub struct CodePipelineData { pub artifact_credentials: CodePipelineArtifactCredentials, #[serde(default)] pub continuation_token: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineActionConfiguration` represents an Action Configuration @@ -37,6 +60,13 @@ pub struct CodePipelineData { #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { pub configuration: CodePipelineConfiguration, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration @@ -49,6 +79,13 @@ pub struct CodePipelineConfiguration { #[serde(default)] #[serde(rename = "UserParameters")] pub user_parameters: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineInputArtifact` represents an input artifact @@ -59,6 +96,13 @@ pub struct CodePipelineInputArtifact { pub revision: Option, #[serde(default)] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineInputLocation` represents a input location @@ -69,6 +113,13 @@ pub struct CodePipelineInputLocation { #[serde(default)] #[serde(rename = "type")] pub location_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineS3Location` represents an s3 input location @@ -79,6 +130,13 @@ pub struct CodePipelineS3Location { pub bucket_name: Option, #[serde(default)] pub object_key: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineOutputArtifact` represents an output artifact @@ -89,6 +147,13 @@ pub struct CodePipelineOutputArtifact { pub revision: Option, #[serde(default)] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineOutputLocation` represents a output location @@ -99,6 +164,13 @@ pub struct CodePipelineOutputLocation { #[serde(default)] #[serde(rename = "type")] pub location_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials @@ -111,6 +183,13 @@ pub struct CodePipelineArtifactCredentials { pub session_token: Option, #[serde(default)] pub access_key_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index a0ebd8d9..315bc2ff 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -22,6 +22,13 @@ pub struct CognitoEvent { #[serde(default)] pub region: Option, pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event @@ -34,6 +41,13 @@ pub struct CognitoDatasetRecord { pub old_value: Option, #[serde(default)] pub op: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreSignup` is sent by AWS Cognito User Pools when a user attempts to register @@ -46,6 +60,13 @@ pub struct CognitoEventUserPoolsPreSignup { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreSignupRequest, pub response: CognitoEventUserPoolsPreSignupResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -70,6 +91,13 @@ pub struct CognitoEventUserPoolsPreAuthentication { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreAuthenticationRequest, pub response: CognitoEventUserPoolsPreAuthenticationResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -95,6 +123,13 @@ where pub request: CognitoEventUserPoolsPostConfirmationRequest, #[serde(bound = "")] pub response: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -116,6 +151,13 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequest, pub response: CognitoEventUserPoolsPreTokenGenResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -144,6 +186,13 @@ pub struct CognitoEventUserPoolsPostAuthentication { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostAuthenticationRequest, pub response: CognitoEventUserPoolsPostAuthenticationResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -165,6 +214,13 @@ pub struct CognitoEventUserPoolsMigrateUser { pub cognito_event_user_pools_migrate_user_request: CognitoEventUserPoolsMigrateUserRequest, #[serde(rename = "response")] pub cognito_event_user_pools_migrate_user_response: CognitoEventUserPoolsMigrateUserResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -185,6 +241,13 @@ pub struct CognitoEventUserPoolsCallerContext { pub awssdk_version: Option, #[serde(default)] pub client_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools @@ -202,6 +265,9 @@ pub struct CognitoEventUserPoolsHeader { pub caller_context: CognitoEventUserPoolsCallerContext, #[serde(default)] pub user_name: Option, + // no `other` catch-all, because this struct is itself #[serde(flatten)]-ed + // into a different struct that contains an `other` catch-all, so any + // additional fields will be caught there instead } /// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event @@ -217,6 +283,13 @@ pub struct CognitoEventUserPoolsPreSignupRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event @@ -226,6 +299,13 @@ pub struct CognitoEventUserPoolsPreSignupResponse { pub auto_confirm_user: bool, pub auto_verify_email: bool, pub auto_verify_phone: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event @@ -238,11 +318,26 @@ pub struct CognitoEventUserPoolsPreAuthenticationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub validation_data: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPreAuthenticationResponse {} +pub struct CognitoEventUserPoolsPreAuthenticationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -253,11 +348,26 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPostConfirmationResponse {} +pub struct CognitoEventUserPoolsPostConfirmationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -270,6 +380,13 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event @@ -277,6 +394,13 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponse { pub claims_override_details: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve @@ -289,6 +413,13 @@ pub struct CognitoEventUserPoolsPreTokenGenV2 { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequestV2, pub response: CognitoEventUserPoolsPreTokenGenResponseV2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event @@ -303,12 +434,26 @@ pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { #[serde(default)] pub client_metadata: HashMap, pub scopes: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { pub claims_and_scope_override_details: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token @@ -318,6 +463,13 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { pub group_override_details: GroupConfiguration, pub id_token_generation: Option, pub access_token_generation: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation @@ -326,6 +478,13 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { pub struct CognitoIdTokenGenerationV2 { pub claims_to_add_or_override: HashMap, pub claims_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation @@ -336,6 +495,13 @@ pub struct CognitoAccessTokenGenerationV2 { pub claims_to_suppress: Vec, pub scopes_to_add: Vec, pub scopes_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event @@ -349,11 +515,26 @@ pub struct CognitoEventUserPoolsPostAuthenticationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPostAuthenticationResponse {} +pub struct CognitoEventUserPoolsPostAuthenticationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -366,6 +547,13 @@ pub struct CognitoEventUserPoolsMigrateUserRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event @@ -383,6 +571,13 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { pub desired_delivery_mediums: Option>, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub force_alias_creation: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ClaimsOverrideDetails` allows lambda to add, suppress or override claims in the token @@ -394,6 +589,13 @@ pub struct ClaimsOverrideDetails { #[serde(default)] pub claims_to_add_or_override: HashMap, pub claims_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role @@ -403,6 +605,13 @@ pub struct GroupConfiguration { pub groups_to_override: Vec, pub iam_roles_to_override: Vec, pub preferred_role: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication @@ -415,6 +624,13 @@ pub struct CognitoEventUserPoolsChallengeResult { pub challenge_result: bool, #[serde(default)] pub challenge_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters @@ -430,6 +646,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters @@ -442,6 +665,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { pub issue_tokens: bool, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub fail_authentication: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow @@ -454,6 +684,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -478,6 +715,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters @@ -492,6 +736,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { pub private_challenge_parameters: HashMap, #[serde(default)] pub challenge_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user @@ -504,6 +755,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -534,6 +792,13 @@ where pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters @@ -542,6 +807,13 @@ where pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub answer_correct: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsVerifyAuthChallenge` sent by AWS Cognito User Pools to verify if the response from the end user @@ -555,6 +827,13 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -574,6 +853,13 @@ pub struct CognitoEventUserPoolsCustomMessage { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCustomMessageRequest, pub response: CognitoEventUserPoolsCustomMessageResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -614,6 +900,13 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event @@ -626,6 +919,13 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { pub email_message: Option, #[serde(default)] pub email_subject: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] @@ -855,7 +1155,8 @@ mod test { assert!(parsed.request.user_not_found); - let output: String = serde_json::to_string(&parsed).unwrap(); + let output: String = serde_json::to_string_pretty(&parsed).unwrap(); + println!("output is: {output}"); let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 7c06e13b..fd2631a6 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -34,6 +36,13 @@ pub struct ConfigEvent { pub rule_parameters: Option, #[serde(default)] pub version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 04f26eb5..c6bdb47b 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -13,6 +15,13 @@ pub struct ConnectEvent { #[serde(default)] #[serde(rename = "Name")] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectDetails` holds the details of a Connect event @@ -26,6 +35,13 @@ pub struct ConnectDetails { #[serde(default)] #[serde(rename = "Parameters")] pub parameters: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. @@ -62,6 +78,13 @@ pub struct ConnectContactData { #[serde(default)] #[serde(rename = "InstanceARN")] pub instance_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectEndpoint` represents routing information. @@ -74,6 +97,13 @@ pub struct ConnectEndpoint { #[serde(default)] #[serde(rename = "Type")] pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectQueue` represents a queue object. @@ -86,6 +116,13 @@ pub struct ConnectQueue { #[serde(default)] #[serde(rename = "ARN")] pub arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type ConnectResponse = HashMap; diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs index 5d1bdc19..d9ff1c4d 100644 --- a/lambda-events/src/event/documentdb/events/commom_types.rs +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -11,34 +11,76 @@ pub struct DatabaseCollection { db: String, #[serde(default)] coll: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentId { #[serde(rename = "_data")] pub data: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyIdOid { #[serde(rename = "$oid")] pub oid: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyId { #[serde(rename = "_id")] pub id: DocumentKeyIdOid, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct InnerTimestamp { t: usize, i: usize, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Timestamp { #[serde(rename = "$timestamp")] pub timestamp: InnerTimestamp, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs index 7761d62f..4085a88b 100644 --- a/lambda-events/src/event/documentdb/events/delete_event.rs +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeDeleteEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index 273c897c..fed75259 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; @@ -16,4 +18,11 @@ pub struct ChangeDropDatabaseEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index a6f92934..23a8d7a8 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -1,5 +1,7 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -15,4 +17,11 @@ pub struct ChangeDropEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index 2f4df397..aaf82ddc 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeInsertEvent { //operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs index 47469ff9..8e8ef930 100644 --- a/lambda-events/src/event/documentdb/events/invalidate_event.rs +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{DocumentId, Timestamp}; @@ -10,4 +12,11 @@ pub struct ChangeInvalidateEvent { #[serde(default)] cluster_time: Option, // operation_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs index 8bc250fb..f1761a2a 100644 --- a/lambda-events/src/event/documentdb/events/rename_event.rs +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; @@ -18,4 +20,11 @@ pub struct ChangeRenameEvent { #[serde(default)] txn_number: Option, to: DatabaseCollection, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index c253e272..b0245241 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeReplaceEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 04369cf0..5d3795d0 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -7,6 +9,13 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentK pub struct UpdateTruncate { field: String, new_size: usize, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -15,6 +24,13 @@ pub struct UpdateDescription { removed_fields: Vec, truncated_arrays: Vec, updated_fields: AnyDocument, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,4 +49,11 @@ pub struct ChangeUpdateEvent { update_description: UpdateDescription, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs index 67f7c9ad..fa753823 100644 --- a/lambda-events/src/event/documentdb/mod.rs +++ b/lambda-events/src/event/documentdb/mod.rs @@ -6,6 +6,8 @@ use self::events::{ replace_event::ChangeReplaceEvent, update_event::ChangeUpdateEvent, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(tag = "operationType", rename_all = "camelCase")] @@ -23,6 +25,13 @@ pub enum ChangeEvent { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentDbInnerEvent { pub event: ChangeEvent, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,6 +42,13 @@ pub struct DocumentDbEvent { pub events: Vec, #[serde(default)] pub event_source: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 91380f82..2a99ed69 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -5,6 +5,8 @@ use crate::{ }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::fmt; #[cfg(test)] @@ -115,6 +117,13 @@ impl fmt::Display for KeyType { pub struct Event { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows @@ -128,6 +137,13 @@ pub struct TimeWindowEvent { #[serde(rename = "TimeWindowProperties")] #[serde(flatten)] pub time_window_properties: TimeWindowProperties, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. @@ -138,6 +154,13 @@ pub struct TimeWindowEventResponse { #[serde(flatten)] pub time_window_event_response_properties: TimeWindowEventResponseProperties, pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// EventRecord stores information about each record of a DynamoDb stream event @@ -198,6 +221,13 @@ pub struct EventRecord { /// The DynamoDB table that this event was recorded for. #[serde(default)] pub table_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -207,6 +237,13 @@ pub struct UserIdentity { pub type_: String, #[serde(default)] pub principal_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item @@ -248,6 +285,13 @@ pub struct StreamRecord { #[serde(default)] #[serde(rename = "StreamViewType")] pub stream_view_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 5502e81a..f68b4e57 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -20,6 +22,13 @@ pub struct EcrScanEvent { #[serde(default)] pub account: Option, pub detail: EcrScanEventDetailType, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -38,6 +47,13 @@ pub struct EcrScanEventDetailType { pub image_digest: Option, #[serde(rename = "image-tags")] pub image_tags: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -61,6 +77,13 @@ pub struct EcrScanEventFindingSeverityCounts { #[serde(default)] #[serde(rename = "UNDEFINED")] pub undefined: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 5ed14840..f2d86550 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -30,6 +30,13 @@ where pub resources: Option>, #[serde(bound = "")] pub detail: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 6a0a13fd..195c6da9 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -3,6 +3,8 @@ use crate::{ encodings::{Base64Data, MillisecondTimestamp}, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. @@ -20,6 +22,13 @@ pub struct KinesisFirehoseEvent { #[serde(default)] pub region: Option, pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -31,12 +40,26 @@ pub struct KinesisFirehoseEventRecord { pub data: Base64Data, #[serde(rename = "kinesisRecordMetadata")] pub kinesis_firehose_record_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -49,6 +72,13 @@ pub struct KinesisFirehoseResponseRecord { pub result: Option, pub data: Base64Data, pub metadata: KinesisFirehoseResponseRecordMetadata, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -57,6 +87,13 @@ pub struct KinesisFirehoseResponseRecordMetadata { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub partition_keys: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -70,6 +107,13 @@ pub struct KinesisFirehoseRecordMetadata { pub sequence_number: Option, pub subsequence_number: i64, pub approximate_arrival_timestamp: MillisecondTimestamp, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 36f59c7b..f4301b1e 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::{borrow::Cow, collections::HashMap, fmt}; use serde::{ @@ -12,6 +14,13 @@ pub struct IamPolicyDocument { #[serde(default)] pub version: Option, pub statement: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource @@ -27,6 +36,13 @@ pub struct IamPolicyStatement { #[serde(default, deserialize_with = "deserialize_policy_condition")] #[serde(skip_serializing_if = "Option::is_none")] pub condition: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type IamPolicyCondition = HashMap>>; @@ -178,6 +194,8 @@ mod tests { effect: IamPolicyEffect::Allow, resource: vec!["some:resource".into()], condition: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; let policy_ser = serde_json::to_value(policy).unwrap(); diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 07352120..cb262bd0 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,6 +1,8 @@ use crate::{custom_serde::serialize_headers, encodings::Base64Data, iam::IamPolicyDocument}; use http::HeaderMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See @@ -13,6 +15,13 @@ pub struct IoTCoreCustomAuthorizerRequest { pub protocols: Vec, pub protocol_data: Option, pub connection_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -21,6 +30,13 @@ pub struct IoTCoreProtocolData { pub tls: Option, pub http: Option, pub mqtt: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +44,13 @@ pub struct IoTCoreProtocolData { pub struct IoTCoreTlsContext { #[serde(default)] pub server_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -38,6 +61,13 @@ pub struct IoTCoreHttpContext { pub headers: HeaderMap, #[serde(default)] pub query_string: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -48,6 +78,13 @@ pub struct IoTCoreMqttContext { pub password: Base64Data, #[serde(default)] pub username: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -55,6 +92,13 @@ pub struct IoTCoreMqttContext { pub struct IoTCoreConnectionMetadata { #[serde(default)] pub id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. @@ -68,6 +112,13 @@ pub struct IoTCoreCustomAuthorizerResponse { pub disconnect_after_in_seconds: u32, pub refresh_after_in_seconds: u32, pub policy_documents: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index bf010b50..866918a9 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -11,12 +13,26 @@ pub struct IoTOneClickEvent { pub device_event: IoTOneClickDeviceEvent, pub device_info: IoTOneClickDeviceInfo, pub placement_info: IoTOneClickPlacementInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { pub button_clicked: IoTOneClickButtonClicked, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -26,6 +42,13 @@ pub struct IoTOneClickButtonClicked { pub click_type: Option, #[serde(default)] pub reported_time: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -39,6 +62,13 @@ pub struct IoTOneClickDeviceInfo { #[serde(default)] pub device_id: Option, pub remaining_life: f64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -54,6 +84,13 @@ pub struct IoTOneClickPlacementInfo { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub devices: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 2d2e4627..00e4f6af 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct IoTButtonEvent { pub click_type: Option, #[serde(default)] pub battery_voltage: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 12c1df99..30475675 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -1,5 +1,7 @@ use crate::iot::*; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema @@ -14,6 +16,13 @@ pub struct IoTCustomAuthorizerRequest { pub authorization_token: Option, #[serde(default)] pub token_signature: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type IoTHttpContext = IoTCoreHttpContext; @@ -33,4 +42,11 @@ pub struct IoTCustomAuthorizerResponse { pub disconnect_after_in_seconds: i32, pub refresh_after_in_seconds: i32, pub policy_documents: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 27a1e921..032f1615 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -1,5 +1,7 @@ use crate::{custom_serde::deserialize_lambda_map, encodings::MillisecondTimestamp}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -14,6 +16,13 @@ pub struct KafkaEvent { pub records: HashMap>, #[serde(default)] pub bootstrap_servers: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -29,6 +38,13 @@ pub struct KafkaRecord { pub key: Option, pub value: Option, pub headers: Vec>>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index 74c95606..e9f8f676 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -1,5 +1,7 @@ use crate::encodings::Base64Data; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct KinesisAnalyticsOutputDeliveryEvent { #[serde(default)] pub application_arn: Option, pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -17,12 +26,26 @@ pub struct KinesisAnalyticsOutputDeliveryEventRecord { #[serde(default)] pub record_id: Option, pub data: Base64Data, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponse { pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,4 +56,11 @@ pub struct KinesisAnalyticsOutputDeliveryResponseRecord { /// possible values include Ok and DeliveryFailed #[serde(default)] pub result: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index a67da850..6b9c1c77 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -3,12 +3,21 @@ use crate::{ time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows @@ -22,6 +31,13 @@ pub struct KinesisTimeWindowEvent { #[serde(rename = "TimeWindowProperties")] #[serde(flatten)] pub time_window_properties: TimeWindowProperties, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. @@ -32,6 +48,13 @@ pub struct KinesisTimeWindowEventResponse { #[serde(flatten)] pub time_window_event_response_properties: TimeWindowEventResponseProperties, // pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +80,13 @@ pub struct KinesisEventRecord { #[serde(default)] pub invoke_identity_arn: Option, pub kinesis: KinesisRecord, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -72,6 +102,13 @@ pub struct KinesisRecord { pub sequence_number: String, #[serde(default)] pub kinesis_schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index 37ddfe39..a754af0d 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -1,5 +1,7 @@ use http::HeaderMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; @@ -25,6 +27,13 @@ pub struct LambdaFunctionUrlRequest { pub request_context: LambdaFunctionUrlRequestContext, pub body: Option, pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. @@ -50,6 +59,13 @@ pub struct LambdaFunctionUrlRequestContext { pub time: Option, pub time_epoch: i64, pub http: LambdaFunctionUrlRequestContextHttpDescription, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. @@ -57,6 +73,13 @@ pub struct LambdaFunctionUrlRequestContext { #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { pub iam: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. @@ -73,6 +96,13 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { pub user_arn: Option, #[serde(default)] pub user_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. @@ -89,6 +119,13 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { pub source_ip: Option, #[serde(default)] pub user_agent: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. @@ -103,4 +140,11 @@ pub struct LambdaFunctionUrlResponse { pub body: Option, pub is_base64_encoded: bool, pub cookies: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index d8f9403c..c9c23c53 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -20,6 +22,13 @@ pub struct LexEvent { pub alternative_intents: Option>, /// Deprecated: the DialogAction field is never populated by Lex events pub dialog_action: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +37,13 @@ pub struct LexBot { pub name: Option, pub alias: Option, pub version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -40,6 +56,13 @@ pub struct LexCurrentIntent { #[serde(default)] pub slot_details: HashMap, pub confirmation_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -52,6 +75,13 @@ pub struct LexAlternativeIntents { #[serde(default)] pub slot_details: HashMap, pub confirmation_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -59,6 +89,13 @@ pub struct LexAlternativeIntents { pub struct SlotDetail { pub resolutions: Option>>, pub original_value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -73,6 +110,13 @@ pub struct LexDialogAction { pub slots: Option, pub slot_to_elicit: Option, pub response_card: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type SessionAttributes = HashMap; @@ -84,6 +128,13 @@ pub type Slots = HashMap>; pub struct LexResponse { pub session_attributes: SessionAttributes, pub dialog_action: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -92,6 +143,13 @@ pub struct LexResponseCard { pub version: Option, pub content_type: Option, pub generic_attachments: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -102,6 +160,13 @@ pub struct Attachment { pub image_url: Option, pub attachment_link_url: Option, pub buttons: Option>>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 23327276..fd9088c5 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -15,6 +15,13 @@ pub struct RabbitMqEvent { #[serde(default)] #[serde(rename = "rmqMessagesByQueue")] pub messages_by_queue: HashMap>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -24,6 +31,13 @@ pub struct RabbitMqMessage { #[serde(default)] pub data: Option, pub redelivered: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -56,6 +70,13 @@ where pub app_id: Option, pub cluster_id: Option, pub body_size: u64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index e3eb691e..133960f3 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -10,6 +12,13 @@ pub struct S3BatchJobEvent { pub invocation_id: Option, pub job: S3BatchJob, pub tasks: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJob` whichs have the job id @@ -18,6 +27,13 @@ pub struct S3BatchJobEvent { pub struct S3BatchJob { #[serde(default)] pub id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobTask` represents one task in the s3 batch job and have all task details @@ -32,6 +48,13 @@ pub struct S3BatchJobTask { pub s3_version_id: Option, #[serde(default)] pub s3_bucket_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobResponse` is the response of a iven s3 batch job with the results @@ -45,6 +68,13 @@ pub struct S3BatchJobResponse { #[serde(default)] pub invocation_id: Option, pub results: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobResult` represents the result of a given task @@ -57,4 +87,11 @@ pub struct S3BatchJobResult { pub result_code: Option, #[serde(default)] pub result_string: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index e062c7d2..46a334db 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -10,6 +12,13 @@ use crate::custom_serde::deserialize_lambda_map; pub struct S3Event { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3EventRecord` which wrap record data @@ -32,6 +41,13 @@ pub struct S3EventRecord { #[serde(default)] pub response_elements: HashMap, pub s3: S3Entity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -39,6 +55,13 @@ pub struct S3EventRecord { pub struct S3UserIdentity { #[serde(default)] pub principal_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -47,6 +70,13 @@ pub struct S3RequestParameters { #[serde(default)] #[serde(rename = "sourceIPAddress")] pub source_ip_address: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -59,6 +89,13 @@ pub struct S3Entity { pub configuration_id: Option, pub bucket: S3Bucket, pub object: S3Object, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -70,6 +107,13 @@ pub struct S3Bucket { pub owner_identity: Option, #[serde(default)] pub arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -86,6 +130,13 @@ pub struct S3Object { pub e_tag: Option, #[serde(default)] pub sequencer: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 1cd7b934..3b01fe73 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -24,6 +24,13 @@ where pub user_request: UserRequest, pub user_identity: UserIdentity, pub protocol_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `GetObjectContext` contains the input and output details @@ -34,6 +41,13 @@ pub struct GetObjectContext { pub input_s3_url: String, pub output_route: String, pub output_token: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `HeadObjectContext` @@ -42,6 +56,13 @@ pub struct GetObjectContext { #[serde(rename_all = "camelCase")] pub struct HeadObjectContext { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ListObjectsContext` @@ -50,6 +71,13 @@ pub struct HeadObjectContext { #[serde(rename_all = "camelCase")] pub struct ListObjectsContext { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ListObjectsV2Context` @@ -58,6 +86,13 @@ pub struct ListObjectsContext { #[serde(rename_all = "camelCase")] pub struct ListObjectsV2Context { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `Configuration` contains information about the Object Lambda access point @@ -72,6 +107,13 @@ where pub supporting_access_point_arn: String, #[serde(default, bound = "")] pub payload: P, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `UserRequest` contains information about the original call to S3 Object Lambda @@ -82,6 +124,13 @@ pub struct UserRequest { #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] pub headers: HeaderMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda @@ -94,6 +143,13 @@ pub struct UserIdentity { pub account_id: String, pub access_key_id: String, pub session_context: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -102,6 +158,13 @@ pub struct SessionContext { pub attributes: HashMap, #[serde(default)] pub session_issuer: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -112,6 +175,13 @@ pub struct SessionIssuer { pub arn: String, pub account_id: String, pub user_name: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index 3ed8d238..2c5b497c 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] @@ -6,6 +8,13 @@ pub struct SecretsManagerSecretRotationEvent { pub step: String, pub secret_id: String, pub client_request_token: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 2a60957a..78262b0b 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -7,6 +9,13 @@ use serde::{Deserialize, Serialize}; pub struct SimpleEmailEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -17,6 +26,13 @@ pub struct SimpleEmailRecord { #[serde(default)] pub event_source: Option, pub ses: SimpleEmailService, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -25,6 +41,13 @@ pub struct SimpleEmailService { pub mail: SimpleEmailMessage, pub receipt: SimpleEmailReceipt, pub content: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -39,6 +62,13 @@ pub struct SimpleEmailMessage { pub headers_truncated: bool, #[serde(default)] pub message_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -55,6 +85,13 @@ pub struct SimpleEmailReceipt { pub virus_verdict: SimpleEmailVerdict, pub action: SimpleEmailReceiptAction, pub processing_time_millis: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -64,6 +101,13 @@ pub struct SimpleEmailHeader { pub name: Option, #[serde(default)] pub value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -79,6 +123,13 @@ pub struct SimpleEmailCommonHeaders { pub date: Option, #[serde(default)] pub subject: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SimpleEmailReceiptAction` is a logical union of fields present in all action @@ -100,6 +151,13 @@ pub struct SimpleEmailReceiptAction { pub invocation_type: Option, pub function_arn: Option, pub organization_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -107,6 +165,13 @@ pub struct SimpleEmailReceiptAction { pub struct SimpleEmailVerdict { #[serde(default)] pub status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type SimpleEmailDispositionValue = String; @@ -116,6 +181,13 @@ pub type SimpleEmailDispositionValue = String; #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { pub disposition: SimpleEmailDispositionValue, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 0fda569d..f68ec984 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; @@ -11,6 +13,14 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; #[serde(rename_all = "PascalCase")] pub struct SnsEvent { pub records: Vec, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// SnsRecord stores information about each record of a SNS event @@ -28,6 +38,14 @@ pub struct SnsRecord { /// An SNS object representing the SNS message. pub sns: SnsMessage, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// SnsMessage stores information about each record of a SNS event @@ -76,6 +94,14 @@ pub struct SnsMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An alternate `Event` notification event to use alongside `SnsRecordObj` and `SnsMessageObj` if you want to deserialize an object inside your SNS messages rather than getting an `Option` message @@ -86,6 +112,14 @@ pub struct SnsMessage { #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsEventObj { pub records: Vec>, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) @@ -104,6 +138,14 @@ pub struct SnsRecordObj { /// An SNS object representing the SNS message. pub sns: SnsMessageObj, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` @@ -156,6 +198,14 @@ pub struct SnsMessageObj { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Structured metadata items (such as timestamps, geospatial data, signatures, and identifiers) about the message. @@ -172,6 +222,14 @@ pub struct MessageAttribute { /// The user-specified message attribute value. #[serde(rename = "Value")] pub value: String, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -188,6 +246,13 @@ pub struct CloudWatchAlarmPayload { pub alarm_arn: String, pub old_state_value: String, pub trigger: CloudWatchAlarmTrigger, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -208,6 +273,13 @@ pub struct CloudWatchAlarmTrigger { pub unit: Option, #[serde(default)] pub dimensions: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -220,6 +292,13 @@ pub struct CloudWatchMetricDataQuery { pub period: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub return_data: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -229,6 +308,13 @@ pub struct CloudWatchMetricStat { pub period: i64, pub stat: String, pub unit: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -238,12 +324,26 @@ pub struct CloudWatchMetric { pub dimensions: Vec, pub metric_name: Option, pub namespace: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CloudWatchDimension { pub name: String, pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 563dda1a..64a368ca 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,5 +1,7 @@ use crate::{custom_serde::deserialize_lambda_map, encodings::Base64Data}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages @@ -8,6 +10,13 @@ use std::collections::HashMap; pub struct SqsEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An individual SQS Message, its metadata, and Message Attributes @@ -38,6 +47,13 @@ pub struct SqsMessage { pub event_source: Option, #[serde(default)] pub aws_region: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SqsEvent` to be used alongside `SqsMessageObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string @@ -48,6 +64,13 @@ pub struct SqsEventObj { #[serde(rename = "Records")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub records: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SqsMessage` to be used alongside `SqsEventObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string @@ -83,6 +106,13 @@ pub struct SqsMessageObj { pub event_source: Option, #[serde(default)] pub aws_region: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -96,18 +126,39 @@ pub struct SqsMessageAttribute { pub binary_list_values: Vec, #[serde(default)] pub data_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BatchItemFailure { pub item_identifier: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages @@ -117,6 +168,13 @@ pub struct BatchItemFailure { pub struct SqsApiEventObj { #[serde(bound(deserialize = "T: DeserializeOwned"))] pub messages: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages @@ -124,6 +182,13 @@ pub struct SqsApiEventObj { #[serde(rename_all = "camelCase")] pub struct SqsApiEvent { pub messages: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to @@ -153,6 +218,13 @@ pub struct SqsApiMessageObj { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An individual SQS API Message, its metadata, and Message Attributes @@ -176,6 +248,13 @@ pub struct SqsApiMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 9e0fd76f..673217fc 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -1,10 +1,19 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisBatchItemFailure` is the individual record which failed processing. @@ -13,6 +22,13 @@ pub struct KinesisEventResponse { pub struct KinesisBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. @@ -20,6 +36,13 @@ pub struct KinesisBatchItemFailure { #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. @@ -28,6 +51,13 @@ pub struct DynamoDbEventResponse { pub struct DynamoDbBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. @@ -35,6 +65,13 @@ pub struct DynamoDbBatchItemFailure { #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SqsBatchItemFailure` is the individual record which failed processing. @@ -43,4 +80,11 @@ pub struct SqsEventResponse { pub struct SqsBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/fixtures/example-apigw-request-catch-all.json b/lambda-events/src/fixtures/example-apigw-request-catch-all.json new file mode 100644 index 00000000..fe1955f4 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request-catch-all.json @@ -0,0 +1,137 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": [ + "me" + ] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser", + "otherField": 2345 + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "otherField": "foobar" +} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4498e275..6788f285 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -23,6 +23,7 @@ apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] +catch-all-fields = ["aws_lambda_events/catch-all-fields"] tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities opentelemetry = ["lambda_runtime/opentelemetry"] # enables access to the OpenTelemetry layers and utilities anyhow = ["lambda_runtime/anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info @@ -49,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.16.0" +version = "0.17.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index fa8953f2..8b868d25 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -77,6 +77,9 @@ impl LambdaResponse { // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { @@ -101,6 +104,9 @@ impl LambdaResponse { // are combined with commas and included in the headers field. headers, multi_value_headers: HeaderMap::new(), + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }) } #[cfg(feature = "alb")] @@ -118,6 +124,9 @@ impl LambdaResponse { status_code, parts.status.canonical_reason().unwrap_or_default() )), + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "apigw_websockets")] RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { @@ -128,6 +137,9 @@ impl LambdaResponse { // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "pass_through")] RequestOrigin::PassThrough => { diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 0a9836c4..85d5fca6 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -21,6 +21,9 @@ serde = { version = "1.0.204", features = ["derive"] } [dev-dependencies] reqwest = { version = "0.12.5", features = ["blocking"] } +[features] +catch-all-fields = ["aws_lambda_events/catch-all-fields"] + [[bin]] name = "helloworld" path = "src/helloworld.rs" diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs index 41ddd2d8..b8dc3782 100644 --- a/lambda-integration-tests/src/authorizer.rs +++ b/lambda-integration-tests/src/authorizer.rs @@ -39,15 +39,21 @@ fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { resource: vec![method_arn.to_owned()], effect: aws_lambda_events::iam::IamPolicyEffect::Allow, condition: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; let policy = ApiGatewayCustomAuthorizerPolicy { version: Some("2012-10-17".to_string()), statement: vec![stmt], + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; ApiGatewayCustomAuthorizerResponse { principal_id: Some("user".to_owned()), policy_document: policy, context: json!({ "hello": "world" }), usage_identifier_key: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), } } diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index b40cd0c6..c3a74f8c 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -22,6 +22,8 @@ async fn func(_event: LambdaEvent) -> Result Date: Thu, 17 Jul 2025 17:17:11 -0700 Subject: [PATCH 380/394] chore(lambda-extension, lambda-http, lambda-runtime-api-client, lambda-runtime): bump patch versions for new release (#1020) --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index d178520a..515a97fe 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.12.1" +version = "0.12.2" edition = "2021" rust-version = "1.81.0" authors = [ @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.12", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 6788f285..9787f302 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.1" +version = "0.15.2" authors = [ "David Calavera ", "Harold Sun ", @@ -38,7 +38,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.14.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.3", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -57,7 +57,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.5.0" axum-extra = { version = "0.10.0", features = ["query"] } -lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index cc2289af..dcc2916d 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.2" +version = "0.12.3" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6e0dde73..3daefc11 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.2" +version = "0.14.3" authors = [ "David Calavera ", "Harold Sun ", @@ -37,8 +37,8 @@ http = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } hyper = { workspace = true, features = ["http1", "client"] } -lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.2", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 1447217b54a4cecf567ba6feb8a83bdd8e355d17 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:17:50 -0700 Subject: [PATCH 381/394] chore: (lambda-http, lambda-extension): add missing docsrs feature annotations (#1021) --- lambda-extension/src/lib.rs | 1 + lambda-http/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index a27635b8..b6aec18f 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -26,6 +26,7 @@ pub mod requests; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub use lambda_runtime_api_client::tracing; /// Execute the given events processor diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index cea99750..33ccea12 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -68,6 +68,7 @@ extern crate maplit; pub use http::{self, Response}; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[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}; From baf3653402930d74500dde0e8a2d2264407d3f4f Mon Sep 17 00:00:00 2001 From: Jonathan Lim Date: Tue, 22 Jul 2025 13:03:24 -0700 Subject: [PATCH 382/394] fix(lambda-events): derive Default on various Events (#1022) * fix(lambda-events): derive Default on various events * fix(lambda-events): format new code --- lambda-events/src/encodings/time.rs | 4 ++-- lambda-events/src/event/activemq/mod.rs | 6 ++--- lambda-events/src/event/autoscaling/mod.rs | 2 +- lambda-events/src/event/chime_bot/mod.rs | 8 +++---- lambda-events/src/event/cloudformation/mod.rs | 13 ++++++++--- .../src/event/cloudformation/provider.rs | 12 +++++++--- .../src/event/cloudwatch_events/mod.rs | 2 +- lambda-events/src/event/code_commit/mod.rs | 2 +- lambda-events/src/event/codebuild/mod.rs | 14 ++++++------ lambda-events/src/event/codedeploy/mod.rs | 6 ++--- .../src/event/codepipeline_cloudwatch/mod.rs | 6 ++--- .../src/event/codepipeline_job/mod.rs | 22 +++++++++---------- lambda-events/src/event/config/mod.rs | 2 +- lambda-events/src/event/connect/mod.rs | 10 ++++----- lambda-events/src/event/dynamodb/mod.rs | 6 ++--- lambda-events/src/event/ecr_scan/mod.rs | 6 ++--- lambda-events/src/event/firehose/mod.rs | 4 ++-- lambda-events/src/event/iot_1_click/mod.rs | 10 ++++----- lambda-events/src/event/iot_button/mod.rs | 2 +- lambda-events/src/event/kafka/mod.rs | 4 ++-- lambda-events/src/event/kinesis/event.rs | 2 +- lambda-events/src/event/lex/mod.rs | 10 ++++----- lambda-events/src/event/rabbitmq/mod.rs | 2 +- lambda-events/src/event/secretsmanager/mod.rs | 2 +- lambda-events/src/event/ses/mod.rs | 2 +- lambda-events/src/event/sns/mod.rs | 6 ++--- 26 files changed, 89 insertions(+), 76 deletions(-) diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index d0d9526e..c7ca04a6 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -7,7 +7,7 @@ use serde::{ use std::ops::{Deref, DerefMut}; /// Timestamp with millisecond precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MillisecondTimestamp( #[serde(deserialize_with = "deserialize_milliseconds")] #[serde(serialize_with = "serialize_milliseconds")] @@ -73,7 +73,7 @@ impl DerefMut for SecondDuration { } /// Duration with minute precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] #[serde(serialize_with = "serialize_duration_minutes")] diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index d9283bea..4bced0ab 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { #[serde(default)] @@ -22,7 +22,7 @@ pub struct ActiveMqEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqMessage { #[serde(default)] @@ -59,7 +59,7 @@ pub struct ActiveMqMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqDestination { #[serde(default)] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 1aeea5a2..cbcde746 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingEvent where diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index d4bcd5f6..42b9ef0e 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEvent { #[serde(rename = "Sender")] @@ -28,7 +28,7 @@ pub struct ChimeBotEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventSender { #[serde(default)] @@ -46,7 +46,7 @@ pub struct ChimeBotEventSender { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventDiscussion { #[serde(default)] @@ -64,7 +64,7 @@ pub struct ChimeBotEventDiscussion { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventInboundHttpsEndpoint { #[serde(default)] diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 4134c144..995ab846 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -19,7 +19,13 @@ where Delete(DeleteRequest), } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +impl Default for CloudFormationCustomResourceRequest { + fn default() -> Self { + CloudFormationCustomResourceRequest::Create(CreateRequest::default()) + } +} + +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest where @@ -99,7 +105,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse { pub status: CloudFormationCustomResourceResponseStatus, @@ -119,9 +125,10 @@ pub struct CloudFormationCustomResourceResponse { pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudFormationCustomResourceResponseStatus { + #[default] Success, Failed, } diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index 786d6075..71277388 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -22,7 +22,13 @@ where Delete(DeleteRequest), } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +impl Default for CloudFormationCustomResourceRequest { + fn default() -> Self { + CloudFormationCustomResourceRequest::Create(CreateRequest::default()) + } +} + +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest where @@ -63,7 +69,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CommonRequestParams where @@ -84,7 +90,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse where diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index fd680c01..c865b0e0 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -21,7 +21,7 @@ pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. /// For examples of events that come via CloudWatch Events, see -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent where diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 81d85ef8..e35ab093 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -6,7 +6,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; /// `CodeCommitEvent` represents a CodeCommit event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index ac0a50de..4ffb821d 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -12,7 +12,7 @@ pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: /// -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { /// AccountID is the id of the AWS account from which the event originated. @@ -54,7 +54,7 @@ pub struct CodeBuildEvent { } /// `CodeBuildEventDetail` represents the all details related to the code build event -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventDetail { #[serde(rename = "build-status")] @@ -101,7 +101,7 @@ pub struct CodeBuildEventDetail { } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventAdditionalInformation { pub artifact: CodeBuildArtifact, @@ -133,7 +133,7 @@ pub struct CodeBuildEventAdditionalInformation { } /// `CodeBuildArtifact` represents the artifact provided to build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildArtifact { #[serde(default)] @@ -154,7 +154,7 @@ pub struct CodeBuildArtifact { } /// `CodeBuildEnvironment` represents the environment for a build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironment { #[serde(default)] @@ -200,7 +200,7 @@ pub struct CodeBuildEnvironmentVariable { } /// `CodeBuildSource` represent the code source will be build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildSource { #[serde(default)] @@ -217,7 +217,7 @@ pub struct CodeBuildSource { } /// `CodeBuildLogs` gives the log details of a code build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildLogs { #[serde(default)] diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index ec6657b6..85649729 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -7,7 +7,7 @@ pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: /// -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { /// AccountID is the id of the AWS account from which the event originated. @@ -49,7 +49,7 @@ pub struct CodeDeployEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEventDetail { /// InstanceGroupID is the ID of the instance group. @@ -81,7 +81,7 @@ pub struct CodeDeployEventDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct CodeDeployLifecycleEvent { pub deployment_id: String, diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 1863e8aa..3bcc5f2b 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -11,7 +11,7 @@ pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: /// -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { /// Version is the version of the event's schema. @@ -53,7 +53,7 @@ pub struct CodePipelineCloudWatchEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetail { #[serde(default)] @@ -80,7 +80,7 @@ pub struct CodePipelineEventDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetailType { #[serde(default)] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index befb4c4c..41a9966e 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJobEvent { #[serde(rename = "CodePipeline.job")] @@ -18,7 +18,7 @@ pub struct CodePipelineJobEvent { } /// `CodePipelineJob` represents a job from an AWS CodePipeline event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJob { #[serde(default)] @@ -36,7 +36,7 @@ pub struct CodePipelineJob { } /// `CodePipelineData` represents a job from an AWS CodePipeline event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineData { pub action_configuration: CodePipelineActionConfiguration, @@ -56,7 +56,7 @@ pub struct CodePipelineData { } /// `CodePipelineActionConfiguration` represents an Action Configuration -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { pub configuration: CodePipelineConfiguration, @@ -70,7 +70,7 @@ pub struct CodePipelineActionConfiguration { } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineConfiguration { #[serde(default)] @@ -89,7 +89,7 @@ pub struct CodePipelineConfiguration { } /// `CodePipelineInputArtifact` represents an input artifact -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputArtifact { pub location: CodePipelineInputLocation, @@ -106,7 +106,7 @@ pub struct CodePipelineInputArtifact { } /// `CodePipelineInputLocation` represents a input location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputLocation { pub s3_location: CodePipelineS3Location, @@ -123,7 +123,7 @@ pub struct CodePipelineInputLocation { } /// `CodePipelineS3Location` represents an s3 input location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineS3Location { #[serde(default)] @@ -140,7 +140,7 @@ pub struct CodePipelineS3Location { } /// `CodePipelineOutputArtifact` represents an output artifact -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputArtifact { pub location: CodePipelineInputLocation, @@ -157,7 +157,7 @@ pub struct CodePipelineOutputArtifact { } /// `CodePipelineOutputLocation` represents a output location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputLocation { pub s3_location: CodePipelineS3Location, @@ -174,7 +174,7 @@ pub struct CodePipelineOutputLocation { } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineArtifactCredentials { #[serde(default)] diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index fd2631a6..981419d8 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigEvent { /// The ID of the AWS account that owns the rule diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index c6bdb47b..3f15ce0c 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `ConnectEvent` contains the data structure for a Connect event. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEvent { #[serde(rename = "Details")] @@ -25,7 +25,7 @@ pub struct ConnectEvent { } /// `ConnectDetails` holds the details of a Connect event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectDetails { #[serde(rename = "ContactData")] @@ -45,7 +45,7 @@ pub struct ConnectDetails { } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectContactData { /// The custom attributes from Connect that the Lambda function was invoked with. @@ -88,7 +88,7 @@ pub struct ConnectContactData { } /// `ConnectEndpoint` represents routing information. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEndpoint { #[serde(default)] @@ -107,7 +107,7 @@ pub struct ConnectEndpoint { } /// `ConnectQueue` represents a queue object. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectQueue { #[serde(default)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2a99ed69..3e1b3ab3 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -113,7 +113,7 @@ impl fmt::Display for KeyType { /// The `Event` stream event handled to Lambda /// -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] pub records: Vec, @@ -164,7 +164,7 @@ pub struct TimeWindowEventResponse { } /// EventRecord stores information about each record of a DynamoDb stream event -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventRecord { /// The region in which the GetRecords request was received. @@ -248,7 +248,7 @@ pub struct UserIdentity { /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item /// in a DynamoDB table. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index f68b4e57..e3ff7ff8 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { #[serde(default)] @@ -31,7 +31,7 @@ pub struct EcrScanEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventDetailType { #[serde(default)] @@ -56,7 +56,7 @@ pub struct EcrScanEventDetailType { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { #[serde(default)] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 195c6da9..8bce49ac 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -8,7 +8,7 @@ use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEvent { #[serde(default)] @@ -31,7 +31,7 @@ pub struct KinesisFirehoseEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEventRecord { #[serde(default)] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 866918a9..50338120 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -7,7 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickEvent { pub device_event: IoTOneClickDeviceEvent, @@ -22,7 +22,7 @@ pub struct IoTOneClickEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { pub button_clicked: IoTOneClickButtonClicked, @@ -35,7 +35,7 @@ pub struct IoTOneClickDeviceEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickButtonClicked { #[serde(default)] @@ -51,7 +51,7 @@ pub struct IoTOneClickButtonClicked { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceInfo { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -71,7 +71,7 @@ pub struct IoTOneClickDeviceInfo { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickPlacementInfo { #[serde(default)] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 00e4f6af..9a7aaec3 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { #[serde(default)] diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 032f1615..7332b1e0 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaEvent { #[serde(default)] @@ -25,7 +25,7 @@ pub struct KafkaEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaRecord { #[serde(default)] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 6b9c1c77..6bfb2bea 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index c9c23c53..6a458c8a 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { pub message_version: Option, @@ -46,7 +46,7 @@ pub struct LexBot { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexCurrentIntent { pub name: Option, @@ -65,7 +65,7 @@ pub struct LexCurrentIntent { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexAlternativeIntents { pub name: Option, @@ -98,7 +98,7 @@ pub struct SlotDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexDialogAction { pub type_: Option, @@ -137,7 +137,7 @@ pub struct LexResponse { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponseCard { pub version: Option, diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index fd9088c5..6c79e2b0 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { #[serde(default)] diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index 2c5b497c..fc883e52 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SecretsManagerSecretRotationEvent { pub step: String, diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 78262b0b..9358135d 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index f68ec984..611a16b7 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -9,7 +9,7 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsEvent { pub records: Vec, @@ -24,7 +24,7 @@ pub struct SnsEvent { } /// SnsRecord stores information about each record of a SNS event -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsRecord { /// A string containing the event source. @@ -49,7 +49,7 @@ pub struct SnsRecord { } /// SnsMessage stores information about each record of a SNS event -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsMessage { /// The type of SNS message. For a lambda event, this should always be **Notification** From aff8d883c62997ef2615714dce9f7ddfd557147d Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:13:04 -0700 Subject: [PATCH 383/394] fix(semver): bump lamda-http to `0.16.0` due to breaking change in dependency, `aws_lambda_events` (#1025) --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9787f302..b930afdb 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.2" +version = "0.16.0" authors = [ "David Calavera ", "Harold Sun ", From 685e81c5d4d0c11f3f816c8f540b196c27173210 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:18:31 -0700 Subject: [PATCH 384/394] chore(ci): unpin cargo lambda in integration test ci (#1026) --- .github/workflows/run-integration-test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 03065d9e..a4fd604b 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,8 +18,6 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 - # TODO: unpin once https://github.com/awslabs/aws-lambda-rust-runtime/issues/1006 is fixed - tag: v1.8.1 - name: install Zig toolchain uses: mlugg/setup-zig@v2 with: From 42e2c1f656e9a4fd5d438b04bcbcbd95c9cf4d48 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Tue, 19 Aug 2025 09:47:38 +0200 Subject: [PATCH 385/394] Fix CI checks (#1030) * remove unused * update link --- lambda-runtime-api-client/src/tracing.rs | 2 +- lambda-runtime/src/requests.rs | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 097e8dcf..5aa9bfa1 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -44,7 +44,7 @@ pub fn init_default_subscriber() { /// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). /// In that case, you might prefer to use [tracing_appender::NonBlocking](https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.NonBlocking.html) instead - particularly if your Lambda is fairly long-running and stays warm. /// Though, note that you are then responsible -/// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. +/// for ensuring gracefuls shutdown. See [aws-samples/graceful-shutdown-with-aws-lambda](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda) for a complete example. /// /// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index e8b0183c..ec1e6ae1 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -24,20 +24,6 @@ impl IntoRequest for NextEventRequest { } } -#[derive(Debug, Eq, PartialEq)] -pub struct NextEventResponse<'a> { - // lambda-runtime-aws-request-id - pub request_id: &'a str, - // lambda-runtime-deadline-ms - pub deadline: u64, - // lambda-runtime-invoked-function-arn - pub arn: &'a str, - // lambda-runtime-trace-id - pub trace_id: &'a str, - // the actual body, - pub body: Vec, -} - // /runtime/invocation/{AwsRequestId}/response pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> where From 92a80b49958b0a8d2d14223279b08d0b3b9d3147 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Thu, 21 Aug 2025 06:53:40 +0200 Subject: [PATCH 386/394] Expose streaming API (#1013) * Expose streaming API --- examples/http-axum-streaming-otel/Cargo.toml | 20 ++ examples/http-axum-streaming-otel/README.md | 25 +++ examples/http-axum-streaming-otel/src/main.rs | 106 +++++++++ examples/http-axum-streaming/Cargo.toml | 14 ++ examples/http-axum-streaming/README.md | 20 ++ examples/http-axum-streaming/src/main.rs | 70 ++++++ lambda-http/src/lib.rs | 2 +- lambda-http/src/streaming.rs | 201 +++++++++++++++--- 8 files changed, 422 insertions(+), 36 deletions(-) create mode 100644 examples/http-axum-streaming-otel/Cargo.toml create mode 100644 examples/http-axum-streaming-otel/README.md create mode 100644 examples/http-axum-streaming-otel/src/main.rs create mode 100644 examples/http-axum-streaming/Cargo.toml create mode 100644 examples/http-axum-streaming/README.md create mode 100644 examples/http-axum-streaming/src/main.rs diff --git a/examples/http-axum-streaming-otel/Cargo.toml b/examples/http-axum-streaming-otel/Cargo.toml new file mode 100644 index 00000000..d917bb03 --- /dev/null +++ b/examples/http-axum-streaming-otel/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-axum-streaming-otel" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +bytes = "1" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_http", "tracing", "opentelemetry" +] } +opentelemetry = "0.30" +opentelemetry_sdk = { version = "0.30", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.30", features = ["trace"] } +thiserror = "2.0" +tokio = { version = "1", features = ["macros"] } +tokio-stream = "0.1.2" +tracing = "0.1" +tracing-opentelemetry = "0.31" +tracing-subscriber = "0.3" diff --git a/examples/http-axum-streaming-otel/README.md b/examples/http-axum-streaming-otel/README.md new file mode 100644 index 00000000..194fe4e4 --- /dev/null +++ b/examples/http-axum-streaming-otel/README.md @@ -0,0 +1,25 @@ +# AWS Lambda Function example + +This example shows how to build a **streaming HTTP response** with `Axum` and +run it on AWS Lambda using a custom runtime with OpenTelemetry (OTel) support. + +Tracing data is exported as console log entries visible in CloudWatch. Note that +CloudWatch assigns a `Timestamp` to each log entry based on when it receives the +data (batch exported). To see when work actually occurred, look at the span's +event attributes, which include the precise local timestamps of those events. + +## Build & Deploy + +1. Install + [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with: + - `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words +4. Enable Lambda streaming response on Lambda console: change the function url's + invoke mode to `RESPONSE_STREAM` +5. Verify the function works: `curl -N `. The results should be + streamed back with 0.5 second pause between each word. + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-streaming-otel/src/main.rs b/examples/http-axum-streaming-otel/src/main.rs new file mode 100644 index 00000000..64f4e49e --- /dev/null +++ b/examples/http-axum-streaming-otel/src/main.rs @@ -0,0 +1,106 @@ +//! # Example: Axum Streaming Responses on AWS Lambda with OTel +//! +//! Demonstrates serving **incremental streaming responses** from Axum handlers +//! running in AWS Lambda using a **custom** `lambda_runtime::Runtime` with +//! OpenTelemetry (OTel) support. +//! +//! - Runs with a custom `Runtime` + `StreamAdapter`, which convert Axum +//! responses into streaming bodies delivered as data is produced (unlike the +//! default `run_with_streaming_response` helper). + +use axum::{ + body::Body, + http::{ + self, + header::{CACHE_CONTROL, CONTENT_TYPE}, + StatusCode, + }, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use bytes::Bytes; +use core::{convert::Infallible, time::Duration}; +use lambda_http::{ + lambda_runtime::{ + layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + tracing::Instrument, + Runtime, + }, + tracing, Error, StreamAdapter, +}; +use opentelemetry::trace::TracerProvider; +use opentelemetry_sdk::trace; +use thiserror::Error; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tracing_subscriber::prelude::*; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("{0}")] + Http(#[from] http::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } +} + +#[tracing::instrument(skip_all)] +async fn stream_words() -> Result { + let (tx, rx) = mpsc::channel::>(8); + let body = Body::from_stream(ReceiverStream::new(rx)); + + tokio::spawn( + async move { + for (idx, msg) in ["Hello", "world", "from", "Lambda!"].iter().enumerate() { + tokio::time::sleep(Duration::from_millis(500)).await; + let line = format!("{msg}\n"); + tracing::info!(chunk.idx = idx, bytes = line.len(), "emit"); + if tx.send(Ok(Bytes::from(line))).await.is_err() { + break; + } + } + } + .instrument(tracing::info_span!("producer.stream_words")), + ); + + Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/plain; charset=utf-8") + .header(CACHE_CONTROL, "no-cache") + .body(body)?) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // Set up OpenTelemetry tracer provider that writes spans to stdout for + // debugging purposes + let exporter = opentelemetry_stdout::SpanExporter::default(); + let tracer_provider = trace::SdkTracerProvider::builder() + .with_batch_exporter(exporter) + .build(); + + // Set up link between OpenTelemetry and tracing crate + tracing_subscriber::registry() + .with(tracing_opentelemetry::OpenTelemetryLayer::new( + tracer_provider.tracer("my-streaming-app"), + )) + .init(); + + let svc = Router::new().route("/", get(stream_words)); + + // Initialize the Lambda runtime and add OpenTelemetry tracing + let runtime = Runtime::new(StreamAdapter::from(svc)).layer( + OtelLayer::new(|| { + if let Err(err) = tracer_provider.force_flush() { + eprintln!("Error flushing traces: {err:#?}"); + } + }) + .with_trigger(OpenTelemetryFaasTrigger::Http), + ); + + runtime.run().await +} diff --git a/examples/http-axum-streaming/Cargo.toml b/examples/http-axum-streaming/Cargo.toml new file mode 100644 index 00000000..a951562b --- /dev/null +++ b/examples/http-axum-streaming/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-axum-streaming" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +bytes = "1" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_http", "tracing" +] } +thiserror = "2.0" +tokio = { version = "1", features = ["macros"] } +tokio-stream = "0.1.2" diff --git a/examples/http-axum-streaming/README.md b/examples/http-axum-streaming/README.md new file mode 100644 index 00000000..fe7e573d --- /dev/null +++ b/examples/http-axum-streaming/README.md @@ -0,0 +1,20 @@ +# AWS Lambda Function example + +This example demonstrates building a **streaming** HTTP response with Axum, +deployed on AWS Lambda using a custom runtime. + +## Build & Deploy + +1. Install + [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with: + - `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words +4. Enable Lambda streaming response on Lambda console: change the function url's + invoke mode to `RESPONSE_STREAM` +5. Verify the function works: `curl -N `. The results should be + streamed back with 0.5 second pause between each word. + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-streaming/src/main.rs b/examples/http-axum-streaming/src/main.rs new file mode 100644 index 00000000..1812f879 --- /dev/null +++ b/examples/http-axum-streaming/src/main.rs @@ -0,0 +1,70 @@ +//! # Example: Axum Streaming Responses on AWS Lambda +//! +//! Demonstrates serving **incremental streaming responses** from Axum handlers +//! running in AWS Lambda. +//! +//! - Runs with `run_with_streaming_response`, which uses the **default Lambda +//! runtime** to convert Axum responses into streaming bodies delivered as +//! data is produced (unlike the OTel example, which used a custom `Runtime` + +//! `StreamAdapter`). + +use axum::{ + body::Body, + http::{ + self, + header::{CACHE_CONTROL, CONTENT_TYPE}, + StatusCode, + }, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use bytes::Bytes; +use core::{convert::Infallible, time::Duration}; +use lambda_http::{run_with_streaming_response, tracing, Error}; +use thiserror::Error; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("{0}")] + Http(#[from] http::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } +} + +async fn stream_words() -> Result { + let (tx, rx) = mpsc::channel::>(8); + let body = Body::from_stream(ReceiverStream::new(rx)); + + tokio::spawn(async move { + for msg in ["Hello", "world", "from", "Lambda!"] { + tokio::time::sleep(Duration::from_millis(500)).await; + if tx.send(Ok(Bytes::from(format!("{msg}\n")))).await.is_err() { + break; + } + } + }); + + Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/plain; charset=utf-8") + .header(CACHE_CONTROL, "no-cache") + .body(body)?) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + let svc = Router::new().route("/", get(stream_words)); + + // Automatically convert the service into a streaming response with a + // default runtime. + run_with_streaming_response(svc).await +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 33ccea12..36e2ffbd 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -102,7 +102,7 @@ use std::{ }; mod streaming; -pub use streaming::run_with_streaming_response; +pub use streaming::{run_with_streaming_response, StreamAdapter}; /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index a93408b4..6dd17230 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,22 +1,88 @@ -use crate::{http::header::SET_COOKIE, request::LambdaRequest, tower::ServiceBuilder, Request, RequestExt}; +use crate::{http::header::SET_COOKIE, request::LambdaRequest, Request, RequestExt}; use bytes::Bytes; -pub use http::{self, Response}; -use http_body::Body; -use lambda_runtime::Diagnostic; -pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::{ +use core::{ fmt::Debug, pin::Pin, task::{Context, Poll}, }; -use tokio_stream::Stream; +use futures_util::{Stream, TryFutureExt}; +pub use http::{self, Response}; +use http_body::Body; +use lambda_runtime::{ + tower::{ + util::{MapRequest, MapResponse}, + ServiceBuilder, ServiceExt, + }, + Diagnostic, +}; +pub use lambda_runtime::{Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; +use std::{future::Future, marker::PhantomData}; + +/// An adapter that lifts a standard [`Service`] into a +/// [`Service>`] which produces streaming Lambda HTTP +/// responses. +pub struct StreamAdapter<'a, S, B> { + service: S, + _phantom_data: PhantomData<&'a B>, +} + +impl<'a, S, B, E> From for StreamAdapter<'a, S, B> +where + S: Service, Error = E>, + S::Future: Send + 'a, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + fn from(service: S) -> Self { + StreamAdapter { + service, + _phantom_data: PhantomData, + } + } +} + +impl<'a, S, B, E> Service> for StreamAdapter<'a, S, B> +where + S: Service, Error = E>, + S::Future: Send + 'a, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + type Response = StreamResponse>; + type Error = E; + type Future = Pin> + Send + 'a>>; -/// Starts the Lambda Rust runtime and stream response back [Configure Lambda -/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: LambdaEvent) -> Self::Future { + let event: Request = req.payload.into(); + Box::pin( + self.service + .call(event.with_lambda_context(req.context)) + .map_ok(into_stream_response), + ) + } +} + +/// Builds a streaming-aware Tower service from a `Service` **without** +/// boxing its future (no heap allocation / vtable). /// -/// This takes care of transforming the LambdaEvent into a [`Request`] and -/// accepts [`http::Response`] as response. -pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), Error> +/// Transforms `LambdaEvent` into `Request` with Lambda context +/// and wraps `Response` into `StreamResponse>`. +/// +/// Used internally by [`run_with_streaming_response`]; not part of the public +/// API. +#[allow(clippy::type_complexity)] +fn into_stream_service<'a, S, B, E>( + handler: S, +) -> MapResponse< + MapRequest) -> Request>, + impl FnOnce(Response) -> StreamResponse> + Clone, +> where S: Service, Error = E>, S::Future: Send + 'a, @@ -25,38 +91,59 @@ where B::Data: Into + Send, B::Error: Into + Send + Debug, { - let svc = ServiceBuilder::new() + ServiceBuilder::new() .map_request(|req: LambdaEvent| { let event: Request = req.payload.into(); event.with_lambda_context(req.context) }) .service(handler) - .map_response(|res| { - let (parts, body) = res.into_parts(); - - let mut prelude_headers = parts.headers; - - let cookies = prelude_headers.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); + .map_response(into_stream_response) +} - prelude_headers.remove(SET_COOKIE); +/// Converts an `http::Response` into a streaming Lambda response. +fn into_stream_response(res: Response) -> StreamResponse> +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + let (parts, body) = res.into_parts(); - let metadata_prelude = MetadataPrelude { - headers: prelude_headers, - status_code: parts.status, - cookies, - }; + let mut headers = parts.headers; + let cookies = headers + .get_all(SET_COOKIE) + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + headers.remove(SET_COOKIE); - StreamResponse { - metadata_prelude, - stream: BodyStream { body }, - } - }); + StreamResponse { + metadata_prelude: MetadataPrelude { + headers, + status_code: parts.status, + cookies, + }, + stream: BodyStream { body }, + } +} - lambda_runtime::run(svc).await +/// Runs the Lambda runtime with a handler that returns **streaming** HTTP +/// responses. +/// +/// See the [AWS docs for response streaming]. +/// +/// [AWS docs for response streaming]: +/// https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html +pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), Error> +where + S: Service, Error = E>, + S::Future: Send + 'a, + E: Debug + Into, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + lambda_runtime::run(into_stream_service(handler)).await } pin_project_lite::pin_project! { @@ -85,3 +172,47 @@ where } } } + +#[cfg(test)] +mod test_stream_adapter { + use super::*; + + use crate::Body; + use http::StatusCode; + + // A middleware that logs requests before forwarding them to another service + struct LogService { + inner: S, + } + + impl Service> for LogService + where + S: Service>, + { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, event: LambdaEvent) -> Self::Future { + println!("Lambda event: {event:#?}"); + self.inner.call(event) + } + } + + #[test] + fn stream_adapter_is_boxable() { + // Works with a concrete service stack (no boxing) + let svc = ServiceBuilder::new() + .layer_fn(|service| LogService { inner: service }) + .layer_fn(StreamAdapter::from) + .service_fn( + |_req: Request| async move { http::Response::builder().status(StatusCode::OK).body(Body::Empty) }, + ); + // Also works when the stack is boxed (type-erased) + let _boxed_svc = svc.boxed(); + } +} From b627d760a62c3d96c9edd3dab2d708eabd1b1aed Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Wed, 27 Aug 2025 04:51:11 +0800 Subject: [PATCH 387/394] fix: serialize `AlbTargetGroupRequest::query_string_parameters` value to string (#955) * fix: serialize `AlbTargetGroupRequest::query_string_parameters` value to string fix #954 * fix: serialize AlbTargetGroupRequest::query_string_parameters with the last value of each key and prevent unnecessary mem alloc. #954 --- lambda-events/src/custom_serde/mod.rs | 5 ++ .../custom_serde/query_string_parameters.rs | 63 +++++++++++++++++++ lambda-events/src/event/alb/mod.rs | 18 +++++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/custom_serde/query_string_parameters.rs diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index aca3cd6c..02b50c78 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -34,6 +34,11 @@ pub(crate) mod float_unix_epoch; #[cfg(any(feature = "alb", feature = "apigw"))] pub(crate) mod http_method; +#[cfg(feature = "alb")] +mod query_string_parameters; +#[cfg(feature = "alb")] +pub(crate) use self::query_string_parameters::*; + pub(crate) fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/lambda-events/src/custom_serde/query_string_parameters.rs b/lambda-events/src/custom_serde/query_string_parameters.rs new file mode 100644 index 00000000..a7144eda --- /dev/null +++ b/lambda-events/src/custom_serde/query_string_parameters.rs @@ -0,0 +1,63 @@ +use query_map::QueryMap; +use serde::{ser::SerializeMap, Serializer}; +use std::collections::HashMap; + +/// Serializes `QueryMap`, converting value from `Vec` to `String` using the last value. +pub fn serialize_query_string_parameters(value: &QueryMap, serializer: S) -> Result +where + S: Serializer, +{ + let mut query_string_parameters = HashMap::new(); + + if let Some((mut last_key, mut last_value)) = value.iter().next() { + // insert the last value for each key + value.iter().for_each(|(k, v)| { + if k != last_key { + query_string_parameters.insert(last_key, last_value); + last_key = k; + } + last_value = v; + }); + // insert the last pair + query_string_parameters.insert(last_key, last_value); + } + + let mut map = serializer.serialize_map(Some(query_string_parameters.len()))?; + for (k, v) in &query_string_parameters { + map.serialize_entry(k, v)?; + } + map.end() +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Serialize; + use serde_json::Value; + + #[test] + fn test_serialize_query_string_parameters() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_query_string_parameters")] + pub v: QueryMap, + } + + fn s(value: &str) -> String { + value.to_string() + } + + let query = QueryMap::from(HashMap::from([ + (s("key1"), vec![s("value1"), s("value2"), s("value3")]), + (s("key2"), vec![s("value4")]), + (s("key3"), vec![s("value5"), s("value6")]), + ])); + + let serialized = serde_json::to_string(&Test { v: query }).unwrap(); + let parsed: Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(parsed["v"]["key1"], Value::String("value3".to_string())); + assert_eq!(parsed["v"]["key2"], Value::String("value4".to_string())); + assert_eq!(parsed["v"]["key3"], Value::String("value6".to_string())); + } +} diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 55e427e2..5ec4d242 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,6 +1,7 @@ use crate::{ custom_serde::{ - deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, serialize_query_string_parameters, }, encodings::Body, }; @@ -19,6 +20,7 @@ pub struct AlbTargetGroupRequest { #[serde(default)] pub path: Option, #[serde(default)] + #[serde(serialize_with = "serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap, @@ -100,6 +102,7 @@ pub struct AlbTargetGroupResponse { #[cfg(test)] mod test { use super::*; + use serde_json::Value; #[test] #[cfg(feature = "alb")] @@ -121,6 +124,19 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "alb")] + fn ensure_alb_lambda_target_request_query_string_parameter_value_is_string() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-request-headers-only.json"); + let parsed: AlbTargetGroupRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: Value = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!( + reparsed["queryStringParameters"]["key"], + Value::String("hello".to_string()) + ); + } + #[test] #[cfg(feature = "alb")] fn example_alb_lambda_target_response() { From 7ac22741fc64140c1c0b82a0d2f2ce05933e8d9f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:17:30 -0700 Subject: [PATCH 388/394] chore: prepare lambda_http@0.17.0, lambda_events@0.18.0, lambda_runtime_api_client@0.12.4, lambda_runtime@0.14.4 (#1034) --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index cd2d86f2..2cd8c969 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.17.0" +version = "0.18.0" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index b930afdb..4c279e80 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.16.0" +version = "0.17.0" authors = [ "David Calavera ", "Harold Sun ", @@ -50,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.17.0" +version = "0.18.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index dcc2916d..ba8f0fa0 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.3" +version = "0.12.4" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 3daefc11..30f0bfa9 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.3" +version = "0.14.4" authors = [ "David Calavera ", "Harold Sun ", From 8e6fac2d58278b5c8aa22a4013ccb9138b908513 Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi <117035062+AntoniaSzecsi@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:35:29 +0100 Subject: [PATCH 389/394] Add local testing infrastructure with AWS Lambda Runtime Interface Emulator (RIE) (#1033) * Add local testing infrastructure with RIE * Update Dockerfile.rie based on PR feedback --------- Co-authored-by: AntoniaSzecsi --- .github/workflows/test-rie.yml | 50 ++++++++++++++++++++++++++++++++++ Dockerfile.rie | 25 +++++++++++++++++ Makefile | 5 +++- README.md | 25 +++++++++++++++++ scripts/test-rie.sh | 22 +++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-rie.yml create mode 100644 Dockerfile.rie create mode 100755 scripts/test-rie.sh diff --git a/.github/workflows/test-rie.yml b/.github/workflows/test-rie.yml new file mode 100644 index 00000000..5d777e2d --- /dev/null +++ b/.github/workflows/test-rie.yml @@ -0,0 +1,50 @@ +name: Test with RIE + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test-rie: + runs-on: ubuntu-latest + strategy: + matrix: + example: [basic-lambda, basic-sqs] + + steps: + - uses: actions/checkout@v4 + + - name: Build and test ${{ matrix.example }} with RIE + run: | + docker build -f Dockerfile.rie --build-arg EXAMPLE=${{ matrix.example }} -t rust-lambda-rie-test-${{ matrix.example }} . + + # Start container in background + docker run -d -p 9000:8080 --name rie-test-${{ matrix.example }} rust-lambda-rie-test-${{ matrix.example }} + + # Wait for container to be ready + sleep 5 + + # Test the function based on example type + if [ "${{ matrix.example }}" = "basic-lambda" ]; then + PAYLOAD='{"command": "test from CI"}' + elif [ "${{ matrix.example }}" = "basic-sqs" ]; then + PAYLOAD='{"Records": [{"body": "{\"id\": \"123\", \"text\": \"hello from SQS\"}", "messageId": "test-id", "receiptHandle": "test-handle", "attributes": {}, "messageAttributes": {}, "md5OfBody": "test-md5", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:test-queue", "awsRegion": "us-east-1"}]}' + fi + + # Make request and verify response + RESPONSE=$(curl -s -XPOST 'http://localhost:9000/2015-03-31/functions/function/invocations' \ + -d "$PAYLOAD" \ + -H 'Content-Type: application/json') + + echo "Response: $RESPONSE" + + # Basic validation that we got a response (not empty) + if [ -z "$RESPONSE" ]; then + echo "Error: Empty response" + exit 1 + fi + + # Stop container + docker stop rie-test-${{ matrix.example }} \ No newline at end of file diff --git a/Dockerfile.rie b/Dockerfile.rie new file mode 100644 index 00000000..1a46b577 --- /dev/null +++ b/Dockerfile.rie @@ -0,0 +1,25 @@ +FROM public.ecr.aws/lambda/provided:al2023 + +RUN dnf install -y gcc +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie +RUN chmod +x /usr/local/bin/aws-lambda-rie + +ARG EXAMPLE=basic-lambda + +COPY Cargo.* /build/ +COPY lambda-runtime /build/lambda-runtime +COPY lambda-runtime-api-client /build/lambda-runtime-api-client +COPY lambda-events /build/lambda-events +COPY lambda-http /build/lambda-http +COPY lambda-extension /build/lambda-extension +COPY examples/${EXAMPLE} /build/examples/${EXAMPLE} + +WORKDIR /build/examples/${EXAMPLE} +RUN cargo build --release +RUN cp target/release/${EXAMPLE} ${LAMBDA_RUNTIME_DIR}/bootstrap + +ENTRYPOINT [] +CMD [ "/usr/local/bin/aws-lambda-rie", "/var/runtime/bootstrap" ] \ No newline at end of file diff --git a/Makefile b/Makefile index ecfd7623..155b7ea1 100644 --- a/Makefile +++ b/Makefile @@ -108,4 +108,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features streams fmt: - cargo +nightly fmt --all \ No newline at end of file + cargo +nightly fmt --all + +test-rie: + ./scripts/test-rie.sh $(EXAMPLE) \ No newline at end of file diff --git a/README.md b/README.md index 5426c0c9..fdec26a5 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,31 @@ curl -v -X POST \ You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/invoke.html) work on the project's [documentation page](https://www.cargo-lambda.info). +### Local testing with Runtime Interface Emulator (RIE) + +For testing with the official AWS Lambda Runtime Interface Emulator, use the provided RIE testing infrastructure: + +```bash +make test-rie +``` + +By default, this uses the `basic-lambda` example. To test a different example: + +```bash +make test-rie EXAMPLE=basic-sqs +make test-rie EXAMPLE=http-basic-lambda +``` + +This command will: +1. Build a Docker image with Rust toolchain and RIE +2. Compile the specified example inside the Linux container +3. Start the RIE container on port 9000 +4. Display the appropriate curl command for testing + +Different examples expect different payload formats. Check the example's source code in `examples/EXAMPLE_NAME/src/main.rs` + +This provides automated testing with Docker and RIE, ensuring your Lambda functions work in a Linux environment identical to AWS Lambda. + ### Lambda Debug Proxy Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. diff --git a/scripts/test-rie.sh b/scripts/test-rie.sh new file mode 100755 index 00000000..911cb390 --- /dev/null +++ b/scripts/test-rie.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +EXAMPLE=${1:-basic-lambda} + +echo "Building Docker image with RIE for example: $EXAMPLE..." +docker build -f Dockerfile.rie --build-arg EXAMPLE=$EXAMPLE -t rust-lambda-rie-test . + +echo "Starting RIE container on port 9000..." +docker run -p 9000:8080 rust-lambda-rie-test & +CONTAINER_PID=$! + +echo "Container started. Test with:" +if [ "$EXAMPLE" = "basic-lambda" ]; then + echo "curl -XPOST 'http://localhost:9000/2015-03-31/functions/function/invocations' -d '{\"command\": \"test from RIE\"}' -H 'Content-Type: application/json'" +else + echo "For example '$EXAMPLE', check examples/$EXAMPLE/src/main.rs for the expected payload format." +fi +echo "" +echo "Press Ctrl+C to stop the container." + +wait $CONTAINER_PID \ No newline at end of file From 4c4e5fac53be0a1fdca671b2f7bb1f905247778e Mon Sep 17 00:00:00 2001 From: Ikuma Yamashita Date: Fri, 19 Sep 2025 00:05:31 +0900 Subject: [PATCH 390/394] feat(lambda-events): add Default implementations for all event (#1037) * feat: implement Default trait for all lambda events --- lambda-events/src/encodings/http.rs | 14 ++++----- lambda-events/src/event/appsync/mod.rs | 30 +++++++++++-------- lambda-events/src/event/clientvpn/mod.rs | 4 +-- lambda-events/src/event/cloudformation/mod.rs | 4 +-- .../src/event/cloudformation/provider.rs | 4 +-- lambda-events/src/event/code_commit/mod.rs | 6 ++-- lambda-events/src/event/codebuild/mod.rs | 4 +-- .../src/event/dynamodb/attributes.rs | 20 ++++++------- lambda-events/src/event/dynamodb/mod.rs | 17 +++++++---- lambda-events/src/event/firehose/mod.rs | 8 ++--- lambda-events/src/event/iam/mod.rs | 2 +- lambda-events/src/event/iot/mod.rs | 14 ++++----- lambda-events/src/event/iot_deprecated/mod.rs | 4 +-- .../src/event/lambda_function_urls/mod.rs | 12 ++++---- lambda-events/src/event/lex/mod.rs | 8 ++--- lambda-events/src/event/rabbitmq/mod.rs | 4 +-- lambda-events/src/event/ses/mod.rs | 18 +++++------ lambda-events/src/event/sns/mod.rs | 6 ++-- lambda-events/src/event/streams/mod.rs | 12 ++++---- lambda-http/src/deserializer.rs | 12 ++++---- lambda-runtime/src/runtime.rs | 4 +-- 21 files changed, 109 insertions(+), 98 deletions(-) diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index d978f522..8524f215 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -264,7 +264,7 @@ mod tests { fn from_str() { match Body::from(String::from("foo").as_str()) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -272,7 +272,7 @@ mod tests { fn from_string() { match Body::from(String::from("foo")) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -280,7 +280,7 @@ mod tests { fn from_cow_str() { match Body::from(Cow::from("foo")) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -288,7 +288,7 @@ mod tests { fn from_cow_bytes() { match Body::from(Cow::from("foo".as_bytes())) { Body::Binary(_) => (), - not => panic!("expected Body::Binary(...) got {:?}", not), + not => panic!("expected Body::Binary(...) got {not:?}"), } } @@ -296,7 +296,7 @@ mod tests { fn from_bytes() { match Body::from("foo".as_bytes()) { Body::Binary(_) => (), - not => panic!("expected Body::Binary(...) got {:?}", not), + not => panic!("expected Body::Binary(...) got {not:?}"), } } @@ -325,12 +325,12 @@ mod tests { fn serialize_from_maybe_encoded() { match Body::from_maybe_encoded(false, "foo") { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } match Body::from_maybe_encoded(true, "Zm9v") { Body::Binary(b) => assert_eq!(&[102, 111, 111], b.as_slice()), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } } diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index ed68915e..312843bd 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate where @@ -27,7 +27,7 @@ where } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIamIdentity { #[serde(default)] @@ -55,7 +55,7 @@ pub struct AppSyncIamIdentity { } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncCognitoIdentity where @@ -87,7 +87,7 @@ where pub type AppSyncOperation = String; /// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequest { #[serde(default)] @@ -104,7 +104,7 @@ pub struct AppSyncLambdaAuthorizerRequest { /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered /// this authorization request. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequestContext where @@ -136,7 +136,7 @@ where } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerResponse where @@ -171,7 +171,7 @@ where /// /// See also: /// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncDirectResolverEvent where TArguments: Serialize + DeserializeOwned, @@ -200,7 +200,7 @@ where /// `AppSyncRequest` contains request-related metadata for a resolver invocation, /// including client-sent headers and optional custom domain name. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncRequest { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -219,7 +219,7 @@ pub struct AppSyncRequest { } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncInfo where @@ -243,7 +243,7 @@ where } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncPrevResult where T: Serialize + DeserializeOwned, @@ -270,8 +270,14 @@ pub enum AppSyncIdentity { Lambda(AppSyncIdentityLambda), } +impl Default for AppSyncIdentity { + fn default() -> Self { + AppSyncIdentity::IAM(AppSyncIamIdentity::default()) + } +} + /// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncIdentityOIDC where T: Serialize + DeserializeOwned, @@ -290,7 +296,7 @@ where } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIdentityLambda where diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index e99d7c8c..89712834 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { #[serde(default)] @@ -40,7 +40,7 @@ pub struct ClientVpnConnectionHandlerRequest { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerResponse { pub allow: bool, diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 995ab846..84e793b0 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -50,7 +50,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest where @@ -79,7 +79,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest where diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index 71277388..dd0043cd 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -39,7 +39,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest where @@ -56,7 +56,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest where diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index e35ab093..021f8942 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -23,7 +23,7 @@ pub struct CodeCommitEvent { pub type CodeCommitEventTime = DateTime; /// `CodeCommitRecord` represents a CodeCommit record -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitRecord { #[serde(default)] @@ -63,7 +63,7 @@ pub struct CodeCommitRecord { } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { pub references: Vec, @@ -80,7 +80,7 @@ pub struct CodeCommitCodeCommit { } /// `CodeCommitReference` represents a Reference object in a CodeCommit object -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitReference { #[serde(default)] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 4ffb821d..304cf465 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -178,7 +178,7 @@ pub struct CodeBuildEnvironment { } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironmentVariable { /// Name is the name of the environment variable. @@ -239,7 +239,7 @@ pub struct CodeBuildLogs { } /// `CodeBuildPhase` represents the phase of a build and its details -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildPhase where diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index e1a42c83..ac43adea 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -15,7 +15,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::Null(true) => {} - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -31,7 +31,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::S(ref s) => assert_eq!("value", s.as_str()), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -47,7 +47,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::N(ref n) => assert_eq!("123.45", n.as_str()), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -68,7 +68,7 @@ mod test { .unwrap(); assert_eq!(&expected, b) } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -84,7 +84,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::Bool(b) => assert!(b), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -103,7 +103,7 @@ mod test { let expected = vec!["Giraffe", "Hippo", "Zebra"]; assert_eq!(expected, s.iter().collect::>()); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -122,7 +122,7 @@ mod test { let expected = vec!["42.2", "-19", "7.5", "3.14"]; assert_eq!(expected, s.iter().collect::>()); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -144,7 +144,7 @@ mod test { .collect::>(); assert_eq!(&expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -167,7 +167,7 @@ mod test { ]; assert_eq!(&expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -188,7 +188,7 @@ mod test { expected.insert("Age".into(), AttributeValue::N("35".into())); assert_eq!(expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } } } diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 3e1b3ab3..8b8041c0 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -12,12 +12,13 @@ use std::fmt; #[cfg(test)] mod attributes; -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, OldImage, NewAndOldImages, + #[default] KeysOnly, } @@ -33,12 +34,13 @@ impl fmt::Display for StreamViewType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, Enabled, Disabling, + #[default] Disabled, } @@ -54,10 +56,11 @@ impl fmt::Display for StreamStatus { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, + #[default] Latest, AtSequenceNumber, AfterSequenceNumber, @@ -75,9 +78,10 @@ impl fmt::Display for SharedIteratorType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { + #[default] Insert, Modify, Remove, @@ -94,9 +98,10 @@ impl fmt::Display for OperationType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { + #[default] Hash, Range, } @@ -128,7 +133,7 @@ pub struct Event { /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { #[serde(rename = "DynamoDBEvent")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 8bce49ac..a97577f5 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -49,7 +49,7 @@ pub struct KinesisFirehoseEventRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { pub records: Vec, @@ -62,7 +62,7 @@ pub struct KinesisFirehoseResponse { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecord { #[serde(default)] @@ -81,7 +81,7 @@ pub struct KinesisFirehoseResponseRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecordMetadata { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -96,7 +96,7 @@ pub struct KinesisFirehoseResponseRecordMetadata { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseRecordMetadata { #[serde(default)] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index f4301b1e..42c8a9c7 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -8,7 +8,7 @@ use serde::{ }; /// `IamPolicyDocument` represents an IAM policy document. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { #[serde(default)] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index cb262bd0..f544510f 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -6,7 +6,7 @@ use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { #[serde(default)] @@ -24,7 +24,7 @@ pub struct IoTCoreCustomAuthorizerRequest { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreProtocolData { pub tls: Option, @@ -39,7 +39,7 @@ pub struct IoTCoreProtocolData { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreTlsContext { #[serde(default)] @@ -53,7 +53,7 @@ pub struct IoTCoreTlsContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreHttpContext { #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] @@ -70,7 +70,7 @@ pub struct IoTCoreHttpContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreMqttContext { #[serde(default)] @@ -87,7 +87,7 @@ pub struct IoTCoreMqttContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreConnectionMetadata { #[serde(default)] @@ -103,7 +103,7 @@ pub struct IoTCoreConnectionMetadata { /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. /// See -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { pub is_authenticated: bool, diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 30475675..30142606 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -5,7 +5,7 @@ use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerRequest { pub http_context: Option, @@ -33,7 +33,7 @@ pub type IoTTlsContext = IoTCoreTlsContext; /// `IoTCustomAuthorizerResponse` represents the expected format of an IoT device gateway authorization response. /// Deprecated: Use IoTCoreCustomAuthorizerResponse. `IoTCustomAuthorizerResponse` does not correctly model the response schema. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerResponse { pub is_authenticated: bool, diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index a754af0d..646d141a 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequest { /// Version is expected to be `"2.0"` @@ -37,7 +37,7 @@ pub struct LambdaFunctionUrlRequest { } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContext { #[serde(default)] @@ -69,7 +69,7 @@ pub struct LambdaFunctionUrlRequestContext { } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { pub iam: Option, @@ -83,7 +83,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { #[serde(default)] @@ -106,7 +106,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextHttpDescription { #[serde(default)] @@ -129,7 +129,7 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlResponse { pub status_code: i64, diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index 6a458c8a..46258951 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -31,7 +31,7 @@ pub struct LexEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexBot { pub name: Option, @@ -84,7 +84,7 @@ pub struct LexAlternativeIntents { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SlotDetail { pub resolutions: Option>>, @@ -123,7 +123,7 @@ pub type SessionAttributes = HashMap; pub type Slots = HashMap>; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponse { pub session_attributes: SessionAttributes, @@ -152,7 +152,7 @@ pub struct LexResponseCard { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attachment { pub title: Option, diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 6c79e2b0..e1a7256b 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -24,7 +24,7 @@ pub struct RabbitMqEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqMessage { pub basic_properties: RabbitMqBasicProperties, @@ -40,7 +40,7 @@ pub struct RabbitMqMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqBasicProperties where diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 9358135d..20498780 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -18,7 +18,7 @@ pub struct SimpleEmailEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailRecord { #[serde(default)] @@ -35,7 +35,7 @@ pub struct SimpleEmailRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailService { pub mail: SimpleEmailMessage, @@ -50,7 +50,7 @@ pub struct SimpleEmailService { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailMessage { pub common_headers: SimpleEmailCommonHeaders, @@ -71,7 +71,7 @@ pub struct SimpleEmailMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceipt { pub recipients: Vec, @@ -94,7 +94,7 @@ pub struct SimpleEmailReceipt { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailHeader { #[serde(default)] @@ -110,7 +110,7 @@ pub struct SimpleEmailHeader { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailCommonHeaders { pub from: Vec, @@ -136,7 +136,7 @@ pub struct SimpleEmailCommonHeaders { /// Types. For example, the FunctionARN and InvocationType fields are only /// present for the Lambda Type, and the BucketName and ObjectKey fields are only /// present for the S3 Type. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceiptAction { #[serde(default)] @@ -160,7 +160,7 @@ pub struct SimpleEmailReceiptAction { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailVerdict { #[serde(default)] @@ -177,7 +177,7 @@ pub struct SimpleEmailVerdict { pub type SimpleEmailDispositionValue = String; /// `SimpleEmailDisposition` disposition return for SES to control rule functions -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { pub disposition: SimpleEmailDispositionValue, diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 611a16b7..163c13ac 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -123,7 +123,7 @@ pub struct SnsEventObj { } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsRecordObj { @@ -150,7 +150,7 @@ pub struct SnsRecordObj { /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` #[serde_with::serde_as] -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsMessageObj { @@ -412,7 +412,7 @@ mod test { } let parsed: SnsEventObj = serde_json::from_slice(data).unwrap(); - println!("{:?}", parsed); + println!("{parsed:?}"); assert_eq!(parsed.records[0].sns.message.foo, "Hello world!"); assert_eq!(parsed.records[0].sns.message.bar, 123); diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 673217fc..caf2c02d 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { pub batch_item_failures: Vec, @@ -17,7 +17,7 @@ pub struct KinesisEventResponse { } /// `KinesisBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisBatchItemFailure { #[serde(default)] @@ -32,7 +32,7 @@ pub struct KinesisBatchItemFailure { } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { pub batch_item_failures: Vec, @@ -46,7 +46,7 @@ pub struct DynamoDbEventResponse { } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbBatchItemFailure { #[serde(default)] @@ -61,7 +61,7 @@ pub struct DynamoDbBatchItemFailure { } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { pub batch_item_failures: Vec, @@ -75,7 +75,7 @@ pub struct SqsEventResponse { } /// `SqsBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchItemFailure { #[serde(default)] diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 4a09ff9a..e0da5e0e 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -61,7 +61,7 @@ mod tests { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("12345678912", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -74,7 +74,7 @@ mod tests { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -87,7 +87,7 @@ mod tests { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -100,7 +100,7 @@ mod tests { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -118,7 +118,7 @@ mod tests { req.request_context.elb.target_group_arn.unwrap() ); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -132,7 +132,7 @@ mod tests { LambdaRequest::WebSocket(req) => { assert_eq!("CONNECT", req.request_context.event_type.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 5749fbb7..517ee64f 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -369,7 +369,7 @@ mod endpoint_tests { }); let next_response = server.mock(|when, then| { when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .path(format!("/2018-06-01/runtime/invocation/{request_id}/response")) .body("{}"); then.status(200).body(""); }); @@ -440,7 +440,7 @@ mod endpoint_tests { let next_response = server.mock(|when, then| { when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .path(format!("/2018-06-01/runtime/invocation/{request_id}/error")) .header("lambda-runtime-function-error-type", "unhandled"); then.status(200).body(""); }); From 726bcea234a965a280801675c16d747d06d3ccf1 Mon Sep 17 00:00:00 2001 From: Chase Naples Date: Wed, 8 Oct 2025 06:33:46 -0400 Subject: [PATCH 391/394] feat: mark selected public enums as #[non_exhaustive] (part of #1016) (#1040) - lambda-events (dynamodb): StreamViewType, StreamStatus, SharedIteratorType, OperationType, KeyType - lambda-http: PayloadError, JsonPayloadError, FormUrlEncodedPayloadError These enums are likely to grow; marking them non_exhaustive helps avoid semver hazards for downstream consumers who pattern-match exhaustively. --- lambda-events/src/event/dynamodb/mod.rs | 5 +++++ lambda-http/src/ext/request.rs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 8b8041c0..316768dc 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -13,6 +13,7 @@ use std::fmt; mod attributes; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, @@ -35,6 +36,7 @@ impl fmt::Display for StreamViewType { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, @@ -57,6 +59,7 @@ impl fmt::Display for StreamStatus { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, @@ -79,6 +82,7 @@ impl fmt::Display for SharedIteratorType { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { #[default] @@ -99,6 +103,7 @@ impl fmt::Display for OperationType { } #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { #[default] diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index c56518f6..dc14532e 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -13,6 +13,7 @@ use crate::Body; /// /// Returned by [`RequestPayloadExt::payload()`] #[derive(Debug)] +#[non_exhaustive] pub enum PayloadError { /// Returned when `application/json` bodies fail to deserialize a payload Json(serde_json::Error), @@ -22,6 +23,7 @@ pub enum PayloadError { /// Indicates a problem processing a JSON payload. #[derive(Debug)] +#[non_exhaustive] pub enum JsonPayloadError { /// Problem deserializing a JSON payload. Parsing(serde_json::Error), @@ -29,6 +31,7 @@ pub enum JsonPayloadError { /// Indicates a problem processing an x-www-form-urlencoded payload. #[derive(Debug)] +#[non_exhaustive] pub enum FormUrlEncodedPayloadError { /// Problem deserializing an x-www-form-urlencoded payload. Parsing(SerdeError), From 3c8a8be5649e22fed475fa0215271e5d85c74425 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:20:08 +0200 Subject: [PATCH 392/394] Bump rustc version to 1.82.0 (#1044) * Update Dependencies Signed-off-by: Astraea Quinn Sinclair --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-integration-test.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- Cargo.toml | 2 ++ README.md | 2 +- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-integration-tests/Cargo.toml | 2 +- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 8 ++++---- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 372375b5..624f96d6 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 2daaa40d..f823dbb4 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index c0d43e25..dd9bd68f 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index c5cf2d08..0327bd34 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index 867e9c0d..7baf97c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,5 @@ pin-project-lite = "0.2" tower = "0.5" tower-layer = "0.3" tower-service = "0.3" + + diff --git a/README.md b/README.md index fdec26a5..954b195c 100644 --- a/README.md +++ b/README.md @@ -485,7 +485,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.81.0, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.82.0, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 2cd8c969..c21e959b 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "aws_lambda_events" version = "0.18.0" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 515a97fe..427b744a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -2,7 +2,7 @@ name = "lambda-extension" version = "0.12.2" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4c279e80..f5f3dfe1 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Harold Sun ", ] edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" @@ -55,8 +55,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] -axum-core = "0.5.0" -axum-extra = { version = "0.10.0", features = ["query"] } +axum-core = "0.5.4" +axum-extra = { version = "0.10.2", features = ["query"] } lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 85d5fca6..2e9817c2 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -3,7 +3,7 @@ name = "lambda-integration-tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index ba8f0fa0..39311a8c 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -2,7 +2,7 @@ name = "lambda_runtime_api_client" version = "0.12.4" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 30f0bfa9..f8192e5c 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] description = "AWS Lambda Runtime" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] @@ -40,7 +40,7 @@ hyper = { workspace = true, features = ["http1", "client"] } lambda-extension = { version = "0.12.2", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.31", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" @@ -56,7 +56,7 @@ tower = { workspace = true, features = ["util"] } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] -httpmock = "0.7.0" +httpmock = "0.8.1" hyper-util = { workspace = true, features = [ "client", "client-legacy", @@ -74,4 +74,4 @@ pin-project-lite = { workspace = true } tracing-appender = "0.2" [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true From b40c011950489aa675a00ffdd863e8bcddbc3489 Mon Sep 17 00:00:00 2001 From: mpindaru <102007013+mpindaru@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:11:54 +0100 Subject: [PATCH 393/394] feat(lambda-events): mark public structs/enums as #[non_exhaustive] (#1045) * feat(lambda-events): mark public structs/enums as #[non_exhaustive] * fix examples --------- Co-authored-by: Mara Pindaru --- .../src/main.rs | 18 +++-- .../src/main.rs | 42 +++++----- examples/basic-s3-thumbnail/src/main.rs | 81 ++++++++++--------- lambda-events/src/encodings/http.rs | 1 + lambda-events/src/encodings/mod.rs | 1 + lambda-events/src/encodings/time.rs | 4 + lambda-events/src/event/activemq/mod.rs | 3 + lambda-events/src/event/alb/mod.rs | 3 + lambda-events/src/event/apigw/mod.rs | 27 +++++++ lambda-events/src/event/appsync/mod.rs | 13 +++ lambda-events/src/event/autoscaling/mod.rs | 1 + .../src/event/bedrock_agent_runtime/mod.rs | 6 ++ lambda-events/src/event/chime_bot/mod.rs | 4 + lambda-events/src/event/clientvpn/mod.rs | 2 + lambda-events/src/event/cloudformation/mod.rs | 6 ++ .../src/event/cloudformation/provider.rs | 6 ++ .../src/event/cloudwatch_alarms/mod.rs | 15 ++++ .../src/event/cloudwatch_events/cloudtrail.rs | 7 ++ .../src/event/cloudwatch_events/codedeploy.rs | 3 + .../event/cloudwatch_events/codepipeline.rs | 4 + .../src/event/cloudwatch_events/ec2.rs | 1 + .../src/event/cloudwatch_events/emr.rs | 4 + .../src/event/cloudwatch_events/gamelift.rs | 12 +++ .../src/event/cloudwatch_events/glue.rs | 8 ++ .../src/event/cloudwatch_events/health.rs | 3 + .../src/event/cloudwatch_events/kms.rs | 1 + .../src/event/cloudwatch_events/macie.rs | 18 +++++ .../src/event/cloudwatch_events/mod.rs | 1 + .../src/event/cloudwatch_events/opsworks.rs | 4 + .../src/event/cloudwatch_events/signin.rs | 4 + .../src/event/cloudwatch_events/sms.rs | 1 + .../src/event/cloudwatch_events/ssm.rs | 14 ++++ .../src/event/cloudwatch_events/tag.rs | 1 + .../event/cloudwatch_events/trustedadvisor.rs | 1 + .../src/event/cloudwatch_logs/mod.rs | 4 + lambda-events/src/event/code_commit/mod.rs | 4 + lambda-events/src/event/codebuild/mod.rs | 9 +++ lambda-events/src/event/codedeploy/mod.rs | 3 + .../src/event/codepipeline_cloudwatch/mod.rs | 3 + .../src/event/codepipeline_job/mod.rs | 11 +++ lambda-events/src/event/cognito/mod.rs | 53 ++++++++++++ lambda-events/src/event/config/mod.rs | 1 + lambda-events/src/event/connect/mod.rs | 5 ++ .../event/documentdb/events/commom_types.rs | 6 ++ .../event/documentdb/events/delete_event.rs | 1 + .../documentdb/events/drop_database_event.rs | 1 + .../src/event/documentdb/events/drop_event.rs | 1 + .../event/documentdb/events/insert_event.rs | 1 + .../documentdb/events/invalidate_event.rs | 1 + .../event/documentdb/events/rename_event.rs | 1 + .../event/documentdb/events/replace_event.rs | 1 + .../event/documentdb/events/update_event.rs | 3 + lambda-events/src/event/documentdb/mod.rs | 3 + lambda-events/src/event/dynamodb/mod.rs | 16 ++-- lambda-events/src/event/ecr_scan/mod.rs | 3 + lambda-events/src/event/eventbridge/mod.rs | 1 + lambda-events/src/event/firehose/mod.rs | 6 ++ lambda-events/src/event/iam/mod.rs | 2 + lambda-events/src/event/iot/mod.rs | 7 ++ lambda-events/src/event/iot_1_click/mod.rs | 5 ++ lambda-events/src/event/iot_button/mod.rs | 1 + lambda-events/src/event/iot_deprecated/mod.rs | 2 + lambda-events/src/event/kafka/mod.rs | 2 + lambda-events/src/event/kinesis/analytics.rs | 4 + lambda-events/src/event/kinesis/event.rs | 6 ++ .../src/event/lambda_function_urls/mod.rs | 6 ++ lambda-events/src/event/lex/mod.rs | 9 +++ lambda-events/src/event/rabbitmq/mod.rs | 3 + lambda-events/src/event/s3/batch_job.rs | 5 ++ lambda-events/src/event/s3/event.rs | 7 ++ lambda-events/src/event/s3/object_lambda.rs | 10 +++ lambda-events/src/event/secretsmanager/mod.rs | 1 + lambda-events/src/event/ses/mod.rs | 10 +++ lambda-events/src/event/sns/mod.rs | 13 +++ lambda-events/src/event/sqs/mod.rs | 11 +++ lambda-events/src/event/streams/mod.rs | 6 ++ lambda-events/src/time_window.rs | 3 + lambda-http/src/response.rs | 3 +- 78 files changed, 507 insertions(+), 67 deletions(-) diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 6cea2f93..4af67b8c 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -96,11 +96,17 @@ where } }, ) - .map(|id| BatchItemFailure { item_identifier: id }) + .map(|id| { + let mut failure_item = BatchItemFailure::default(); + failure_item.item_identifier = id; + failure_item + }) .collect(); - Ok(SqsBatchResponse { - batch_item_failures: failure_items, + Ok({ + let mut response = SqsBatchResponse::default(); + response.batch_item_failures = failure_items; + response }) } @@ -140,8 +146,10 @@ mod test { .unwrap(); let lambda_event = LambdaEvent { - payload: SqsEventObj { - records: vec![msg_to_fail, msg_to_succeed], + payload: { + let mut event_object = SqsEventObj::default(); + event_object.records = vec![msg_to_fail, msg_to_succeed]; + event_object }, context: Context::default(), }; diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 699fb044..2f844059 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -126,24 +126,28 @@ mod tests { } fn get_s3_event() -> S3ObjectLambdaEvent { - S3ObjectLambdaEvent { - x_amz_request_id: ("ID".to_string()), - head_object_context: (Some(HeadObjectContext::default())), - list_objects_context: (Some(ListObjectsContext::default())), - get_object_context: (Some(GetObjectContext { - input_s3_url: ("S3_URL".to_string()), - output_route: ("O_ROUTE".to_string()), - output_token: ("O_TOKEN".to_string()), - })), - list_objects_v2_context: (Some(ListObjectsV2Context::default())), - protocol_version: ("VERSION".to_string()), - user_identity: (UserIdentity::default()), - user_request: (UserRequest::default()), - configuration: (Configuration { - access_point_arn: ("APRN".to_string()), - supporting_access_point_arn: ("SAPRN".to_string()), - payload: (json!(null)), - }), - } + let mut event = S3ObjectLambdaEvent::default(); + event.x_amz_request_id = "ID".to_string(); + event.head_object_context = Some(HeadObjectContext::default()); + event.list_objects_context = Some(ListObjectsContext::default()); + event.get_object_context = Some({ + let mut context = GetObjectContext::default(); + context.input_s3_url = "S3_URL".to_string(); + context.output_route = "O_ROUTE".to_string(); + context.output_token = "O_TOKEN".to_string(); + context + }); + event.list_objects_v2_context = Some(ListObjectsV2Context::default()); + event.protocol_version = "VERSION".to_string(); + event.user_identity = UserIdentity::default(); + event.user_request = UserRequest::default(); + event.configuration = { + let mut configuration = Configuration::default(); + configuration.access_point_arn = "APRN".to_string(); + configuration.supporting_access_point_arn = "SAPRN".to_string(); + configuration.payload = json!(null); + configuration + }; + event } } diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d09da116..0d647b05 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -185,46 +185,53 @@ mod tests { } fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { - S3Event { - records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), - } + let mut event = S3Event::default(); + event.records = vec![get_s3_event_record(event_name, bucket_name, object_key)]; + event } fn get_s3_event_record(event_name: &str, bucket_name: &str, object_key: &str) -> S3EventRecord { - let s3_entity = S3Entity { - schema_version: (Some(String::default())), - configuration_id: (Some(String::default())), - bucket: (S3Bucket { - name: (Some(bucket_name.to_string())), - owner_identity: Some(S3UserIdentity { - principal_id: (Some(String::default())), - }), - arn: (Some(String::default())), - }), - object: (S3Object { - key: (Some(object_key.to_string())), - size: (Some(1)), - url_decoded_key: (Some(String::default())), - version_id: (Some(String::default())), - e_tag: (Some(String::default())), - sequencer: (Some(String::default())), - }), + let mut s3_bucket = S3Bucket::default(); + s3_bucket.name = (Some(bucket_name.to_string())); + s3_bucket.owner_identity = { + let mut s3_user_identity = S3UserIdentity::default(); + s3_user_identity.principal_id = Some(String::default()); + Some(s3_user_identity) }; - - S3EventRecord { - event_version: (Some(String::default())), - event_source: (Some(String::default())), - aws_region: (Some(String::default())), - event_time: (chrono::DateTime::default()), - event_name: (Some(event_name.to_string())), - principal_id: (S3UserIdentity { - principal_id: (Some("X".to_string())), - }), - request_parameters: (S3RequestParameters { - source_ip_address: (Some(String::default())), - }), - response_elements: (HashMap::new()), - s3: (s3_entity), - } + s3_bucket.arn = Some(String::default()); + + let mut s3_object = S3Object::default(); + s3_object.key = Some(object_key.to_string()); + s3_object.size = Some(1); + s3_object.url_decoded_key = Some(String::default()); + s3_object.version_id = Some(String::default()); + s3_object.e_tag = Some(String::default()); + s3_object.sequencer = Some(String::default()); + + let mut s3_entity = S3Entity::default(); + s3_entity.schema_version = Some(String::default()); + s3_entity.configuration_id = Some(String::default()); + s3_entity.bucket = s3_bucket; + s3_entity.object = s3_object; + + let mut s3_event_record = S3EventRecord::default(); + s3_event_record.event_version = Some(String::default()); + s3_event_record.event_source = Some(String::default()); + s3_event_record.aws_region = Some(String::default()); + s3_event_record.event_time = chrono::DateTime::default(); + s3_event_record.event_name = Some(event_name.to_string()); + s3_event_record.principal_id = { + let mut s3_user_identity = S3UserIdentity::default(); + s3_user_identity.principal_id = Some("X".to_string()); + s3_user_identity + }; + s3_event_record.request_parameters = { + let mut s3_request_parameters = S3RequestParameters::default(); + s3_request_parameters.source_ip_address = Some(String::default()); + s3_request_parameters + }; + s3_event_record.response_elements = HashMap::new(); + s3_event_record.s3 = s3_entity; + s3_event_record } } diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 8524f215..32137a75 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -60,6 +60,7 @@ use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// /// For more information about API Gateway's body types, /// refer to [this documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html). +#[non_exhaustive] #[derive(Debug, Default, Eq, PartialEq)] pub enum Body { /// An empty body diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index f7520c30..8b308947 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -17,6 +17,7 @@ pub use self::http::*; pub type Error = Box; /// Binary data encoded in base64. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Base64Data( #[serde(deserialize_with = "deserialize_base64")] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index c7ca04a6..4e6d802b 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -7,6 +7,7 @@ use serde::{ use std::ops::{Deref, DerefMut}; /// Timestamp with millisecond precision. +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MillisecondTimestamp( #[serde(deserialize_with = "deserialize_milliseconds")] @@ -29,6 +30,7 @@ impl DerefMut for MillisecondTimestamp { } /// Timestamp with second precision. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondTimestamp( #[serde(deserialize_with = "deserialize_seconds")] @@ -51,6 +53,7 @@ impl DerefMut for SecondTimestamp { } /// Duration with second precision. +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondDuration( #[serde(deserialize_with = "deserialize_duration_seconds")] @@ -73,6 +76,7 @@ impl DerefMut for SecondDuration { } /// Duration with minute precision. +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 4bced0ab..60ef8568 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { @@ -22,6 +23,7 @@ pub struct ActiveMqEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqMessage { @@ -59,6 +61,7 @@ pub struct ActiveMqMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqDestination { diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 5ec4d242..2e89f588 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequest { @@ -44,6 +45,7 @@ pub struct AlbTargetGroupRequest { } /// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequestContext { @@ -58,6 +60,7 @@ pub struct AlbTargetGroupRequestContext { } /// `ElbContext` contains the information to identify the ARN invoking the lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ElbContext { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index c7a6175b..199447ec 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,6 +13,7 @@ use serde_json::Value; use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequest { @@ -82,6 +83,7 @@ pub struct ApiGatewayProxyResponse { /// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the /// Lambda function. It also includes Cognito identity information for the caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequestContext { @@ -134,6 +136,7 @@ pub struct ApiGatewayProxyRequestContext { } /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequest { @@ -191,6 +194,7 @@ pub struct ApiGatewayV2httpRequest { } /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContext { @@ -230,6 +234,7 @@ pub struct ApiGatewayV2httpRequestContext { } /// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct ApiGatewayRequestAuthorizer { #[serde(skip_serializing_if = "Option::is_none")] @@ -254,6 +259,7 @@ pub struct ApiGatewayRequestAuthorizer { } /// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerJwtDescription { @@ -272,6 +278,7 @@ pub struct ApiGatewayRequestAuthorizerJwtDescription { } /// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerIamDescription { @@ -299,6 +306,7 @@ pub struct ApiGatewayRequestAuthorizerIamDescription { } /// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerCognitoIdentity { @@ -317,6 +325,7 @@ pub struct ApiGatewayRequestAuthorizerCognitoIdentity { } /// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextHttpDescription { @@ -365,6 +374,7 @@ pub struct ApiGatewayV2httpResponse { } /// `ApiGatewayRequestIdentity` contains identity information for the request caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestIdentity { @@ -405,6 +415,7 @@ pub struct ApiGatewayRequestIdentity { } /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequest { @@ -453,6 +464,7 @@ pub struct ApiGatewayWebsocketProxyRequest { /// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify /// the AWS account and resources invoking the Lambda function. It also includes /// Cognito identity information for the caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequestContext { @@ -522,6 +534,7 @@ pub struct ApiGatewayWebsocketProxyRequestContext { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { @@ -543,6 +556,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { @@ -567,6 +581,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity { @@ -584,6 +599,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit } /// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthentication { @@ -599,6 +615,7 @@ pub struct ApiGatewayV2httpRequestContextAuthentication { } /// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { @@ -623,6 +640,7 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { } /// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { @@ -639,6 +657,7 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { @@ -669,6 +688,7 @@ pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV1Request { @@ -711,6 +731,7 @@ pub struct ApiGatewayV2CustomAuthorizerV1Request { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV2Request { @@ -755,6 +776,7 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { /// `ApiGatewayCustomAuthorizerContext` represents the expected format of an API Gateway custom authorizer response. /// Deprecated. Code should be updated to use the Authorizer map from APIGatewayRequestIdentity. Ex: Authorizer["principalId"] +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerContext { @@ -773,6 +795,7 @@ pub struct ApiGatewayCustomAuthorizerContext { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { @@ -808,6 +831,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { } /// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequest { @@ -828,6 +852,7 @@ pub struct ApiGatewayCustomAuthorizerRequest { } /// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { @@ -895,6 +920,7 @@ where } /// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerSimpleResponse @@ -914,6 +940,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerIamPolicyResponse diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 312843bd..223c706b 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate @@ -27,6 +28,7 @@ where } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIamIdentity { @@ -55,6 +57,7 @@ pub struct AppSyncIamIdentity { } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncCognitoIdentity @@ -87,6 +90,7 @@ where pub type AppSyncOperation = String; /// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequest { @@ -104,6 +108,7 @@ pub struct AppSyncLambdaAuthorizerRequest { /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered /// this authorization request. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequestContext @@ -136,6 +141,7 @@ where } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerResponse @@ -171,6 +177,7 @@ where /// /// See also: /// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncDirectResolverEvent where @@ -200,6 +207,7 @@ where /// `AppSyncRequest` contains request-related metadata for a resolver invocation, /// including client-sent headers and optional custom domain name. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncRequest { @@ -219,6 +227,7 @@ pub struct AppSyncRequest { } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncInfo @@ -243,6 +252,7 @@ where } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncPrevResult where @@ -261,6 +271,7 @@ where /// `AppSyncIdentity` represents the identity of the caller as determined by the /// configured AppSync authorization mechanism (IAM, Cognito, OIDC, or Lambda). +#[non_exhaustive] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(untagged, rename_all = "camelCase")] pub enum AppSyncIdentity { @@ -277,6 +288,7 @@ impl Default for AppSyncIdentity { } /// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncIdentityOIDC where @@ -296,6 +308,7 @@ where } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIdentityLambda diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index cbcde746..9a0eda8a 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingEvent diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index e7b4e69c..ce119914 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -4,6 +4,7 @@ use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from Agents for Amazon Bedrock. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AgentEvent { @@ -40,6 +41,7 @@ pub struct AgentEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct RequestBody { @@ -54,6 +56,7 @@ pub struct RequestBody { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Content { @@ -68,6 +71,7 @@ pub struct Content { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Property { @@ -86,6 +90,7 @@ pub struct Property { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Parameter { @@ -104,6 +109,7 @@ pub struct Parameter { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Agent { diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index 42b9ef0e..7a0990ef 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEvent { @@ -28,6 +29,7 @@ pub struct ChimeBotEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventSender { @@ -46,6 +48,7 @@ pub struct ChimeBotEventSender { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventDiscussion { @@ -64,6 +67,7 @@ pub struct ChimeBotEventDiscussion { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventInboundHttpsEndpoint { diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 89712834..3d6152c9 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { @@ -40,6 +41,7 @@ pub struct ClientVpnConnectionHandlerRequest { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerResponse { diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 84e793b0..3ea66e30 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; pub mod provider; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest @@ -25,6 +26,7 @@ impl Default for CloudFormationCustomResourceRequest { } } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest @@ -50,6 +52,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest @@ -79,6 +82,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest @@ -105,6 +109,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse { @@ -125,6 +130,7 @@ pub struct CloudFormationCustomResourceResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudFormationCustomResourceResponseStatus { diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index dd0043cd..34e8136f 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -7,6 +7,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest @@ -28,6 +29,7 @@ impl Default for CloudFormationCustomResourceRequest { } } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest @@ -39,6 +41,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest @@ -56,6 +59,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest @@ -69,6 +73,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CommonRequestParams @@ -90,6 +95,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index c3efebe3..46c9503a 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -12,6 +12,7 @@ use serde_json::Value; /// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. /// For examples of events that come via CloudWatch Alarms, /// see +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarm @@ -50,6 +51,7 @@ pub type CloudWatchMetricAlarm = pub type CloudWatchCompositeAlarm = CloudWatchAlarm; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmData @@ -75,6 +77,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmState @@ -99,6 +102,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricAlarmConfiguration { @@ -115,6 +119,7 @@ pub struct CloudWatchMetricAlarmConfiguration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricDefinition { @@ -131,6 +136,7 @@ pub struct CloudWatchMetricDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricStatDefinition { @@ -149,6 +155,7 @@ pub struct CloudWatchMetricStatDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricStatMetricDefinition { @@ -165,6 +172,7 @@ pub struct CloudWatchMetricStatMetricDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchCompositeAlarmConfiguration { @@ -181,6 +189,7 @@ pub struct CloudWatchCompositeAlarmConfiguration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudWatchAlarmStateValue { @@ -190,6 +199,7 @@ pub enum CloudWatchAlarmStateValue { InsufficientData, } +#[non_exhaustive] #[derive(Clone, Debug, PartialEq)] pub enum CloudWatchAlarmStateReasonData { Metric(CloudWatchAlarmStateReasonDataMetric), @@ -203,6 +213,7 @@ impl Default for CloudWatchAlarmStateReasonData { } } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateReasonDataMetric { @@ -234,6 +245,7 @@ pub struct CloudWatchAlarmStateReasonDataMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateEvaluatedDatapoint { @@ -253,6 +265,7 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClodWatchAlarmStateReasonDataComposite { @@ -267,6 +280,7 @@ pub struct ClodWatchAlarmStateReasonDataComposite { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateTriggeringAlarm { @@ -281,6 +295,7 @@ pub struct CloudWatchAlarmStateTriggeringAlarm { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateTriggeringAlarmState { diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 08b7500a..70275c4f 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -2,6 +2,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AWSAPICall { @@ -32,6 +33,7 @@ pub struct AWSAPICall { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionIssuer { @@ -49,6 +51,7 @@ pub struct SessionIssuer { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct WebIdFederationData { @@ -63,6 +66,7 @@ pub struct WebIdFederationData { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attributes { @@ -77,6 +81,7 @@ pub struct Attributes { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionContext { @@ -94,6 +99,7 @@ pub struct SessionContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OnBehalfOf { @@ -109,6 +115,7 @@ pub struct OnBehalfOf { } // https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index b52528ca..f01a5bba 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StateChangeNotification { @@ -20,6 +21,7 @@ pub struct StateChangeNotification { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeploymentStateChangeNotification { @@ -39,6 +41,7 @@ pub struct DeploymentStateChangeNotification { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChangeNotification { diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index e99798e7..89f2029c 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PipelineExecutionStateChange { @@ -19,6 +20,7 @@ pub struct PipelineExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StageExecutionStateChange { @@ -37,6 +39,7 @@ pub struct StageExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionExecutionStateChange { @@ -59,6 +62,7 @@ pub struct ActionExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionExecutionStateChangeType { diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c77fab4f..378b64f1 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 4aed4818..f30d5334 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingPolicyStateChange { @@ -19,6 +20,7 @@ pub struct AutoScalingPolicyStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClusterStateChange { @@ -37,6 +39,7 @@ pub struct ClusterStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceGroupStateChange { @@ -59,6 +62,7 @@ pub struct InstanceGroupStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StepStatusChange { diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index 009f1056..95678329 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -4,6 +4,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingSearching { @@ -20,6 +21,7 @@ pub struct MatchmakingSearching { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Ticket { @@ -35,6 +37,7 @@ pub struct Ticket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Player { @@ -52,6 +55,7 @@ pub struct Player { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GameSessionInfo { @@ -65,6 +69,7 @@ pub struct GameSessionInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PotentialMatchCreated { @@ -84,6 +89,7 @@ pub struct PotentialMatchCreated { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct RuleEvaluationMetric { @@ -99,6 +105,7 @@ pub struct RuleEvaluationMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AcceptMatch { @@ -115,6 +122,7 @@ pub struct AcceptMatch { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AcceptMatchCompleted { @@ -132,6 +140,7 @@ pub struct AcceptMatchCompleted { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingSucceeded { @@ -148,6 +157,7 @@ pub struct MatchmakingSucceeded { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingTimedOut { @@ -166,6 +176,7 @@ pub struct MatchmakingTimedOut { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingCancelled { @@ -184,6 +195,7 @@ pub struct MatchmakingCancelled { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingFailed { diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index 21669098..69d428b2 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobRunStateChange { @@ -19,6 +20,7 @@ pub struct JobRunStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerStarted { @@ -36,6 +38,7 @@ pub struct CrawlerStarted { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerSucceeded { @@ -63,6 +66,7 @@ pub struct CrawlerSucceeded { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerFailed { @@ -81,6 +85,7 @@ pub struct CrawlerFailed { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobRunStatus { @@ -100,6 +105,7 @@ pub struct JobRunStatus { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct NotificationCondition { @@ -114,6 +120,7 @@ pub struct NotificationCondition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DataCatalogTableStateChange { @@ -130,6 +137,7 @@ pub struct DataCatalogTableStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DataCatalogDatabaseStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index c6d8cca1..af5ebfc4 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Event { @@ -23,6 +24,7 @@ pub struct Event { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventDescription { @@ -37,6 +39,7 @@ pub struct EventDescription { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Entity { diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index 2a0041de..c96d2be5 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CMKEvent { diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index a652b8b2..7a1a989b 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { @@ -34,6 +35,7 @@ pub type BucketScanAlert = Alert; pub type BucketWritableAlert = Alert; pub type BucketContainsHighRiskObjectAlert = Alert; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Trigger { @@ -54,6 +56,7 @@ pub struct Trigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketScanSummary { @@ -83,6 +86,7 @@ pub struct BucketScanSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Ip { @@ -101,6 +105,7 @@ pub struct Ip { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeRange { @@ -116,6 +121,7 @@ pub struct TimeRange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Location { @@ -130,6 +136,7 @@ pub struct Location { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionInfo { @@ -146,6 +153,7 @@ pub struct ActionInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketWritableSummary { @@ -170,6 +178,7 @@ pub struct BucketWritableSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Bucket { @@ -184,6 +193,7 @@ pub struct Bucket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Acl { @@ -198,6 +208,7 @@ pub struct Acl { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SecretBucketName { @@ -214,6 +225,7 @@ pub struct SecretBucketName { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Owner { @@ -230,6 +242,7 @@ pub struct Owner { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Grant { @@ -246,6 +259,7 @@ pub struct Grant { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Grantee { @@ -261,6 +275,7 @@ pub struct Grantee { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketContainsHighRiskObjectSummary { @@ -289,6 +304,7 @@ pub struct BucketContainsHighRiskObjectSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlertUpdated { @@ -314,6 +330,7 @@ pub struct AlertUpdated { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdatedTrigger { @@ -329,6 +346,7 @@ pub struct UpdatedTrigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct FeatureInfo { diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index c865b0e0..91f7e7fd 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -21,6 +21,7 @@ pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. /// For examples of events that come via CloudWatch Events, see +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index 30818648..7c26baaf 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChange { @@ -26,6 +27,7 @@ pub struct InstanceStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CommandStateChange { @@ -44,6 +46,7 @@ pub struct CommandStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeploymentStateChange { @@ -65,6 +68,7 @@ pub struct DeploymentStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 2eb1ea5e..458787bd 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SignIn { @@ -28,6 +29,7 @@ pub struct SignIn { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -44,6 +46,7 @@ pub struct UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ResponseElements { @@ -58,6 +61,7 @@ pub struct ResponseElements { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalEventData { diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 6447887a..e3bfeeb4 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index 53a48e10..1bf81ed2 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationStepStatusChange { @@ -33,6 +34,7 @@ pub struct EC2AutomationStepStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationExecutionStatusChange { @@ -61,6 +63,7 @@ pub struct EC2AutomationExecutionStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StateChange { @@ -76,6 +79,7 @@ pub struct StateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigurationComplianceStateChange { @@ -101,6 +105,7 @@ pub struct ConfigurationComplianceStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTargetRegistration { @@ -118,6 +123,7 @@ pub struct MaintenanceWindowTargetRegistration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowExecutionStateChange { @@ -139,6 +145,7 @@ pub struct MaintenanceWindowExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTaskExecutionStateChange { @@ -162,6 +169,7 @@ pub struct MaintenanceWindowTaskExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTaskTargetInvocationStateChange { @@ -189,6 +197,7 @@ pub struct MaintenanceWindowTaskTargetInvocationStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowStateChange { @@ -204,6 +213,7 @@ pub struct MaintenanceWindowStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ParameterStoreStateChange { @@ -220,6 +230,7 @@ pub struct ParameterStoreStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2CommandStatusChange { @@ -242,6 +253,7 @@ pub struct EC2CommandStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2CommandInvocationStatusChange { @@ -263,6 +275,7 @@ pub struct EC2CommandInvocationStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2StateManagerAssociationStateChange { @@ -299,6 +312,7 @@ pub struct EC2StateManagerAssociationStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2StateManagerInstanceAssociationStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index 08fbe24c..e6d3b96f 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TagChangeOnResource { diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index 19f2aba0..fdaf4a82 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CheckItemRefreshNotification { diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 1a485a06..e494918a 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -10,6 +10,7 @@ use serde_json::Value; use std::{fmt, io::BufReader}; /// `LogsEvent` represents the raw event sent by CloudWatch +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct LogsEvent { /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer @@ -25,6 +26,7 @@ pub struct LogsEvent { } /// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event +#[non_exhaustive] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AwsLogs { /// `data` is the log data after is decompressed @@ -32,6 +34,7 @@ pub struct AwsLogs { } /// `LogData` represents the logs group event information +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct LogData { @@ -57,6 +60,7 @@ pub struct LogData { } /// `LogEntry` represents a log entry from cloudwatch logs +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct LogEntry { /// Unique id for the entry diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 021f8942..126d7160 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; /// `CodeCommitEvent` represents a CodeCommit event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitEvent { @@ -23,6 +24,7 @@ pub struct CodeCommitEvent { pub type CodeCommitEventTime = DateTime; /// `CodeCommitRecord` represents a CodeCommit record +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitRecord { @@ -63,6 +65,7 @@ pub struct CodeCommitRecord { } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { @@ -80,6 +83,7 @@ pub struct CodeCommitCodeCommit { } /// `CodeCommitReference` represents a Reference object in a CodeCommit object +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitReference { diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 304cf465..27a0e060 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -12,6 +12,7 @@ pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { @@ -54,6 +55,7 @@ pub struct CodeBuildEvent { } /// `CodeBuildEventDetail` represents the all details related to the code build event +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventDetail { @@ -101,6 +103,7 @@ pub struct CodeBuildEventDetail { } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventAdditionalInformation { @@ -133,6 +136,7 @@ pub struct CodeBuildEventAdditionalInformation { } /// `CodeBuildArtifact` represents the artifact provided to build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildArtifact { @@ -154,6 +158,7 @@ pub struct CodeBuildArtifact { } /// `CodeBuildEnvironment` represents the environment for a build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironment { @@ -178,6 +183,7 @@ pub struct CodeBuildEnvironment { } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironmentVariable { @@ -200,6 +206,7 @@ pub struct CodeBuildEnvironmentVariable { } /// `CodeBuildSource` represent the code source will be build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildSource { @@ -217,6 +224,7 @@ pub struct CodeBuildSource { } /// `CodeBuildLogs` gives the log details of a code build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildLogs { @@ -239,6 +247,7 @@ pub struct CodeBuildLogs { } /// `CodeBuildPhase` represents the phase of a build and its details +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildPhase diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 85649729..debe2bf5 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -7,6 +7,7 @@ pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { @@ -49,6 +50,7 @@ pub struct CodeDeployEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEventDetail { @@ -81,6 +83,7 @@ pub struct CodeDeployEventDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct CodeDeployLifecycleEvent { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 3bcc5f2b..087d1452 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -11,6 +11,7 @@ pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { @@ -53,6 +54,7 @@ pub struct CodePipelineCloudWatchEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetail { @@ -80,6 +82,7 @@ pub struct CodePipelineEventDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetailType { diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 41a9966e..83dfce65 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJobEvent { @@ -18,6 +19,7 @@ pub struct CodePipelineJobEvent { } /// `CodePipelineJob` represents a job from an AWS CodePipeline event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJob { @@ -36,6 +38,7 @@ pub struct CodePipelineJob { } /// `CodePipelineData` represents a job from an AWS CodePipeline event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineData { @@ -56,6 +59,7 @@ pub struct CodePipelineData { } /// `CodePipelineActionConfiguration` represents an Action Configuration +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { @@ -70,6 +74,7 @@ pub struct CodePipelineActionConfiguration { } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineConfiguration { @@ -89,6 +94,7 @@ pub struct CodePipelineConfiguration { } /// `CodePipelineInputArtifact` represents an input artifact +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputArtifact { @@ -106,6 +112,7 @@ pub struct CodePipelineInputArtifact { } /// `CodePipelineInputLocation` represents a input location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputLocation { @@ -123,6 +130,7 @@ pub struct CodePipelineInputLocation { } /// `CodePipelineS3Location` represents an s3 input location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineS3Location { @@ -140,6 +148,7 @@ pub struct CodePipelineS3Location { } /// `CodePipelineOutputArtifact` represents an output artifact +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputArtifact { @@ -157,6 +166,7 @@ pub struct CodePipelineOutputArtifact { } /// `CodePipelineOutputLocation` represents a output location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputLocation { @@ -174,6 +184,7 @@ pub struct CodePipelineOutputLocation { } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineArtifactCredentials { diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 315bc2ff..d656bb59 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// `CognitoEvent` contains data from an event sent from AWS Cognito Sync +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEvent { @@ -32,6 +33,7 @@ pub struct CognitoEvent { } /// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoDatasetRecord { @@ -52,6 +54,7 @@ pub struct CognitoDatasetRecord { /// `CognitoEventUserPoolsPreSignup` is sent by AWS Cognito User Pools when a user attempts to register /// (sign up), allowing a Lambda to perform custom validation to accept or deny the registration request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignup { @@ -69,6 +72,7 @@ pub struct CognitoEventUserPoolsPreSignup { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreSignupTriggerSource { #[serde(rename = "PreSignUp_SignUp")] @@ -82,6 +86,7 @@ pub enum CognitoEventUserPoolsPreSignupTriggerSource { /// `CognitoEventUserPoolsPreAuthentication` is sent by AWS Cognito User Pools when a user submits their information /// to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreAuthentication { @@ -100,6 +105,7 @@ pub struct CognitoEventUserPoolsPreAuthentication { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { #[serde(rename = "PreAuthentication_Authentication")] @@ -109,6 +115,7 @@ pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { /// `CognitoEventUserPoolsPostConfirmation` is sent by AWS Cognito User Pools after a user is confirmed, /// allowing the Lambda to send custom messages or add custom logic. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostConfirmation @@ -132,6 +139,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { #[serde(rename = "PostConfirmation_ConfirmForgotPassword")] @@ -143,6 +151,7 @@ pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { /// `CognitoEventUserPoolsPreTokenGen` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGen { @@ -160,6 +169,7 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { #[serde(rename = "TokenGeneration_HostedAuth")] @@ -177,6 +187,7 @@ pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { /// `CognitoEventUserPoolsPostAuthentication` is sent by AWS Cognito User Pools after a user is authenticated, /// allowing the Lambda to add custom logic. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostAuthentication { @@ -195,6 +206,7 @@ pub struct CognitoEventUserPoolsPostAuthentication { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { #[serde(rename = "PostAuthentication_Authentication")] @@ -204,6 +216,7 @@ pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { /// `CognitoEventUserPoolsMigrateUser` is sent by AWS Cognito User Pools when a user does not exist in the /// user pool at the time of sign-in with a password, or in the forgot-password flow. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUser { @@ -223,6 +236,7 @@ pub struct CognitoEventUserPoolsMigrateUser { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsMigrateUserTriggerSource { #[serde(rename = "UserMigration_Authentication")] @@ -233,6 +247,7 @@ pub enum CognitoEventUserPoolsMigrateUserTriggerSource { } /// `CognitoEventUserPoolsCallerContext` contains information about the caller +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCallerContext { @@ -251,6 +266,7 @@ pub struct CognitoEventUserPoolsCallerContext { } /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsHeader { @@ -271,6 +287,7 @@ pub struct CognitoEventUserPoolsHeader { } /// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignupRequest { @@ -293,6 +310,7 @@ pub struct CognitoEventUserPoolsPreSignupRequest { } /// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignupResponse { @@ -309,6 +327,7 @@ pub struct CognitoEventUserPoolsPreSignupResponse { } /// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreAuthenticationRequest { @@ -328,6 +347,7 @@ pub struct CognitoEventUserPoolsPreAuthenticationRequest { } /// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPreAuthenticationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -339,6 +359,7 @@ pub struct CognitoEventUserPoolsPreAuthenticationResponse { pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostConfirmationRequest { @@ -358,6 +379,7 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { } /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostConfirmationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -370,6 +392,7 @@ pub struct CognitoEventUserPoolsPostConfirmationResponse { } /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenRequest { @@ -390,6 +413,7 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { } /// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponse { @@ -405,6 +429,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { /// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims. This is the Version 2 Payload +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenV2 { @@ -423,6 +448,7 @@ pub struct CognitoEventUserPoolsPreTokenGenV2 { } /// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { @@ -443,6 +469,7 @@ pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { @@ -457,6 +484,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { } /// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClaimsAndScopeOverrideDetailsV2 { @@ -473,6 +501,7 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { } /// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoIdTokenGenerationV2 { @@ -488,6 +517,7 @@ pub struct CognitoIdTokenGenerationV2 { } /// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoAccessTokenGenerationV2 { @@ -505,6 +535,7 @@ pub struct CognitoAccessTokenGenerationV2 { } /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostAuthenticationRequest { @@ -525,6 +556,7 @@ pub struct CognitoEventUserPoolsPostAuthenticationRequest { } /// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostAuthenticationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -536,6 +568,7 @@ pub struct CognitoEventUserPoolsPostAuthenticationResponse { pub other: serde_json::Map, } /// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUserRequest { @@ -557,6 +590,7 @@ pub struct CognitoEventUserPoolsMigrateUserRequest { } /// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUserResponse { @@ -581,6 +615,7 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { } /// `ClaimsOverrideDetails` allows lambda to add, suppress or override claims in the token +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClaimsOverrideDetails { @@ -599,6 +634,7 @@ pub struct ClaimsOverrideDetails { } /// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GroupConfiguration { @@ -616,6 +652,7 @@ pub struct GroupConfiguration { /// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication /// process that is underway, along with the corresponding result. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsChallengeResult { @@ -634,6 +671,7 @@ pub struct CognitoEventUserPoolsChallengeResult { } /// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { @@ -656,6 +694,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { } /// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { @@ -675,6 +714,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { } /// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallenge { @@ -693,6 +733,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { #[serde(rename = "DefineAuthChallenge_Authentication")] @@ -701,6 +742,7 @@ pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { } /// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { @@ -725,6 +767,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { @@ -746,6 +789,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { } /// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallenge { @@ -764,6 +808,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { #[serde(rename = "CreateAuthChallenge_Authentication")] @@ -772,6 +817,7 @@ pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { } /// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeRequest @@ -802,6 +848,7 @@ where } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { @@ -818,6 +865,7 @@ pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { /// `CognitoEventUserPoolsVerifyAuthChallenge` sent by AWS Cognito User Pools to verify if the response from the end user /// for a custom Auth Challenge is valid or not +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallenge { @@ -836,6 +884,7 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { #[serde(rename = "VerifyAuthChallengeResponse_Authentication")] @@ -845,6 +894,7 @@ pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { /// `CognitoEventUserPoolsCustomMessage` is sent by AWS Cognito User Pools before a verification or MFA message is sent, /// allowing a user to customize the message dynamically. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessage { @@ -862,6 +912,7 @@ pub struct CognitoEventUserPoolsCustomMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsCustomMessageTriggerSource { #[serde(rename = "CustomMessage_SignUp")] @@ -882,6 +933,7 @@ pub enum CognitoEventUserPoolsCustomMessageTriggerSource { } /// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessageRequest @@ -910,6 +962,7 @@ where } /// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessageResponse { diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 981419d8..dda9afa3 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigEvent { diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 3f15ce0c..b5f5fe3d 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `ConnectEvent` contains the data structure for a Connect event. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEvent { @@ -25,6 +26,7 @@ pub struct ConnectEvent { } /// `ConnectDetails` holds the details of a Connect event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectDetails { @@ -45,6 +47,7 @@ pub struct ConnectDetails { } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectContactData { @@ -88,6 +91,7 @@ pub struct ConnectContactData { } /// `ConnectEndpoint` represents routing information. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEndpoint { @@ -107,6 +111,7 @@ pub struct ConnectEndpoint { } /// `ConnectQueue` represents a queue object. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectQueue { diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs index d9ff1c4d..9624901d 100644 --- a/lambda-events/src/event/documentdb/events/commom_types.rs +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -5,6 +5,7 @@ use serde_json::Value; pub type AnyDocument = HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DatabaseCollection { @@ -20,6 +21,7 @@ pub struct DatabaseCollection { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentId { #[serde(rename = "_data")] @@ -33,6 +35,7 @@ pub struct DocumentId { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyIdOid { #[serde(rename = "$oid")] @@ -46,6 +49,7 @@ pub struct DocumentKeyIdOid { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyId { #[serde(rename = "_id")] @@ -59,6 +63,7 @@ pub struct DocumentKeyId { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct InnerTimestamp { t: usize, @@ -72,6 +77,7 @@ pub struct InnerTimestamp { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Timestamp { #[serde(rename = "$timestamp")] diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs index 4085a88b..1c2e6afe 100644 --- a/lambda-events/src/event/documentdb/events/delete_event.rs +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDeleteEvent { diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index fed75259..2506f9cb 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDropDatabaseEvent { diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index 23a8d7a8..42bb566b 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDropEvent { diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index aaf82ddc..0c139220 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeInsertEvent { diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs index 8e8ef930..f721013e 100644 --- a/lambda-events/src/event/documentdb/events/invalidate_event.rs +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeInvalidateEvent { diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs index f1761a2a..75797aca 100644 --- a/lambda-events/src/event/documentdb/events/rename_event.rs +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeRenameEvent { diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index b0245241..0ff6ffba 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeReplaceEvent { diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 5d3795d0..8f6a48a9 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateTruncate { @@ -18,6 +19,7 @@ pub struct UpdateTruncate { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateDescription { @@ -33,6 +35,7 @@ pub struct UpdateDescription { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeUpdateEvent { diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs index fa753823..c802db62 100644 --- a/lambda-events/src/event/documentdb/mod.rs +++ b/lambda-events/src/event/documentdb/mod.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(tag = "operationType", rename_all = "camelCase")] pub enum ChangeEvent { @@ -22,6 +23,7 @@ pub enum ChangeEvent { Rename(ChangeRenameEvent), } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentDbInnerEvent { pub event: ChangeEvent, @@ -34,6 +36,7 @@ pub struct DocumentDbInnerEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DocumentDbEvent { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 316768dc..62872f3e 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -12,8 +12,8 @@ use std::fmt; #[cfg(test)] mod attributes; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, @@ -35,8 +35,8 @@ impl fmt::Display for StreamViewType { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, @@ -58,8 +58,8 @@ impl fmt::Display for StreamStatus { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, @@ -81,8 +81,8 @@ impl fmt::Display for SharedIteratorType { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { #[default] @@ -102,8 +102,8 @@ impl fmt::Display for OperationType { } } -#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { #[default] @@ -123,6 +123,7 @@ impl fmt::Display for KeyType { /// The `Event` stream event handled to Lambda /// +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] @@ -138,6 +139,7 @@ pub struct Event { /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { @@ -157,6 +159,7 @@ pub struct TimeWindowEvent { } /// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponse { @@ -174,6 +177,7 @@ pub struct TimeWindowEventResponse { } /// EventRecord stores information about each record of a DynamoDb stream event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventRecord { @@ -240,6 +244,7 @@ pub struct EventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -258,6 +263,7 @@ pub struct UserIdentity { /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item /// in a DynamoDB table. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StreamRecord { diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index e3ff7ff8..b28678f4 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { @@ -31,6 +32,7 @@ pub struct EcrScanEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventDetailType { @@ -56,6 +58,7 @@ pub struct EcrScanEventDetailType { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index f2d86550..824de1ef 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// /// See for structure details. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index a97577f5..d0dec03d 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -8,6 +8,7 @@ use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEvent { @@ -31,6 +32,7 @@ pub struct KinesisFirehoseEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEventRecord { @@ -49,6 +51,7 @@ pub struct KinesisFirehoseEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { @@ -62,6 +65,7 @@ pub struct KinesisFirehoseResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecord { @@ -81,6 +85,7 @@ pub struct KinesisFirehoseResponseRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecordMetadata { @@ -96,6 +101,7 @@ pub struct KinesisFirehoseResponseRecordMetadata { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseRecordMetadata { diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 42c8a9c7..29ef203e 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -8,6 +8,7 @@ use serde::{ }; /// `IamPolicyDocument` represents an IAM policy document. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { @@ -47,6 +48,7 @@ pub struct IamPolicyStatement { pub type IamPolicyCondition = HashMap>>; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub enum IamPolicyEffect { #[default] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index f544510f..e1d7b504 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { @@ -24,6 +25,7 @@ pub struct IoTCoreCustomAuthorizerRequest { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreProtocolData { @@ -39,6 +41,7 @@ pub struct IoTCoreProtocolData { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreTlsContext { @@ -53,6 +56,7 @@ pub struct IoTCoreTlsContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreHttpContext { @@ -70,6 +74,7 @@ pub struct IoTCoreHttpContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreMqttContext { @@ -87,6 +92,7 @@ pub struct IoTCoreMqttContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreConnectionMetadata { @@ -103,6 +109,7 @@ pub struct IoTCoreConnectionMetadata { /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. /// See +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 50338120..a88041c0 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -7,6 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickEvent { @@ -22,6 +23,7 @@ pub struct IoTOneClickEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { @@ -35,6 +37,7 @@ pub struct IoTOneClickDeviceEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickButtonClicked { @@ -51,6 +54,7 @@ pub struct IoTOneClickButtonClicked { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceInfo { @@ -71,6 +75,7 @@ pub struct IoTOneClickDeviceInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickPlacementInfo { diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 9a7aaec3..70a24f9d 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 30142606..22b13db9 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -5,6 +5,7 @@ use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerRequest { @@ -33,6 +34,7 @@ pub type IoTTlsContext = IoTCoreTlsContext; /// `IoTCustomAuthorizerResponse` represents the expected format of an IoT device gateway authorization response. /// Deprecated: Use IoTCoreCustomAuthorizerResponse. `IoTCustomAuthorizerResponse` does not correctly model the response schema. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerResponse { diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 7332b1e0..ecd02d87 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaEvent { @@ -25,6 +26,7 @@ pub struct KafkaEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaRecord { diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index e9f8f676..9ca62f69 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryEvent { @@ -20,6 +21,7 @@ pub struct KinesisAnalyticsOutputDeliveryEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryEventRecord { @@ -35,6 +37,7 @@ pub struct KinesisAnalyticsOutputDeliveryEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponse { @@ -48,6 +51,7 @@ pub struct KinesisAnalyticsOutputDeliveryResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponseRecord { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 6bfb2bea..215e9288 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { @@ -22,6 +23,7 @@ pub struct KinesisEvent { /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { @@ -41,6 +43,7 @@ pub struct KinesisTimeWindowEvent { } /// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEventResponse { @@ -57,6 +60,7 @@ pub struct KinesisTimeWindowEventResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventRecord { @@ -89,6 +93,7 @@ pub struct KinesisEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisRecord { @@ -111,6 +116,7 @@ pub struct KinesisRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KinesisEncryptionType { diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index 646d141a..f5771623 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequest { @@ -37,6 +38,7 @@ pub struct LambdaFunctionUrlRequest { } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContext { @@ -69,6 +71,7 @@ pub struct LambdaFunctionUrlRequestContext { } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { @@ -83,6 +86,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { @@ -106,6 +110,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextHttpDescription { @@ -129,6 +134,7 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlResponse { diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index 46258951..b64279d7 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { @@ -31,6 +32,7 @@ pub struct LexEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexBot { @@ -46,6 +48,7 @@ pub struct LexBot { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexCurrentIntent { @@ -65,6 +68,7 @@ pub struct LexCurrentIntent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexAlternativeIntents { @@ -84,6 +88,7 @@ pub struct LexAlternativeIntents { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SlotDetail { @@ -98,6 +103,7 @@ pub struct SlotDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexDialogAction { @@ -123,6 +129,7 @@ pub type SessionAttributes = HashMap; pub type Slots = HashMap>; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponse { @@ -137,6 +144,7 @@ pub struct LexResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponseCard { @@ -152,6 +160,7 @@ pub struct LexResponseCard { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attachment { diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index e1a7256b..5b75e3c2 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { @@ -24,6 +25,7 @@ pub struct RabbitMqEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqMessage { @@ -40,6 +42,7 @@ pub struct RabbitMqMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqBasicProperties diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index 133960f3..9b8f419b 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobEvent { @@ -22,6 +23,7 @@ pub struct S3BatchJobEvent { } /// `S3BatchJob` whichs have the job id +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJob { @@ -37,6 +39,7 @@ pub struct S3BatchJob { } /// `S3BatchJobTask` represents one task in the s3 batch job and have all task details +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobTask { @@ -58,6 +61,7 @@ pub struct S3BatchJobTask { } /// `S3BatchJobResponse` is the response of a iven s3 batch job with the results +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobResponse { @@ -78,6 +82,7 @@ pub struct S3BatchJobResponse { } /// `S3BatchJobResult` represents the result of a given task +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobResult { diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 46a334db..68b9aee7 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `S3Event` which wrap an array of `S3Event`Record +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Event { @@ -22,6 +23,7 @@ pub struct S3Event { } /// `S3EventRecord` which wrap record data +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3EventRecord { @@ -50,6 +52,7 @@ pub struct S3EventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3UserIdentity { @@ -64,6 +67,7 @@ pub struct S3UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3RequestParameters { @@ -79,6 +83,7 @@ pub struct S3RequestParameters { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Entity { @@ -98,6 +103,7 @@ pub struct S3Entity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Bucket { @@ -116,6 +122,7 @@ pub struct S3Bucket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Object { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 3b01fe73..be52e1f8 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -7,6 +7,7 @@ use crate::custom_serde::{deserialize_headers, serialize_headers}; /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas /// See: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3ObjectLambdaEvent

@@ -35,6 +36,7 @@ where /// `GetObjectContext` contains the input and output details /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetObjectContext { @@ -52,6 +54,7 @@ pub struct GetObjectContext { /// `HeadObjectContext` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct HeadObjectContext { @@ -67,6 +70,7 @@ pub struct HeadObjectContext { /// `ListObjectsContext` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ListObjectsContext { @@ -82,6 +86,7 @@ pub struct ListObjectsContext { /// `ListObjectsV2Context` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ListObjectsV2Context { @@ -96,6 +101,7 @@ pub struct ListObjectsV2Context { } /// `Configuration` contains information about the Object Lambda access point +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Configuration

@@ -117,6 +123,7 @@ where } /// `UserRequest` contains information about the original call to S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserRequest { @@ -134,6 +141,7 @@ pub struct UserRequest { } /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -152,6 +160,7 @@ pub struct UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionContext { @@ -167,6 +176,7 @@ pub struct SessionContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionIssuer { diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index fc883e52..9502d654 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SecretsManagerSecretRotationEvent { diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 20498780..31a55ea3 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailEvent { @@ -18,6 +19,7 @@ pub struct SimpleEmailEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailRecord { @@ -35,6 +37,7 @@ pub struct SimpleEmailRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailService { @@ -50,6 +53,7 @@ pub struct SimpleEmailService { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailMessage { @@ -71,6 +75,7 @@ pub struct SimpleEmailMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceipt { @@ -94,6 +99,7 @@ pub struct SimpleEmailReceipt { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailHeader { @@ -110,6 +116,7 @@ pub struct SimpleEmailHeader { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailCommonHeaders { @@ -136,6 +143,7 @@ pub struct SimpleEmailCommonHeaders { /// Types. For example, the FunctionARN and InvocationType fields are only /// present for the Lambda Type, and the BucketName and ObjectKey fields are only /// present for the S3 Type. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceiptAction { @@ -160,6 +168,7 @@ pub struct SimpleEmailReceiptAction { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailVerdict { @@ -177,6 +186,7 @@ pub struct SimpleEmailVerdict { pub type SimpleEmailDispositionValue = String; /// `SimpleEmailDisposition` disposition return for SES to control rule functions +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 163c13ac..d40dd45c 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -9,6 +9,7 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsEvent { @@ -24,6 +25,7 @@ pub struct SnsEvent { } /// SnsRecord stores information about each record of a SNS event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsRecord { @@ -49,6 +51,7 @@ pub struct SnsRecord { } /// SnsMessage stores information about each record of a SNS event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsMessage { @@ -107,6 +110,7 @@ pub struct SnsMessage { /// An alternate `Event` notification event to use alongside `SnsRecordObj` and `SnsMessageObj` if you want to deserialize an object inside your SNS messages rather than getting an `Option` message /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -123,6 +127,7 @@ pub struct SnsEventObj { } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -149,6 +154,7 @@ pub struct SnsRecordObj { } /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] @@ -213,6 +219,7 @@ pub struct SnsMessageObj { /// Message attributes are optional and separate from—but are sent together with—the message body. The receiver can use this information to decide how to handle the message without having to process the message body first. /// /// Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct MessageAttribute { /// The data type of the attribute. Per the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html), lambda notifications, this will only be **String** or **Binary**. @@ -232,6 +239,7 @@ pub struct MessageAttribute { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchAlarmPayload { @@ -255,6 +263,7 @@ pub struct CloudWatchAlarmPayload { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchAlarmTrigger { @@ -282,6 +291,7 @@ pub struct CloudWatchAlarmTrigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetricDataQuery { @@ -301,6 +311,7 @@ pub struct CloudWatchMetricDataQuery { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetricStat { @@ -317,6 +328,7 @@ pub struct CloudWatchMetricStat { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetric { @@ -333,6 +345,7 @@ pub struct CloudWatchMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CloudWatchDimension { pub name: String, diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 64a368ca..0a380734 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -5,6 +5,7 @@ use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEvent { @@ -20,6 +21,7 @@ pub struct SqsEvent { } /// An individual SQS Message, its metadata, and Message Attributes +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsMessage { @@ -57,6 +59,7 @@ pub struct SqsMessage { } /// Alternative to `SqsEvent` to be used alongside `SqsMessageObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -74,6 +77,7 @@ pub struct SqsEventObj { } /// Alternative to `SqsMessage` to be used alongside `SqsEventObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -115,6 +119,7 @@ pub struct SqsMessageObj { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsMessageAttribute { @@ -135,6 +140,7 @@ pub struct SqsMessageAttribute { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchResponse { @@ -148,6 +154,7 @@ pub struct SqsBatchResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BatchItemFailure { @@ -162,6 +169,7 @@ pub struct BatchItemFailure { } /// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -178,6 +186,7 @@ pub struct SqsApiEventObj { } /// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsApiEvent { @@ -194,6 +203,7 @@ pub struct SqsApiEvent { /// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to /// deserialize a nested object into a struct of type T within the SQS Message rather /// than just using the raw SQS Message string +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -228,6 +238,7 @@ pub struct SqsApiMessageObj { } /// An individual SQS API Message, its metadata, and Message Attributes +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SqsApiMessage { diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index caf2c02d..9f2391c8 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { @@ -17,6 +18,7 @@ pub struct KinesisEventResponse { } /// `KinesisBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisBatchItemFailure { @@ -32,6 +34,7 @@ pub struct KinesisBatchItemFailure { } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { @@ -46,6 +49,7 @@ pub struct DynamoDbEventResponse { } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbBatchItemFailure { @@ -61,6 +65,7 @@ pub struct DynamoDbBatchItemFailure { } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { @@ -75,6 +80,7 @@ pub struct SqsEventResponse { } /// `SqsBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchItemFailure { diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 424050ab..d67a8e91 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -7,6 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `Window` is the object that captures the time window for the records in the event when using the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Window { @@ -26,6 +27,7 @@ impl Default for Window { /// `TimeWindowProperties` is the object that captures properties that relate to the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowProperties { @@ -53,6 +55,7 @@ pub struct TimeWindowProperties { /// `TimeWindowEventResponseProperties` is the object that captures response properties that relate to the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponseProperties { diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 8b868d25..9b6208a3 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -62,6 +62,7 @@ impl LambdaResponse { Body::Empty => (false, None), b @ Body::Text(_) => (false, Some(b)), b @ Body::Binary(_) => (true, Some(b)), + _ => (false, None), }; let headers = parts.headers; @@ -79,7 +80,7 @@ impl LambdaResponse { multi_value_headers: headers, // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + other: Default::default() }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { From bfc73a63b44582f64b01cd5be506f4d81be313d3 Mon Sep 17 00:00:00 2001 From: mpindaru <102007013+mpindaru@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:21:36 +0100 Subject: [PATCH 394/394] feat(lambda-events, lambda-http): mark all public structs/enums as #[non_exhaustive] (#1046) Co-authored-by: Mara Pindaru --- lambda-events/src/event/alb/mod.rs | 1 + lambda-events/src/event/apigw/mod.rs | 4 ++ lambda-events/src/event/iam/mod.rs | 1 + lambda-http/src/ext/extensions.rs | 4 ++ lambda-http/src/ext/request.rs | 6 +- lambda-http/src/lib.rs | 2 + lambda-http/src/request.rs | 3 + lambda-http/src/response.rs | 81 +++++++++++++--------- lambda-http/src/streaming.rs | 2 + lambda-integration-tests/src/authorizer.rs | 44 +++++++----- lambda-integration-tests/src/helloworld.rs | 18 +++-- 11 files changed, 108 insertions(+), 58 deletions(-) diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 2e89f588..1829bf01 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -77,6 +77,7 @@ pub struct ElbContext { } /// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupResponse { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 199447ec..015eff40 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -58,6 +58,7 @@ pub struct ApiGatewayProxyRequest { } /// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyResponse { @@ -349,6 +350,7 @@ pub struct ApiGatewayV2httpRequestContextHttpDescription { } /// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpResponse { @@ -897,6 +899,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { } /// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerResponse @@ -963,6 +966,7 @@ where } /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct ApiGatewayCustomAuthorizerPolicy { diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 29ef203e..fd190950 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -25,6 +25,7 @@ pub struct IamPolicyDocument { } /// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyStatement { diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index cfbdaec2..65bf8ac0 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -7,12 +7,14 @@ use lambda_runtime::Context; use crate::request::RequestContext; /// ALB/API gateway pre-parsed http query string parameters +#[non_exhaustive] #[derive(Clone)] pub(crate) struct QueryStringParameters(pub(crate) QueryMap); /// API gateway pre-extracted url path parameters /// /// These will always be empty for ALB requests +#[non_exhaustive] #[derive(Clone)] pub(crate) struct PathParameters(pub(crate) QueryMap); @@ -20,10 +22,12 @@ pub(crate) struct PathParameters(pub(crate) QueryMap); /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// /// These will always be empty for ALB requests +#[non_exhaustive] #[derive(Clone)] pub(crate) struct StageVariables(pub(crate) QueryMap); /// ALB/API gateway raw http path without any stage information +#[non_exhaustive] #[derive(Clone)] pub(crate) struct RawHttpPath(pub(crate) String); diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index dc14532e..38e45afa 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -12,8 +12,8 @@ use crate::Body; /// Request payload deserialization errors /// /// Returned by [`RequestPayloadExt::payload()`] -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum PayloadError { /// Returned when `application/json` bodies fail to deserialize a payload Json(serde_json::Error), @@ -22,16 +22,16 @@ pub enum PayloadError { } /// Indicates a problem processing a JSON payload. -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum JsonPayloadError { /// Problem deserializing a JSON payload. Parsing(serde_json::Error), } /// Indicates a problem processing an x-www-form-urlencoded payload. -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum FormUrlEncodedPayloadError { /// Problem deserializing an x-www-form-urlencoded payload. Parsing(SerdeError), diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 36e2ffbd..60e279c7 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -110,6 +110,7 @@ pub type Request = http::Request; /// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] /// /// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function. +#[non_exhaustive] #[doc(hidden)] pub enum TransformResponse<'a, R, E> { Request(RequestOrigin, RequestFuture<'a, R, E>), @@ -143,6 +144,7 @@ where /// Wraps a `Service` in a `Service>` /// /// This is completely internal to the `lambda_http::run` function. +#[non_exhaustive] #[doc(hidden)] pub struct Adapter<'a, R, S> { service: S, diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index a9281b46..fc88fc4a 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -39,6 +39,7 @@ use url::Url; /// /// This is not intended to be a type consumed by crate users directly. The order /// of the variants are notable. Serde will try to deserialize in this order. +#[non_exhaustive] #[doc(hidden)] #[derive(Debug)] pub enum LambdaRequest { @@ -85,6 +86,7 @@ impl LambdaRequest { pub type RequestFuture<'a, R, E> = Pin> + Send + 'a>>; /// Represents the origin from which the lambda was requested from. +#[non_exhaustive] #[doc(hidden)] #[derive(Debug, Clone)] pub enum RequestOrigin { @@ -388,6 +390,7 @@ fn apigw_path_with_stage(stage: &Option, path: &str) -> String { /// Event request context as an enumeration of request contexts /// for both ALB and API Gateway and HTTP API events +#[non_exhaustive] #[derive(Deserialize, Debug, Clone, Serialize)] #[serde(untagged)] pub enum RequestContext { diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 9b6208a3..6fad374b 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -40,6 +40,7 @@ const TEXT_ENCODING_PREFIXES: [&str; 5] = [ const TEXT_ENCODING_SUFFIXES: [&str; 3] = ["+xml", "+yaml", "+json"]; /// Representation of Lambda response +#[non_exhaustive] #[doc(hidden)] #[derive(Serialize, Debug)] #[serde(untagged)] @@ -70,17 +71,22 @@ impl LambdaResponse { match request_origin { #[cfg(feature = "apigw_rest")] - RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { - body, - is_base64_encoded, - status_code: status_code as i64, + RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1({ + let mut response = ApiGatewayProxyResponse::default(); + + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // Explicitly empty, as API gateway v1 will merge "headers" and // "multi_value_headers" fields together resulting in duplicate response headers. - headers: HeaderMap::new(), - multi_value_headers: headers, + response.headers = HeaderMap::new(); + response.multi_value_headers = headers; // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default() + { + response.other = Default::default(); + } + response }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { @@ -96,51 +102,64 @@ impl LambdaResponse { .collect(); headers.remove(SET_COOKIE); - LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse { - body, - is_base64_encoded, - status_code: status_code as i64, - cookies, + LambdaResponse::ApiGatewayV2({ + let mut response = ApiGatewayV2httpResponse::default(); + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; + response.cookies = cookies; // API gateway v2 doesn't have "multi_value_headers" field. Duplicate headers // are combined with commas and included in the headers field. - headers, - multi_value_headers: HeaderMap::new(), + response.headers = headers; + response.multi_value_headers = HeaderMap::new(); // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }) } #[cfg(feature = "alb")] - RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse { - body, - status_code: status_code as i64, - is_base64_encoded, + RequestOrigin::Alb => LambdaResponse::Alb({ + let mut response = AlbTargetGroupResponse::default(); + + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // ALB responses are used for ALB integration, which can be configured to use // either "headers" or "multi_value_headers" field. We need to return both fields // to ensure both configuration work correctly. - headers: headers.clone(), - multi_value_headers: headers, - status_description: Some(format!( + response.headers = headers.clone(); + response.multi_value_headers = headers; + response.status_description = Some(format!( "{} {}", status_code, parts.status.canonical_reason().unwrap_or_default() - )), + )); // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }), #[cfg(feature = "apigw_websockets")] - RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { - body, - is_base64_encoded, - status_code: status_code as i64, + RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1({ + let mut response = ApiGatewayProxyResponse::default(); + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // Explicitly empty, as API gateway v1 will merge "headers" and // "multi_value_headers" fields together resulting in duplicate response headers. - headers: HeaderMap::new(), - multi_value_headers: headers, + response.headers = HeaderMap::new(); + response.multi_value_headers = headers; // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }), #[cfg(feature = "pass_through")] RequestOrigin::PassThrough => { diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 6dd17230..ed61c773 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -21,6 +21,7 @@ use std::{future::Future, marker::PhantomData}; /// An adapter that lifts a standard [`Service`] into a /// [`Service>`] which produces streaming Lambda HTTP /// responses. +#[non_exhaustive] pub struct StreamAdapter<'a, S, B> { service: S, _phantom_data: PhantomData<&'a B>, @@ -147,6 +148,7 @@ where } pin_project_lite::pin_project! { +#[non_exhaustive] pub struct BodyStream { #[pin] pub(crate) body: B, diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs index b8dc3782..23f17b29 100644 --- a/lambda-integration-tests/src/authorizer.rs +++ b/lambda-integration-tests/src/authorizer.rs @@ -34,26 +34,36 @@ async fn func( } fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { - let stmt = IamPolicyStatement { - action: vec!["execute-api:Invoke".to_string()], - resource: vec![method_arn.to_owned()], - effect: aws_lambda_events::iam::IamPolicyEffect::Allow, - condition: None, + let stmt = { + let mut statement = IamPolicyStatement::default(); + statement.action = vec!["execute-api:Invoke".to_string()]; + statement.resource = vec![method_arn.to_owned()]; + statement.effect = aws_lambda_events::iam::IamPolicyEffect::Allow; + statement.condition = None; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + statement.other = Default::default(); + } + statement }; - let policy = ApiGatewayCustomAuthorizerPolicy { - version: Some("2012-10-17".to_string()), - statement: vec![stmt], + let policy = { + let mut policy = ApiGatewayCustomAuthorizerPolicy::default(); + policy.version = Some("2012-10-17".to_string()); + policy.statement = vec![stmt]; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + policy.other = Default::default(); + } + policy }; - ApiGatewayCustomAuthorizerResponse { - principal_id: Some("user".to_owned()), - policy_document: policy, - context: json!({ "hello": "world" }), - usage_identifier_key: None, - #[cfg(feature = "catch-all-fields")] - other: Default::default(), + let mut response = ApiGatewayCustomAuthorizerResponse::default(); + response.principal_id = Some("user".to_owned()); + response.policy_document = policy; + response.context = json!({ "hello": "world" }); + response.usage_identifier_key = None; + #[cfg(feature = "catch-all-fields")] + { + response.other = Default::default(); } + response } diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index c3a74f8c..2eafc409 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -16,14 +16,18 @@ async fn main() -> Result<(), Error> { async fn func(_event: LambdaEvent) -> Result { let mut headers = HeaderMap::new(); headers.insert("content-type", "text/html".parse().unwrap()); - let resp = ApiGatewayProxyResponse { - status_code: 200, - multi_value_headers: headers.clone(), - is_base64_encoded: false, - body: Some("Hello world!".into()), - headers, + let resp = { + let mut response = ApiGatewayProxyResponse::default(); + response.status_code = 200; + response.multi_value_headers = headers.clone(); + response.is_base64_encoded = false; + response.body = Some("Hello world!".into()); + response.headers = headers; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }; Ok(resp) }