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 + } + } +}