Skip to main content

huawei_dongle_api/models/
sms.rs

1//! SMS management models
2
3use super::enums::{SmsBoxType, SmsPriority, SmsSortType, SmsStatus, SmsType};
4use serde::{Deserialize, Serialize};
5
6/// SMS count response from `/api/sms/sms-count`.
7///
8/// Provides message counts for both local storage and SIM card storage,
9/// broken down by message type (inbox, outbox, draft).
10///
11/// # Example
12///
13/// ```no_run
14/// # use huawei_dongle_api::{Client, Config};
15/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
16/// let client = Client::new(Config::default())?;
17/// let count = client.sms().count().await?;
18///
19/// println!("Total unread: {}", count.total_unread().unwrap_or(0));
20/// println!("New messages: {}", count.has_new_messages());
21/// # Ok(())
22/// # }
23/// ```
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename = "response")]
26pub struct SmsCount {
27    #[serde(rename = "LocalUnread")]
28    pub local_unread: String,
29
30    #[serde(rename = "LocalInbox")]
31    pub local_inbox: String,
32
33    #[serde(rename = "LocalOutbox")]
34    pub local_outbox: String,
35
36    #[serde(rename = "LocalDraft")]
37    pub local_draft: String,
38
39    #[serde(rename = "SimUnread")]
40    pub sim_unread: String,
41
42    #[serde(rename = "SimInbox")]
43    pub sim_inbox: String,
44
45    #[serde(rename = "SimOutbox")]
46    pub sim_outbox: String,
47
48    #[serde(rename = "SimDraft")]
49    pub sim_draft: String,
50
51    #[serde(rename = "NewMsg")]
52    pub new_msg: String,
53}
54
55/// SMS list request for `/api/sms/sms-list`
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename = "request")]
58pub struct SmsListRequest {
59    #[serde(rename = "PageIndex")]
60    pub page_index: String,
61
62    #[serde(rename = "ReadCount")]
63    pub read_count: String,
64
65    #[serde(rename = "BoxType")]
66    pub box_type: String,
67
68    #[serde(rename = "SortType")]
69    pub sort_type: String,
70
71    #[serde(rename = "Ascending")]
72    pub ascending: String,
73
74    #[serde(rename = "UnreadPreferred")]
75    pub unread_preferred: String,
76}
77
78/// SMS message from `/api/sms/sms-list` response
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename = "Message")]
81pub struct SmsMessage {
82    #[serde(rename = "Smstat")]
83    pub status: SmsStatus,
84
85    #[serde(rename = "Index")]
86    pub index: String,
87
88    #[serde(rename = "Phone")]
89    pub phone: String,
90
91    #[serde(rename = "Content")]
92    pub content: String,
93
94    #[serde(rename = "Date")]
95    pub date: String,
96
97    #[serde(rename = "Sca")]
98    pub sca: Option<String>,
99
100    #[serde(rename = "SaveType")]
101    pub save_type: String,
102
103    #[serde(rename = "Priority")]
104    pub priority: SmsPriority,
105
106    #[serde(rename = "SmsType")]
107    pub sms_type: SmsType,
108}
109
110/// Messages container from SMS list response
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct SmsMessages {
113    #[serde(rename = "$value", default)]
114    pub messages: Vec<SmsMessage>,
115}
116
117/// SMS list response from `/api/sms/sms-list`
118#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(rename = "response")]
120pub struct SmsListResponse {
121    #[serde(rename = "Count", default)]
122    pub count: Option<String>,
123
124    #[serde(rename = "Messages")]
125    pub messages: SmsMessages,
126}
127
128impl SmsListResponse {
129    /// Get the message count, either from the Count field or by counting messages
130    pub fn message_count(&self) -> usize {
131        if let Some(count_str) = &self.count {
132            count_str.parse().unwrap_or(self.messages.messages.len())
133        } else {
134            self.messages.messages.len()
135        }
136    }
137}
138
139/// SMS delete request for `/api/sms/delete-sms`
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename = "request")]
142pub struct SmsDeleteRequest {
143    #[serde(rename = "Index")]
144    pub index: String,
145}
146
147/// SMS set read request for `/api/sms/set-read`
148#[derive(Debug, Clone, Serialize, Deserialize)]
149#[serde(rename = "request")]
150pub struct SmsSetReadRequest {
151    #[serde(rename = "Index")]
152    pub index: String,
153}
154
155impl SmsCount {
156    /// Get total unread messages count
157    pub fn total_unread(&self) -> Result<u32, std::num::ParseIntError> {
158        let local: u32 = self.local_unread.parse()?;
159        let sim: u32 = self.sim_unread.parse()?;
160        Ok(local + sim)
161    }
162
163    /// Get total inbox messages count
164    pub fn total_inbox(&self) -> Result<u32, std::num::ParseIntError> {
165        let local: u32 = self.local_inbox.parse()?;
166        let sim: u32 = self.sim_inbox.parse()?;
167        Ok(local + sim)
168    }
169
170    /// Check if there are new messages
171    pub fn has_new_messages(&self) -> bool {
172        self.new_msg.parse::<u32>().unwrap_or(0) > 0
173    }
174}
175
176impl SmsListRequest {
177    /// Create a new SMS list request
178    pub fn new(
179        page_index: u32,
180        read_count: u32,
181        box_type: SmsBoxType,
182        sort_type: SmsSortType,
183        ascending: bool,
184        unread_preferred: bool,
185    ) -> Self {
186        Self {
187            page_index: page_index.to_string(),
188            read_count: read_count.to_string(),
189            box_type: box_type.to_string(),
190            sort_type: sort_type.to_string(),
191            ascending: if ascending { "1" } else { "0" }.to_string(),
192            unread_preferred: if unread_preferred { "1" } else { "0" }.to_string(),
193        }
194    }
195}
196
197impl SmsMessage {
198    /// Check if message is unread
199    pub fn is_unread(&self) -> bool {
200        self.status.is_unread()
201    }
202
203    /// Check if message is read
204    pub fn is_read(&self) -> bool {
205        self.status.is_read()
206    }
207
208    /// Get message ID for deletion
209    pub fn id(&self) -> &str {
210        &self.index
211    }
212
213    /// Get formatted phone number
214    pub fn phone_number(&self) -> &str {
215        &self.phone
216    }
217
218    /// Get message text content
219    pub fn text(&self) -> &str {
220        &self.content
221    }
222
223    /// Get formatted date
224    pub fn date_str(&self) -> &str {
225        &self.date
226    }
227}
228
229impl SmsDeleteRequest {
230    /// Create a new delete request
231    pub fn new(message_id: &str) -> Self {
232        Self {
233            index: message_id.to_string(),
234        }
235    }
236}
237
238impl SmsSetReadRequest {
239    /// Create a new set read request
240    pub fn new(message_id: &str) -> Self {
241        Self {
242            index: message_id.to_string(),
243        }
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_sms_count_totals() {
253        let count = SmsCount {
254            local_unread: "3".to_string(),
255            local_inbox: "10".to_string(),
256            local_outbox: "5".to_string(),
257            local_draft: "1".to_string(),
258            sim_unread: "2".to_string(),
259            sim_inbox: "8".to_string(),
260            sim_outbox: "3".to_string(),
261            sim_draft: "0".to_string(),
262            new_msg: "1".to_string(),
263        };
264
265        assert_eq!(count.total_unread().unwrap(), 5);
266        assert_eq!(count.total_inbox().unwrap(), 18);
267        assert!(count.has_new_messages());
268    }
269
270    #[test]
271    fn test_sms_message_status() {
272        let unread = SmsMessage {
273            status: SmsStatus::Unread,
274            index: "1".to_string(),
275            phone: "+1234567890".to_string(),
276            content: "Test message".to_string(),
277            date: "2024-01-01 12:00:00".to_string(),
278            sca: None,
279            save_type: "3".to_string(),
280            priority: SmsPriority::Normal,
281            sms_type: SmsType::Single,
282        };
283
284        assert!(unread.is_unread());
285        assert!(!unread.is_read());
286        assert_eq!(unread.id(), "1");
287        assert_eq!(unread.text(), "Test message");
288    }
289
290    #[test]
291    fn test_sms_list_request_creation() {
292        let request = SmsListRequest::new(
293            1,
294            20,
295            SmsBoxType::LocalInbox,
296            SmsSortType::ByTime,
297            false,
298            true,
299        );
300
301        assert_eq!(request.page_index, "1");
302        assert_eq!(request.read_count, "20");
303        assert_eq!(request.box_type, "1"); // LocalInbox
304        assert_eq!(request.sort_type, "0"); // ByTime
305        assert_eq!(request.ascending, "0");
306        assert_eq!(request.unread_preferred, "1"); // unread preferred
307    }
308
309    #[test]
310    fn test_sms_list_response_missing_count() {
311        let xml_without_count = r#"<response>
312    <Messages>
313        <Message>
314            <Smstat>0</Smstat>
315            <Index>1</Index>
316            <Phone>+123456789</Phone>
317            <Content>Test message</Content>
318            <Date>2023-01-01 12:00:00</Date>
319            <Sca></Sca>
320            <SaveType>0</SaveType>
321            <Priority>0</Priority>
322            <SmsType>1</SmsType>
323        </Message>
324    </Messages>
325</response>"#;
326
327        let response: SmsListResponse = serde_xml_rs::from_str(xml_without_count).unwrap();
328        assert!(response.count.is_none());
329        assert_eq!(response.message_count(), 1);
330        assert_eq!(response.messages.messages.len(), 1);
331    }
332
333    #[test]
334    fn test_sms_list_response_with_count() {
335        let xml_with_count = r#"<response>
336    <Count>1</Count>
337    <Messages>
338        <Message>
339            <Smstat>0</Smstat>
340            <Index>1</Index>
341            <Phone>+123456789</Phone>
342            <Content>Test message</Content>
343            <Date>2023-01-01 12:00:00</Date>
344            <Sca></Sca>
345            <SaveType>0</SaveType>
346            <Priority>0</Priority>
347            <SmsType>1</SmsType>
348        </Message>
349    </Messages>
350</response>"#;
351
352        let response: SmsListResponse = serde_xml_rs::from_str(xml_with_count).unwrap();
353        assert_eq!(response.count, Some("1".to_string()));
354        assert_eq!(response.message_count(), 1);
355        assert_eq!(response.messages.messages.len(), 1);
356    }
357
358    #[test]
359    fn test_sms_list_response_multiple_messages() {
360        let xml_multiple_messages = r#"<response>
361    <Count>2</Count>
362    <Messages>
363        <Message>
364            <Smstat>0</Smstat>
365            <Index>40003</Index>
366            <Phone>+48616673870</Phone>
367            <Content>Test message 1</Content>
368            <Date>2025-06-09 17:08:58</Date>
369            <Sca></Sca>
370            <SaveType>0</SaveType>
371            <Priority>0</Priority>
372            <SmsType>1</SmsType>
373        </Message>
374        <Message>
375            <Smstat>1</Smstat>
376            <Index>40002</Index>
377            <Phone>3350</Phone>
378            <Content>Test message 2</Content>
379            <Date>2024-11-22 12:32:12</Date>
380            <Sca></Sca>
381            <SaveType>0</SaveType>
382            <Priority>0</Priority>
383            <SmsType>5</SmsType>
384        </Message>
385    </Messages>
386</response>"#;
387
388        let response: SmsListResponse = serde_xml_rs::from_str(xml_multiple_messages).unwrap();
389        assert_eq!(response.count, Some("2".to_string()));
390        assert_eq!(response.message_count(), 2);
391        assert_eq!(response.messages.messages.len(), 2);
392
393        assert_eq!(response.messages.messages[0].index, "40003");
394        assert_eq!(response.messages.messages[0].phone, "+48616673870");
395        assert!(response.messages.messages[0].is_unread());
396
397        assert_eq!(response.messages.messages[1].index, "40002");
398        assert_eq!(response.messages.messages[1].phone, "3350");
399        assert!(response.messages.messages[1].is_read());
400    }
401}