Skip to content

Commit ee8d6e1

Browse files
abhu85claude
andcommitted
fix(sns): support SubscriptionConfirmation and UnsubscribeConfirmation message types
The SnsMessage and SnsMessageObj structs were failing to deserialize SubscriptionConfirmation and UnsubscribeConfirmation SNS messages because: 1. `unsubscribe_url` was required (String) but these message types don't have it 2. `subscribe_url` and `token` fields were missing entirely This fix: - Makes `unsubscribe_url` optional (Option<String>) since it's only present in Notification messages - Adds `subscribe_url` field (Option<String>) for confirmation messages - Adds `token` field (Option<String>) for confirmation messages All fields use #[serde(default)] to handle missing fields gracefully. Fixes #966 Signed-off-by: abhu85 <151518127+abhu85@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b3c4f9e commit ee8d6e1

2 files changed

Lines changed: 98 additions & 2 deletions

File tree

lambda-events/src/event/sns/mod.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,24 @@ pub struct SnsMessage {
9494
pub signing_cert_url: String,
9595

9696
/// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint.
97+
///
98+
/// Note: This field is only present in Notification messages. It is not present in SubscriptionConfirmation or UnsubscribeConfirmation messages.
9799
#[serde(alias = "UnsubscribeURL")]
98-
pub unsubscribe_url: String,
100+
#[serde(default)]
101+
pub unsubscribe_url: Option<String>,
102+
103+
/// A URL that you can visit to re-confirm the subscription or confirm the unsubscription.
104+
///
105+
/// Note: This field is only present in SubscriptionConfirmation and UnsubscribeConfirmation messages.
106+
#[serde(alias = "SubscribeURL")]
107+
#[serde(default)]
108+
pub subscribe_url: Option<String>,
109+
110+
/// A value you can use with the ConfirmSubscription action to re-confirm the subscription.
111+
///
112+
/// Note: This field is only present in SubscriptionConfirmation and UnsubscribeConfirmation messages.
113+
#[serde(default)]
114+
pub token: Option<String>,
99115

100116
/// The Message value specified when the notification was published to the topic.
101117
pub message: String,
@@ -205,8 +221,24 @@ pub struct SnsMessageObj<T: Serialize> {
205221
pub signing_cert_url: String,
206222

207223
/// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint.
224+
///
225+
/// Note: This field is only present in Notification messages. It is not present in SubscriptionConfirmation or UnsubscribeConfirmation messages.
208226
#[serde(alias = "UnsubscribeURL")]
209-
pub unsubscribe_url: String,
227+
#[serde(default)]
228+
pub unsubscribe_url: Option<String>,
229+
230+
/// A URL that you can visit to re-confirm the subscription or confirm the unsubscription.
231+
///
232+
/// Note: This field is only present in SubscriptionConfirmation and UnsubscribeConfirmation messages.
233+
#[serde(alias = "SubscribeURL")]
234+
#[serde(default)]
235+
pub subscribe_url: Option<String>,
236+
237+
/// A value you can use with the ConfirmSubscription action to re-confirm the subscription.
238+
///
239+
/// Note: This field is only present in SubscriptionConfirmation and UnsubscribeConfirmation messages.
240+
#[serde(default)]
241+
pub token: Option<String>,
210242

211243
/// Deserialized into a `T` from nested JSON inside the SNS message string. `T` must implement the `Deserialize` or `DeserializeOwned` trait.
212244
#[serde_as(as = "serde_with::json::JsonString")]
@@ -462,4 +494,45 @@ mod test {
462494
let reparsed: SnsEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
463495
assert_eq!(parsed, reparsed);
464496
}
497+
498+
#[test]
499+
#[cfg(feature = "sns")]
500+
fn my_example_sns_subscription_confirmation() {
501+
// Test for issue #966: SnsMessage struct fails with SubscriptionConfirmation types
502+
// SubscriptionConfirmation messages have SubscribeURL and Token fields instead of UnsubscribeURL
503+
let data = include_bytes!("../../fixtures/example-sns-subscription-confirmation.json");
504+
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
505+
assert_eq!(1, parsed.records.len());
506+
507+
let sns_message = &parsed.records[0].sns;
508+
assert_eq!("SubscriptionConfirmation", sns_message.sns_message_type);
509+
assert!(sns_message.unsubscribe_url.is_none());
510+
assert!(sns_message.subscribe_url.is_some());
511+
assert!(sns_message.token.is_some());
512+
assert_eq!(
513+
"https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e2425dacbbffff",
514+
sns_message.subscribe_url.as_ref().unwrap()
515+
);
516+
assert_eq!(
517+
"2336412f37fb687f5d51e6e2425dacbbffff",
518+
sns_message.token.as_ref().unwrap()
519+
);
520+
521+
let output: String = serde_json::to_string(&parsed).unwrap();
522+
let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
523+
assert_eq!(parsed, reparsed);
524+
}
525+
526+
#[test]
527+
#[cfg(feature = "sns")]
528+
fn my_example_sns_notification_has_unsubscribe_url() {
529+
// Verify that Notification messages still have unsubscribe_url
530+
let data = include_bytes!("../../fixtures/example-sns-event.json");
531+
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
532+
let sns_message = &parsed.records[0].sns;
533+
assert_eq!("Notification", sns_message.sns_message_type);
534+
assert!(sns_message.unsubscribe_url.is_some());
535+
assert!(sns_message.subscribe_url.is_none());
536+
assert!(sns_message.token.is_none());
537+
}
465538
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"Records": [
3+
{
4+
"EventVersion": "1.0",
5+
"EventSubscriptionArn": "arn:aws:sns:us-east-1:123456789012:MyTopic:00000000-0000-0000-0000-000000000000",
6+
"EventSource": "aws:sns",
7+
"Sns": {
8+
"Type": "SubscriptionConfirmation",
9+
"MessageId": "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
10+
"TopicArn": "arn:aws:sns:us-east-1:123456789012:MyTopic",
11+
"Message": "You have chosen to subscribe to the topic arn:aws:sns:us-east-1:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
12+
"Timestamp": "2012-04-26T20:45:04.751Z",
13+
"SignatureVersion": "1",
14+
"Signature": "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",
15+
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
16+
"SubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e2425dacbbffff",
17+
"Token": "2336412f37fb687f5d51e6e2425dacbbffff",
18+
"Subject": null,
19+
"MessageAttributes": {}
20+
}
21+
}
22+
]
23+
}

0 commit comments

Comments
 (0)