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

Issue:
`EventBridgeEventObj.detail` expects a string instead of an object
`EventBridgeEventObj` expects a JSON string in the `detail` field always as discussed and implemented in #704. I don't know if it's possible for it to actually be a string, but it can definitely be an object. See an example here: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html.

This makes the types here currently unusable for the most common pattern which is when creating a Lambda that processes EventBridge events from AWS services, like ECS.


Discussion / hints:

Fix patch:
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<String>,
-    #[serde(default)]
-    pub id: Option<String>,
-    pub detail_type: String,
-    pub source: String,
-    #[serde(default)]
-    pub account: Option<String>,
-    #[serde(default)]
-    pub time: Option<DateTime<Utc>>,
-    #[serde(default)]
-    pub region: Option<String>,
-    #[serde(default)]
-    pub resources: Option<Vec<String>>,
-    #[serde(default)]
-    pub detail: Option<String>,
-}
-
-#[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<T: Serialize> {
+pub struct EventBridgeEvent<T: Serialize> {
     #[serde(default)]
     pub version: Option<String>,
     #[serde(default)]
@@ -43,7 +24,6 @@ pub struct EventBridgeEventObj<T: Serialize> {
     pub region: Option<String>,
     #[serde(default)]
     pub resources: Option<Vec<String>>,
-    #[serde_as(as = "serde_with::json::JsonString")]
     #[serde(bound(deserialize = "T: DeserializeOwned"))]
     pub detail: T,
 }
@@ -53,35 +33,24 @@ pub struct EventBridgeEventObj<T: Serialize> {
 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<CustomStruct> = serde_json::from_slice(data).unwrap();
+        let parsed: EventBridgeEvent<Ec2StateChange> = 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<CustomStruct> = 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<Ec2StateChange> = 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"
-}


Test patch: