guerrillamail_client/
models.rs1use serde::Deserialize;
4use serde::Deserializer;
5use std::fmt;
6
7#[derive(Debug, Clone, Deserialize)]
9pub struct Message {
10 pub mail_id: String,
12 pub mail_from: String,
14 pub mail_subject: String,
16 pub mail_excerpt: String,
18 pub mail_timestamp: String,
20}
21
22#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
24pub struct Attachment {
25 #[serde(default, rename = "f")]
27 pub filename: String,
28 #[serde(default, rename = "t")]
30 pub content_type_or_hint: Option<String>,
31 #[serde(default, rename = "p")]
33 pub part_id: String,
34}
35
36#[derive(Deserialize)]
37#[serde(untagged)]
38enum StrOrNumU32 {
39 Str(String),
40 Num(u64),
41}
42
43fn de_u32_str_or_num_opt<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
44where
45 D: Deserializer<'de>,
46{
47 let value = Option::<StrOrNumU32>::deserialize(deserializer)?;
48 match value {
49 None => Ok(None),
50 Some(StrOrNumU32::Str(raw)) => raw
51 .trim()
52 .parse::<u32>()
53 .map(Some)
54 .map_err(serde::de::Error::custom),
55 Some(StrOrNumU32::Num(num)) => u32::try_from(num)
56 .map(Some)
57 .map_err(serde::de::Error::custom),
58 }
59}
60
61#[derive(Clone, Deserialize)]
63pub struct EmailDetails {
64 pub mail_id: String,
66 pub mail_from: String,
68 pub mail_subject: String,
70 pub mail_body: String,
72 pub mail_timestamp: String,
74 #[serde(default, rename = "att_info")]
76 pub attachments: Vec<Attachment>,
77 #[serde(default, rename = "att", deserialize_with = "de_u32_str_or_num_opt")]
79 pub attachment_count: Option<u32>,
80 #[serde(default)]
82 pub sid_token: Option<String>,
83}
84
85impl fmt::Debug for EmailDetails {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.debug_struct("EmailDetails")
88 .field("mail_id", &self.mail_id)
89 .field("mail_from", &self.mail_from)
90 .field("mail_subject", &self.mail_subject)
91 .field("mail_body", &self.mail_body)
92 .field("mail_timestamp", &self.mail_timestamp)
93 .field("attachments", &self.attachments)
94 .field("attachment_count", &self.attachment_count)
95 .field("sid_token", &self.sid_token.as_ref().map(|_| "<redacted>"))
96 .finish()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use serde_json::json;
104
105 #[test]
106 fn email_details_deserialize_without_attachments() {
107 let value = json!({
108 "mail_id": "123",
109 "mail_from": "sender@example.com",
110 "mail_subject": "Hello",
111 "mail_body": "<p>Body</p>",
112 "mail_timestamp": "1700000000"
113 });
114
115 let details: EmailDetails = serde_json::from_value(value).unwrap();
116 assert_eq!(details.mail_id, "123");
117 assert!(details.attachments.is_empty());
118 assert!(details.attachment_count.is_none());
119 assert!(details.sid_token.is_none());
120 }
121
122 #[test]
123 fn email_details_deserialize_with_attachments() {
124 let value = json!({
125 "mail_id": "123",
126 "mail_from": "sender@example.com",
127 "mail_subject": "Hello",
128 "mail_body": "<p>Body</p>",
129 "mail_timestamp": "1700000000",
130 "att": 1,
131 "att_info": [
132 { "f": "file.txt", "t": "text/plain", "p": "99" }
133 ],
134 "sid_token": "sid123"
135 });
136
137 let details: EmailDetails = serde_json::from_value(value).unwrap();
138 assert_eq!(details.attachment_count, Some(1));
139 assert_eq!(
140 details.attachments,
141 vec![Attachment {
142 filename: "file.txt".to_string(),
143 content_type_or_hint: Some("text/plain".to_string()),
144 part_id: "99".to_string(),
145 }]
146 );
147 assert_eq!(details.sid_token.as_deref(), Some("sid123"));
148 }
149
150 #[test]
151 fn email_details_deserialize_attachment_count_string() {
152 let value = json!({
153 "mail_id": "123",
154 "mail_from": "sender@example.com",
155 "mail_subject": "Hello",
156 "mail_body": "<p>Body</p>",
157 "mail_timestamp": "1700000000",
158 "att": "1"
159 });
160
161 let details: EmailDetails = serde_json::from_value(value).unwrap();
162 assert_eq!(details.attachment_count, Some(1));
163 }
164
165 #[test]
166 fn email_details_deserialize_attachment_count_missing() {
167 let value = json!({
168 "mail_id": "123",
169 "mail_from": "sender@example.com",
170 "mail_subject": "Hello",
171 "mail_body": "<p>Body</p>",
172 "mail_timestamp": "1700000000"
173 });
174
175 let details: EmailDetails = serde_json::from_value(value).unwrap();
176 assert!(details.attachment_count.is_none());
177 }
178}