1use crate::status::SmsStatus;
4use serde::{Deserialize, Serialize, Deserializer};
5
6fn deserialize_string_or_u32<'de, D>(deserializer: D) -> Result<u32, D::Error>
8where
9 D: Deserializer<'de>,
10{
11 use serde::de::Error;
12
13 match serde_json::Value::deserialize(deserializer)? {
14 serde_json::Value::String(s) => {
15 s.parse::<u32>().map_err(|_| D::Error::custom(format!("invalid number string: {}", s)))
16 }
17 serde_json::Value::Number(n) => {
18 n.as_u64()
19 .and_then(|n| if n <= u32::MAX as u64 { Some(n as u32) } else { None })
20 .ok_or_else(|| D::Error::custom("number out of range for u32"))
21 }
22 _ => Err(D::Error::custom("expected string or number")),
23 }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "lowercase")]
29pub enum MessageType {
30 Text,
32 Binary,
34 Wappush,
36 Unicode,
38}
39
40impl std::fmt::Display for MessageType {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 MessageType::Text => write!(f, "text"),
44 MessageType::Binary => write!(f, "binary"),
45 MessageType::Wappush => write!(f, "wappush"),
46 MessageType::Unicode => write!(f, "unicode"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum MessageClass {
54 #[serde(rename = "0")]
56 Flash = 0,
57 #[serde(rename = "1")]
59 MeSpecific = 1,
60 #[serde(rename = "2")]
62 SimSpecific = 2,
63 #[serde(rename = "3")]
65 TeSpecific = 3,
66}
67
68#[derive(Debug, Clone, Serialize)]
70pub struct SmsRequest {
71 pub from: String,
73 pub to: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub text: Option<String>,
78 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
80 pub message_type: Option<MessageType>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub body: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub udh: Option<String>,
87 #[serde(rename = "protocol-id", skip_serializing_if = "Option::is_none")]
89 pub protocol_id: Option<u8>,
90 #[serde(rename = "message-class", skip_serializing_if = "Option::is_none")]
92 pub message_class: Option<MessageClass>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub ttl: Option<u32>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub callback: Option<String>,
99 #[serde(rename = "status-report-req", skip_serializing_if = "Option::is_none")]
101 pub status_report_req: Option<bool>,
102 #[serde(rename = "client-ref", skip_serializing_if = "Option::is_none")]
104 pub client_ref: Option<String>,
105 #[serde(rename = "account-ref", skip_serializing_if = "Option::is_none")]
107 pub account_ref: Option<String>,
108 #[serde(rename = "entity-id", skip_serializing_if = "Option::is_none")]
110 pub entity_id: Option<String>,
111 #[serde(rename = "content-id", skip_serializing_if = "Option::is_none")]
113 pub content_id: Option<String>,
114}
115
116impl SmsRequest {
117 pub fn text(from: impl Into<String>, to: impl Into<String>, text: impl Into<String>) -> Self {
119 Self {
120 from: from.into(),
121 to: to.into(),
122 text: Some(text.into()),
123 message_type: Some(MessageType::Text),
124 body: None,
125 udh: None,
126 protocol_id: None,
127 message_class: None,
128 ttl: None,
129 callback: None,
130 status_report_req: None,
131 client_ref: None,
132 account_ref: None,
133 entity_id: None,
134 content_id: None,
135 }
136 }
137
138 pub fn binary(from: impl Into<String>, to: impl Into<String>, body: impl Into<String>) -> Self {
140 Self {
141 from: from.into(),
142 to: to.into(),
143 text: None,
144 message_type: Some(MessageType::Binary),
145 body: Some(body.into()),
146 udh: None,
147 protocol_id: None,
148 message_class: None,
149 ttl: None,
150 callback: None,
151 status_report_req: None,
152 client_ref: None,
153 account_ref: None,
154 entity_id: None,
155 content_id: None,
156 }
157 }
158
159 pub fn with_client_ref(mut self, client_ref: impl Into<String>) -> Self {
161 self.client_ref = Some(client_ref.into());
162 self
163 }
164
165 pub fn with_callback(mut self, callback: impl Into<String>) -> Self {
167 self.callback = Some(callback.into());
168 self
169 }
170
171 pub fn with_status_report(mut self, enabled: bool) -> Self {
173 self.status_report_req = Some(enabled);
174 self
175 }
176
177 pub fn with_ttl(mut self, ttl: u32) -> Self {
179 self.ttl = Some(ttl);
180 self
181 }
182}
183
184#[derive(Debug, Clone, Deserialize)]
186pub struct SmsMessage {
187 pub to: Option<String>,
189 #[serde(rename = "message-id")]
191 pub message_id: Option<String>,
192 pub status: SmsStatus,
194 #[serde(rename = "error-text")]
196 pub error_text: Option<String>,
197 #[serde(rename = "remaining-balance")]
199 pub remaining_balance: Option<String>,
200 #[serde(rename = "message-price")]
202 pub message_price: Option<String>,
203 pub network: Option<String>,
205 #[serde(rename = "account-ref")]
207 pub account_ref: Option<String>,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub enum MessageStatus {
213 Delivered,
215 Failed,
217 Pending,
219 Unknown,
221}
222
223#[derive(Debug, Clone, Deserialize)]
225pub struct SmsResponse {
226 #[serde(rename = "message-count", deserialize_with = "deserialize_string_or_u32")]
228 pub message_count: u32,
229 pub messages: Vec<SmsMessage>,
231}
232
233impl SmsResponse {
234 pub fn all_successful(&self) -> bool {
236 self.messages.iter().all(|msg| msg.status.is_success())
237 }
238
239 pub fn success_count(&self) -> u32 {
241 self.messages.iter().filter(|msg| msg.status.is_success()).count() as u32
242 }
243
244 pub fn failure_count(&self) -> u32 {
246 self.messages.iter().filter(|msg| msg.status.is_error()).count() as u32
247 }
248
249 pub fn failed_messages(&self) -> Vec<&SmsMessage> {
251 self.messages.iter().filter(|msg| msg.status.is_error()).collect()
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_sms_response_deserialize_string_count() {
261 let json = r#"{
262 "message-count": "1",
263 "messages": [{
264 "to": "447700900000",
265 "message-id": "0A0000000123ABCD1",
266 "status": "0",
267 "remaining-balance": "3.14159265",
268 "message-price": "0.03330000",
269 "network": "12345"
270 }]
271 }"#;
272
273 let response: SmsResponse = serde_json::from_str(json).unwrap();
274 assert_eq!(response.message_count, 1);
275 assert_eq!(response.messages.len(), 1);
276 }
277
278 #[test]
279 fn test_sms_response_deserialize_number_count() {
280 let json = r#"{
281 "message-count": 2,
282 "messages": [{
283 "to": "447700900000",
284 "message-id": "0A0000000123ABCD1",
285 "status": "0",
286 "remaining-balance": "3.14159265",
287 "message-price": "0.03330000",
288 "network": "12345"
289 }]
290 }"#;
291
292 let response: SmsResponse = serde_json::from_str(json).unwrap();
293 assert_eq!(response.message_count, 2);
294 assert_eq!(response.messages.len(), 1);
295 }
296}