Repository: awslabs/aws-lambda-rust-runtime (language: rust)

Issue:
Serialize/deserialize Context 
I'd like enable serialization / deserialization of Context to enable passing off handler's input/output for remote execution over the network.

I use a proxy handler that sends the request to AWS SQS queue and then reads a response from another queue. It acts as a proxy for the actual lambda running on my laptop. It's a very handy way of debugging it locally while being fully plugged into the AWS workflow. A more detailed explanation is in https://github.com/rimutaka/lambda-debug-proxy and https://dev.to/rimutaka/debugging-lambdas-on-aws-from-your-local-computer-4j59.

**The changes are superficial**: 
- added Config to Context (gotta be done anyway)
- added Serialize, Deserialize to Config and Context
- replaced ? with expect() with detailed error messages in the affected lines of code

**Full diff**: https://github.com/awslabs/aws-lambda-rust-runtime/compare/master...rimutaka:proxy-experiment

**Branch with the changes**: https://github.com/rimutaka/aws-lambda-rust-runtime/tree/proxy-experiment

This change is unlikely to benefit anyone outside of this specific use case, so it would be logical to make it a feature, but the amount of code added by the compiler is minimal, sometime negative. I tested this by compiling our examples for release.

 Example | proxy | master | diff 
----------|----|------|--------
 basic | 6506808 | 6520728 | -13,920 
 error-handling | 6612224 | 6633936 | -21,712 
 hello-http | 6736936 | 6730992 | 5,944 
 hello-http-without-macros | 6755200 | 6748960 | 6,240 
 macro | 6450784 | 6460960 | -10,176 

I am at a loss why some builds from proxy branch are smaller than master when they have Ser/Deser in them. What I know is that I've been using this proxy branch for weeks with no problems. What did I miss?

The proxy branch is ahead of master and is based on PR #244, so it cannot be merged. I am maintaining it in my own fork and will open a new PR once #244 is resolved. This issue is just for the record.


Discussion / hints:
I've been using this approach to run Lambdas locally since submitting this issue and will prepare a PR as soon as the 0.3 release is finalized. TBD.
@rimutaka 0.3 has been released, so no need to wait.

Fix patch:
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<dyn std::error::Error + Send + Sync + 'static>;
 
 /// 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<Self, Error> {
         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::<i32>()?,
-            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::<i32>()
+                .expect("AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var is not <i32>"),
+            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<Item = Result<http::Response<hyper::Body>, Error>> + Send,
         handler: F,
+        config: &Config,
     ) -> Result<(), Error>
     where
         F: Handler<A, B> + 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>(_: 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<HeaderMap> 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
+        }
+    }
+}


Test patch: