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

Issue:
Data did not match any variant of untagged enum LambdaRequest when invoking with sample JSON
When invoking a lambda_http service function using the template payload from the Lambda test console, serde fails to deserialize.

Cargo.toml:
```toml
[package]
name = "project"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.70"
lambda_http = "0.8.0"
serde = { version = "1.0.159", features = ["derive"] }
tokio = { version = "1.27.0", features = ["macros"] }
```

bin/upload_err.rs:
```rust
use lambda_http::RequestPayloadExt;

#[derive(serde::Deserialize)]
pub struct UploadRequest {
    file_name: String,
}

#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
    tracing_subscriber::fmt().init();
    lambda_http::run(lambda_http::service_fn(
        |request: lambda_http::Request| async move {
            let file_name = request
                .payload::<UploadRequest>()?
                .ok_or_else(|| anyhow::anyhow!("missing upload request"))?
                .file_name;
            Ok::<_, anyhow::Error>(format!(r#"{{"url": "/path/to/{file_name}"}}"#))
        },
    ))
    .await
}
```

example.json:
```json
{
  "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"
  }
}
```

Runner:
```sh
%cargo lambda watch
````

Invoke:
```sh
% cargo lambda invoke upload_er
r --data-file example.json 
Error: serde_json::error::Error

  × data did not match any variant of untagged enum LambdaRequest
  ```

Log:
```text
2023-05-16T18:22:46.995666Z ERROR Lambda runtime invoke{requestId="ef6c5745-52ce-4b08-9119-72717c99179e" xrayTraceId="Root=1-6463c9df-b71d5c4715e826747799163f;Parent=cc2d5d821583bf7e;Sampled=1"}: lambda_runtime: Error("data did not match any variant of untagged enum LambdaRequest", line: 0, column: 0)
```



Discussion / hints:
This looks suspiciously similar to #365 and #367, which makes me think that either the example is too loose, or the definitions in lambda_http are too strict.
> makes me think that either the example is too loose, or the definitions in lambda_http are too strict.

There is no specification about the payloads, so it's hard to tell. Other languages are definitely more forgiving than Rust parsing those examples. Given the payload that you have in the description, it should be easy to add a new test case in the apigw module to validate that those payloads are supported:

https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-events/src/event/apigw/mod.rs#L749

Fix patch:
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<String>,
+    #[serde(default)]
     pub request_time_epoch: i64,
     /// The API Gateway rest API Id
     #[serde(default)]
@@ -180,6 +181,7 @@ where
     pub domain_prefix: Option<String>,
     #[serde(default)]
     pub time: Option<String>,
+    #[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


Test patch: