Skip to main content

android_sms_gateway/types/
webhook.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4/// A webhook event type.
5///
6/// This is a transparent newtype over `String` with predefined constants
7/// for all available event types.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct WebhookEvent(pub String);
11
12impl WebhookEvent {
13    pub const SMS_RECEIVED: &'static str = "sms:received";
14    pub const SMS_DATA_RECEIVED: &'static str = "sms:data-received";
15    pub const SMS_SENT: &'static str = "sms:sent";
16    pub const SMS_DELIVERED: &'static str = "sms:delivered";
17    pub const SMS_FAILED: &'static str = "sms:failed";
18    pub const SMS_CANCELLED: &'static str = "sms:cancelled";
19    pub const SYSTEM_PING: &'static str = "system:ping";
20    pub const MMS_RECEIVED: &'static str = "mms:received";
21    pub const MMS_DOWNLOADED: &'static str = "mms:downloaded";
22    pub const APP_STARTED: &'static str = "app:started";
23    pub const SMS_BATCH_RECEIVED: &'static str = "sms:batch:received";
24    pub const SMS_BATCH_DATA_RECEIVED: &'static str = "sms:batch:data-received";
25    pub const MMS_BATCH_RECEIVED: &'static str = "mms:batch:received";
26    pub const MMS_BATCH_DOWNLOADED: &'static str = "mms:batch:downloaded";
27
28    /// Creates a new webhook event type.
29    pub fn new(s: impl Into<String>) -> Self {
30        Self(s.into())
31    }
32
33    /// Returns the event type as a string slice.
34    pub fn as_str(&self) -> &str {
35        &self.0
36    }
37}
38
39/// All valid webhook event type strings.
40pub const WEBHOOK_EVENT_TYPES: &[&str] = &[
41    WebhookEvent::SMS_RECEIVED,
42    WebhookEvent::SMS_DATA_RECEIVED,
43    WebhookEvent::SMS_SENT,
44    WebhookEvent::SMS_DELIVERED,
45    WebhookEvent::SMS_FAILED,
46    WebhookEvent::SMS_CANCELLED,
47    WebhookEvent::SYSTEM_PING,
48    WebhookEvent::MMS_RECEIVED,
49    WebhookEvent::MMS_DOWNLOADED,
50    WebhookEvent::APP_STARTED,
51    WebhookEvent::SMS_BATCH_RECEIVED,
52    WebhookEvent::SMS_BATCH_DATA_RECEIVED,
53    WebhookEvent::MMS_BATCH_RECEIVED,
54    WebhookEvent::MMS_BATCH_DOWNLOADED,
55];
56
57/// Returns `true` if the string is a valid webhook event type.
58pub fn is_valid_webhook_event(e: &str) -> bool {
59    WEBHOOK_EVENT_TYPES.contains(&e)
60}
61
62/// A webhook registration.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Webhook {
65    /// Webhook ID (generated if not provided).
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub id: Option<String>,
68    /// Optional device ID to associate with this webhook.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub device_id: Option<String>,
71    /// The URL to send webhook requests to (must be HTTPS).
72    pub url: String,
73    /// The event type that triggers this webhook.
74    pub event: WebhookEvent,
75}
76
77impl Webhook {
78    /// Validates the webhook configuration.
79    ///
80    /// Checks that the event type is valid and the URL uses HTTPS.
81    pub fn validate(&self) -> Result<(), crate::Error> {
82        if !is_valid_webhook_event(self.event.as_str()) {
83            return Err(crate::Error::Validation("invalid event type".to_string()));
84        }
85
86        if !self.url.to_lowercase().starts_with("https://") {
87            return Err(crate::Error::Validation(
88                "url must start with https://".to_string(),
89            ));
90        }
91
92        let parsed = url::Url::parse(&self.url)
93            .map_err(|_| crate::Error::Validation("invalid url".to_string()))?;
94        if parsed.host_str().is_none_or(|h| h.is_empty()) {
95            return Err(crate::Error::Validation(
96                "url must have a valid host".to_string(),
97            ));
98        }
99
100        Ok(())
101    }
102}
103
104/// Base fields present on all message-related webhook payloads.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct SmsEventPayload {
108    /// The unique identifier of the message.
109    pub message_id: String,
110    /// The phone number of the sender (incoming) or recipient (outgoing).
111    pub phone_number: String,
112    /// The phone number of the message sender.
113    pub sender: String,
114    /// The phone number of the message recipient.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub recipient: Option<String>,
117    /// The SIM card number that sent or received the message.
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub sim_number: Option<u8>,
120}
121
122/// Payload of an `sms:received` event.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct SmsReceivedPayload {
126    #[serde(flatten)]
127    pub base: SmsEventPayload,
128    /// The content of the SMS message received.
129    pub message: String,
130    /// The timestamp when the SMS message was received.
131    pub received_at: DateTime<Utc>,
132}
133
134/// Payload of an `sms:sent` event.
135#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct SmsSentPayload {
138    #[serde(flatten)]
139    pub base: SmsEventPayload,
140    /// The timestamp when the SMS message was sent.
141    pub sent_at: DateTime<Utc>,
142}
143
144/// Payload of an `sms:delivered` event.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct SmsDeliveredPayload {
148    #[serde(flatten)]
149    pub base: SmsEventPayload,
150    /// The timestamp when the SMS message was delivered.
151    pub delivered_at: DateTime<Utc>,
152}
153
154/// Payload of an `sms:cancelled` event.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct SmsCancelledPayload {
158    #[serde(flatten)]
159    pub base: SmsEventPayload,
160    /// The timestamp when the SMS message was cancelled.
161    pub cancelled_at: DateTime<Utc>,
162}
163
164/// Payload of an `sms:failed` event.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct SmsFailedPayload {
168    #[serde(flatten)]
169    pub base: SmsEventPayload,
170    /// The timestamp when the SMS message failed.
171    pub failed_at: DateTime<Utc>,
172    /// The reason for the failure.
173    pub reason: String,
174}
175
176/// Payload of an `sms:data-received` event.
177#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct SmsDataReceivedPayload {
180    #[serde(flatten)]
181    pub base: SmsEventPayload,
182    /// Base64-encoded content of the data SMS received.
183    pub data: String,
184    /// The timestamp when the data SMS was received.
185    pub received_at: DateTime<Utc>,
186}
187
188/// Payload of an `mms:received` event (MMS notification, not yet downloaded).
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct MmsReceivedPayload {
192    #[serde(flatten)]
193    pub base: SmsEventPayload,
194    /// Unique MMS transaction identifier.
195    pub transaction_id: String,
196    /// Message subject line.
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub subject: Option<String>,
199    /// MMS content classification.
200    pub content_class: String,
201    /// Attachment size in bytes.
202    pub size: i64,
203    /// The timestamp when the MMS message was received.
204    pub received_at: DateTime<Utc>,
205}
206
207/// Metadata for a non-text MMS part (attachment).
208#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct MmsDownloadedAttachment {
211    /// The `_id` from `content://mms/part`.
212    pub part_id: i32,
213    /// MIME type of the attachment (e.g. `image/jpeg`).
214    pub content_type: String,
215    /// Filename of the attachment, if present.
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub name: Option<String>,
218    /// Base64-encoded attachment data, if available.
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub data: Option<String>,
221    /// Size in bytes, if known.
222    #[serde(default, skip_serializing_if = "Option::is_none")]
223    pub size: Option<i64>,
224}
225
226/// Payload of an `mms:downloaded` event (fully downloaded MMS with attachments).
227#[derive(Debug, Clone, Serialize, Deserialize)]
228#[serde(rename_all = "camelCase")]
229pub struct MmsDownloadedPayload {
230    #[serde(flatten)]
231    pub base: SmsEventPayload,
232    /// Message subject line.
233    #[serde(default, skip_serializing_if = "Option::is_none")]
234    pub subject: Option<String>,
235    /// Aggregated text content of the MMS message.
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub body: Option<String>,
238    /// Metadata for non-text MMS parts, including optional Base64 content.
239    pub attachments: Vec<MmsDownloadedAttachment>,
240    /// The timestamp when the MMS message was received.
241    pub received_at: DateTime<Utc>,
242}
243
244/// Payload of an `sms:batch:received` event.
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct SmsBatchReceivedPayload {
248    /// The ordered list of received SMS messages.
249    #[serde(default)]
250    pub messages: Vec<SmsReceivedPayload>,
251}
252
253/// Payload of an `sms:batch:data-received` event.
254#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct SmsBatchDataReceivedPayload {
257    /// The ordered list of received data SMS messages.
258    #[serde(default)]
259    pub messages: Vec<SmsDataReceivedPayload>,
260}
261
262/// Payload of an `mms:batch:received` event.
263#[derive(Debug, Clone, Serialize, Deserialize)]
264#[serde(rename_all = "camelCase")]
265pub struct MmsBatchReceivedPayload {
266    /// The ordered list of received MMS messages.
267    #[serde(default)]
268    pub messages: Vec<MmsReceivedPayload>,
269}
270
271/// Payload of an `mms:batch:downloaded` event.
272#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct MmsBatchDownloadedPayload {
275    /// The ordered list of downloaded MMS messages.
276    #[serde(default)]
277    pub messages: Vec<MmsDownloadedPayload>,
278}