aws_lambda_events/event/sqs/
mod.rs

1use crate::{custom_serde::deserialize_lambda_map, encodings::Base64Data};
2use serde::{de::DeserializeOwned, Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages
6#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
7#[serde(rename_all = "camelCase")]
8pub struct SqsEvent {
9    #[serde(rename = "Records")]
10    pub records: Vec<SqsMessage>,
11}
12
13/// An individual SQS Message, its metadata, and Message Attributes
14#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct SqsMessage {
17    /// nolint: stylecheck
18    #[serde(default)]
19    pub message_id: Option<String>,
20    #[serde(default)]
21    pub receipt_handle: Option<String>,
22    #[serde(default)]
23    pub body: Option<String>,
24    #[serde(default)]
25    pub md5_of_body: Option<String>,
26    #[serde(default)]
27    pub md5_of_message_attributes: Option<String>,
28    #[serde(deserialize_with = "deserialize_lambda_map")]
29    #[serde(default)]
30    pub attributes: HashMap<String, String>,
31    #[serde(deserialize_with = "deserialize_lambda_map")]
32    #[serde(default)]
33    pub message_attributes: HashMap<String, SqsMessageAttribute>,
34    #[serde(default)]
35    #[serde(rename = "eventSourceARN")]
36    pub event_source_arn: Option<String>,
37    #[serde(default)]
38    pub event_source: Option<String>,
39    #[serde(default)]
40    pub aws_region: Option<String>,
41}
42
43/// Alternative to `SqsEvent` to be used alongside `SqsMessageObj<T>` 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
44#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
45#[serde(rename_all = "camelCase")]
46#[serde(bound(deserialize = "T: DeserializeOwned"))]
47pub struct SqsEventObj<T: Serialize> {
48    #[serde(rename = "Records")]
49    #[serde(bound(deserialize = "T: DeserializeOwned"))]
50    pub records: Vec<SqsMessageObj<T>>,
51}
52
53/// Alternative to `SqsMessage` to be used alongside `SqsEventObj<T>` 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
54#[serde_with::serde_as]
55#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
56#[serde(bound(deserialize = "T: DeserializeOwned"))]
57#[serde(rename_all = "camelCase")]
58pub struct SqsMessageObj<T: Serialize> {
59    /// nolint: stylecheck
60    #[serde(default)]
61    pub message_id: Option<String>,
62    #[serde(default)]
63    pub receipt_handle: Option<String>,
64
65    /// Deserialized into a `T` from nested JSON inside the SQS body string. `T` must implement the `Deserialize` or `DeserializeOwned` trait.
66    #[serde_as(as = "serde_with::json::JsonString")]
67    #[serde(bound(deserialize = "T: DeserializeOwned"))]
68    pub body: T,
69    #[serde(default)]
70    pub md5_of_body: Option<String>,
71    #[serde(default)]
72    pub md5_of_message_attributes: Option<String>,
73    #[serde(deserialize_with = "deserialize_lambda_map")]
74    #[serde(default)]
75    pub attributes: HashMap<String, String>,
76    #[serde(deserialize_with = "deserialize_lambda_map")]
77    #[serde(default)]
78    pub message_attributes: HashMap<String, SqsMessageAttribute>,
79    #[serde(default)]
80    #[serde(rename = "eventSourceARN")]
81    pub event_source_arn: Option<String>,
82    #[serde(default)]
83    pub event_source: Option<String>,
84    #[serde(default)]
85    pub aws_region: Option<String>,
86}
87
88#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
89#[serde(rename_all = "camelCase")]
90pub struct SqsMessageAttribute {
91    pub string_value: Option<String>,
92    pub binary_value: Option<Base64Data>,
93    #[serde(default)]
94    pub string_list_values: Vec<String>,
95    #[serde(default)]
96    pub binary_list_values: Vec<Base64Data>,
97    #[serde(default)]
98    pub data_type: Option<String>,
99}
100
101#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
102#[serde(rename_all = "camelCase")]
103pub struct SqsBatchResponse {
104    pub batch_item_failures: Vec<BatchItemFailure>,
105}
106
107#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
108#[serde(rename_all = "camelCase")]
109pub struct BatchItemFailure {
110    pub item_identifier: String,
111}
112
113/// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages
114#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "PascalCase")]
116#[serde(bound(deserialize = "T: DeserializeOwned"))]
117pub struct SqsApiEventObj<T: Serialize> {
118    #[serde(bound(deserialize = "T: DeserializeOwned"))]
119    pub messages: Vec<SqsApiMessageObj<T>>,
120}
121
122/// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages
123#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct SqsApiEvent {
126    pub messages: Vec<SqsApiMessage>,
127}
128
129/// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj<T>` when you need to
130/// deserialize a nested object into a struct of type T within the SQS Message rather
131/// than just using the raw SQS Message string
132#[serde_with::serde_as]
133#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
134#[serde(bound(deserialize = "T: DeserializeOwned"))]
135#[serde(rename_all = "PascalCase")]
136pub struct SqsApiMessageObj<T: Serialize> {
137    /// nolint: stylecheck
138    #[serde(default)]
139    pub message_id: Option<String>,
140    #[serde(default)]
141    pub receipt_handle: Option<String>,
142    /// Deserialized into a `T` from nested JSON inside the SQS body string. `T` must implement the `Deserialize` or `DeserializeOwned` trait.
143    #[serde_as(as = "serde_with::json::JsonString")]
144    #[serde(bound(deserialize = "T: DeserializeOwned"))]
145    pub body: T,
146    #[serde(default)]
147    pub md5_of_body: Option<String>,
148    #[serde(default)]
149    pub md5_of_message_attributes: Option<String>,
150    #[serde(deserialize_with = "deserialize_lambda_map")]
151    #[serde(default)]
152    pub attributes: HashMap<String, String>,
153    #[serde(deserialize_with = "deserialize_lambda_map")]
154    #[serde(default)]
155    pub message_attributes: HashMap<String, SqsMessageAttribute>,
156}
157
158/// An individual SQS API Message, its metadata, and Message Attributes
159#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
160#[serde(rename_all = "PascalCase")]
161pub struct SqsApiMessage {
162    /// nolint: stylecheck
163    #[serde(default)]
164    pub message_id: Option<String>,
165    #[serde(default)]
166    pub receipt_handle: Option<String>,
167    #[serde(default)]
168    pub body: Option<String>,
169    #[serde(default)]
170    pub md5_of_body: Option<String>,
171    #[serde(default)]
172    pub md5_of_message_attributes: Option<String>,
173    #[serde(deserialize_with = "deserialize_lambda_map")]
174    #[serde(default)]
175    pub attributes: HashMap<String, String>,
176    #[serde(deserialize_with = "deserialize_lambda_map")]
177    #[serde(default)]
178    pub message_attributes: HashMap<String, SqsMessageAttribute>,
179}
180
181#[cfg(test)]
182mod test {
183    use super::*;
184
185    #[test]
186    #[cfg(feature = "sqs")]
187    fn example_sqs_event() {
188        let data = include_bytes!("../../fixtures/example-sqs-event.json");
189        let parsed: SqsEvent = serde_json::from_slice(data).unwrap();
190        let output: String = serde_json::to_string(&parsed).unwrap();
191        let reparsed: SqsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
192        assert_eq!(parsed, reparsed);
193    }
194
195    #[test]
196    #[cfg(feature = "sqs")]
197    fn example_sqs_obj_event() {
198        #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
199        struct CustStruct {
200            a: String,
201            b: u32,
202        }
203
204        let data = include_bytes!("../../fixtures/example-sqs-event-obj.json");
205        let parsed: SqsEventObj<CustStruct> = serde_json::from_slice(data).unwrap();
206
207        assert_eq!(parsed.records[0].body.a, "Test");
208        assert_eq!(parsed.records[0].body.b, 123);
209
210        let output: String = serde_json::to_string(&parsed).unwrap();
211        let reparsed: SqsEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
212        assert_eq!(parsed, reparsed);
213    }
214
215    #[test]
216    #[cfg(feature = "sqs")]
217    fn example_sqs_batch_response() {
218        // Example sqs batch response fetched 2022-05-13, from:
219        // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting
220        let data = include_bytes!("../../fixtures/example-sqs-batch-response.json");
221        let parsed: SqsBatchResponse = serde_json::from_slice(data).unwrap();
222        let output: String = serde_json::to_string(&parsed).unwrap();
223        let reparsed: SqsBatchResponse = serde_json::from_slice(output.as_bytes()).unwrap();
224        assert_eq!(parsed, reparsed);
225    }
226
227    #[test]
228    #[cfg(feature = "sqs")]
229    fn example_sqs_api_obj_event() {
230        // Example sqs api receive message response, fetched 2023-10-23, inspired from:
231        // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html#API_ReceiveMessage_ResponseSyntax
232        #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
233        struct CustStruct {
234            city: String,
235            country: String,
236        }
237
238        let data = include_bytes!("../../fixtures/example-sqs-api-event-obj.json");
239        let parsed: SqsApiEventObj<CustStruct> = serde_json::from_slice(data).unwrap();
240
241        assert_eq!(parsed.messages[0].body.city, "provincetown");
242        assert_eq!(parsed.messages[0].body.country, "usa");
243
244        let output: String = serde_json::to_string(&parsed).unwrap();
245        let reparsed: SqsApiEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
246        assert_eq!(parsed, reparsed);
247    }
248}