1mod error;
32
33pub use error::PostalError;
34
35use reqwest::StatusCode;
36use serde::{Deserialize, Serialize};
37use serde_json::Value as Json;
38use std::collections::HashMap;
39use url::Url;
40
41#[derive(Debug, Clone, Eq, PartialEq)]
43pub struct Client {
44 address: Url,
45 token: String,
46}
47
48impl Client {
49 pub fn new<U, S>(url: U, token: S) -> Result<Self, PostalError>
51 where
52 U: AsRef<str>,
53 S: Into<String>,
54 {
55 let url = Url::parse(url.as_ref())?;
56 let token = token.into();
57
58 Ok(Self {
59 address: url,
60 token,
61 })
62 }
63
64 pub async fn send<M: Into<Message>>(&self, message: M) -> Result<Vec<SendResult>, PostalError> {
66 let address = self.address.join("/api/v1/send/message")?;
67 let message = message.into();
68
69 let client = reqwest::Client::new();
70 let res = client
71 .post(address)
72 .json(&message)
73 .header("X-Server-API-Key", &self.token)
74 .send()
75 .await?;
76
77 handle_send(res).await
78 }
79
80 pub async fn send_raw<M: Into<RawMessage>>(
82 &self,
83 message: M,
84 ) -> Result<Vec<SendResult>, PostalError> {
85 let address = self.address.join("/api/v1/send/raw")?;
86 let message = message.into();
87
88 let client = reqwest::Client::new();
89 let res = client
90 .post(address)
91 .json(&message)
92 .header("X-Server-API-Key", &self.token)
93 .send()
94 .await?;
95
96 handle_send(res).await
97 }
98
99 pub async fn get_message_details<I: Into<DetailsInterest>>(
107 &self,
108 interest: I,
109 ) -> Result<HashMap<String, Json>, PostalError> {
110 let interest = interest.into();
111 let address = self.address.join("/api/v1/messages/message")?;
112
113 let client = reqwest::Client::new();
114 let body: Json = interest.into();
115 let res = client
116 .post(address)
117 .json(&body)
118 .header("X-Server-API-Key", &self.token)
119 .send()
120 .await?;
121
122 check_status(res.status())?;
123
124 let data: api_structures::Responce<HashMap<String, Json>> = res.json().await?;
125 let data = check_responce(data)?;
126
127 Ok(data)
128 }
129
130 pub async fn get_message_deliveries(
132 &self,
133 id: MessageHash,
134 ) -> Result<Vec<HashMap<String, Json>>, PostalError> {
135 let address = self.address.join("/api/v1/messages/deliveries")?;
136
137 let client = reqwest::Client::new();
138 let body: Json = serde_json::json!({ "id": id });
139 let res = client
140 .post(address)
141 .json(&body)
142 .header("X-Server-API-Key", &self.token)
143 .send()
144 .await?;
145
146 check_status(res.status())?;
147
148 let data: api_structures::Responce<Vec<HashMap<String, Json>>> = res.json().await?;
149 let data = check_responce(data)?;
150
151 Ok(data)
152 }
153}
154
155async fn handle_send(resp: reqwest::Response) -> Result<Vec<SendResult>, PostalError> {
156 check_status(resp.status())?;
157
158 let data: api_structures::Responce<api_structures::MessageSucessData> = resp.json().await?;
159 let data = check_responce(data)?;
160
161 let messages = data
162 .messages
163 .into_iter()
164 .map(|(to, m)| SendResult { to, id: m.id })
165 .collect();
166
167 Ok(messages)
168}
169
170fn check_status(sc: StatusCode) -> Result<(), PostalError> {
171 match sc {
172 StatusCode::OK => Ok(()),
173 StatusCode::INTERNAL_SERVER_ERROR => Err(PostalError::InternalServerError),
174 StatusCode::MOVED_PERMANENTLY | StatusCode::PERMANENT_REDIRECT => {
175 Err(PostalError::ExpectedAlternativeUrl)
176 }
177 StatusCode::SERVICE_UNAVAILABLE => Err(PostalError::ServiceUnavailableError),
178 _ => unreachable!(),
181 }
182}
183
184fn check_responce<T>(data: api_structures::Responce<T>) -> Result<T, PostalError> {
185 match data {
186 api_structures::Responce::Success { data, .. } => Ok(data),
187 api_structures::Responce::Error { data, .. } => Err(PostalError::Error {
188 code: data.code,
189 message: data.message,
190 }),
191 api_structures::Responce::ParameterError {} => unimplemented!(),
193 }
194}
195
196pub type MessageHash = u64;
199
200#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
202pub struct Message {
203 pub to: Option<Vec<String>>,
205 pub cc: Option<Vec<String>>,
207 pub bcc: Option<Vec<String>>,
209 pub from: Option<String>,
211 pub sender: Option<String>,
213 pub subject: Option<String>,
215 pub tag: Option<String>,
217 pub reply_to: Option<String>,
219 pub plain_body: Option<String>,
221 pub html_body: Option<String>,
223 pub attachments: Option<Vec<Vec<u8>>>,
225 pub headers: Option<MessageHash>,
227 pub bounce: Option<bool>,
229}
230
231impl Message {
232 pub fn from<S: Into<String>>(mut self, s: S) -> Self {
233 self.from = Some(s.into());
234 self
235 }
236
237 pub fn to(mut self, to: &[String]) -> Self {
238 self.to = Some(to.to_vec());
239 self
240 }
241
242 pub fn subject<S: Into<String>>(mut self, s: S) -> Self {
243 self.subject = Some(s.into());
244 self
245 }
246
247 pub fn text<S: Into<String>>(mut self, s: S) -> Self {
248 self.plain_body = Some(s.into());
249 self
250 }
251
252 pub fn html<S: Into<String>>(mut self, s: S) -> Self {
253 self.html_body = Some(s.into());
254 self
255 }
256}
257
258#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
261pub struct RawMessage {
262 pub mail_from: String,
264 pub rcpt_to: Vec<String>,
266 pub data: String,
268 pub bounce: Option<bool>,
270}
271
272impl RawMessage {
273 pub fn new<S1: Into<String>, S2: Into<String>>(to: &[String], from: S1, data: S2) -> Self {
274 Self {
275 rcpt_to: to.to_owned(),
276 mail_from: from.into(),
277 data: data.into(),
278 bounce: None,
279 }
280 }
281}
282
283#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
286pub struct DetailsInterest {
287 id: MessageHash,
288 status: Option<()>,
289 details: Option<()>,
290 inspection: Option<()>,
291 plain_body: Option<()>,
292 html_body: Option<()>,
293 attachments: Option<()>,
294 headers: Option<()>,
295 raw_message: Option<()>,
296}
297
298impl DetailsInterest {
299 pub fn new(id: MessageHash) -> Self {
300 id.into()
301 }
302
303 pub fn with_status(mut self) -> Self {
304 self.status = Some(());
305 self
306 }
307
308 pub fn with_details(mut self) -> Self {
309 self.details = Some(());
310 self
311 }
312
313 pub fn with_inspection(mut self) -> Self {
314 self.inspection = Some(());
315 self
316 }
317
318 pub fn with_plain_body(mut self) -> Self {
319 self.plain_body = Some(());
320 self
321 }
322
323 pub fn with_html_body(mut self) -> Self {
324 self.html_body = Some(());
325 self
326 }
327
328 pub fn with_headers(mut self) -> Self {
329 self.headers = Some(());
330 self
331 }
332
333 pub fn with_raw_message(mut self) -> Self {
334 self.raw_message = Some(());
335 self
336 }
337
338 fn build_expansions_list(&self) -> Option<Vec<Json>> {
339 let mut expansions: Option<Vec<Json>> = None;
340 if self.status.is_some() {
341 expansions = Some(expansions.unwrap_or_default());
342 expansions
343 .as_mut()
344 .unwrap()
345 .push(Json::String("status".to_owned()));
346 }
347 if self.details.is_some() {
348 expansions = Some(expansions.unwrap_or_default());
349 expansions
350 .as_mut()
351 .unwrap()
352 .push(Json::String("details".to_owned()));
353 }
354 if self.inspection.is_some() {
355 expansions = Some(expansions.unwrap_or_default());
356 expansions
357 .as_mut()
358 .unwrap()
359 .push(Json::String("inspection".to_owned()));
360 }
361 if self.plain_body.is_some() {
362 expansions = Some(expansions.unwrap_or_default());
363 expansions
364 .as_mut()
365 .unwrap()
366 .push(Json::String("plain_body".to_owned()));
367 }
368 if self.html_body.is_some() {
369 expansions = Some(expansions.unwrap_or_default());
370 expansions
371 .as_mut()
372 .unwrap()
373 .push(Json::String("html_body".to_owned()));
374 }
375 if self.headers.is_some() {
376 expansions = Some(expansions.unwrap_or_default());
377 expansions
378 .as_mut()
379 .unwrap()
380 .push(Json::String("headers".to_owned()));
381 }
382 if self.raw_message.is_some() {
383 expansions = Some(expansions.unwrap_or_default());
384 expansions
385 .as_mut()
386 .unwrap()
387 .push(Json::String("raw_message".to_owned()));
388 }
389
390 expansions
391 }
392}
393
394impl Into<DetailsInterest> for MessageHash {
395 fn into(self) -> DetailsInterest {
396 DetailsInterest {
397 id: self,
398 status: None,
399 details: None,
400 inspection: None,
401 plain_body: None,
402 html_body: None,
403 attachments: None,
404 headers: None,
405 raw_message: None,
406 }
407 }
408}
409
410impl Into<Json> for DetailsInterest {
411 fn into(self) -> Json {
412 let mut map: HashMap<String, Json> = HashMap::new();
413 map.insert("id".to_owned(), self.id.into());
414
415 let expansions = self.build_expansions_list();
416 if let Some(expansions) = expansions {
417 map.insert("_expansions".to_owned(), Json::Array(expansions));
418 }
419
420 serde_json::json!(map)
421 }
422}
423
424#[derive(Debug, Eq, PartialEq, Clone)]
426pub struct SendResult {
427 pub to: String,
429 pub id: MessageHash,
432}
433
434mod api_structures {
435 use super::*;
436
437 #[derive(Debug, Clone, Serialize, Deserialize)]
438 #[serde(tag = "status", rename_all = "camelCase")]
439 pub enum Responce<D> {
440 Success {
441 time: f64,
442 flags: HashMap<String, u64>,
443 data: D,
444 },
445 ParameterError {},
446 Error {
447 time: f64,
448 flags: HashMap<String, u64>,
449 data: ResponceError,
450 },
451 }
452
453 #[derive(Debug, Clone, Serialize, Deserialize)]
454 pub struct MessageSucessData {
455 pub message_id: String,
456 pub messages: HashMap<String, MessageDataTo>,
457 }
458
459 #[derive(Debug, Clone, Serialize, Deserialize)]
460 pub struct MessageDataTo {
461 pub id: u64,
462 pub token: String,
463 }
464
465 #[derive(Debug, Clone, Serialize, Deserialize)]
466 pub struct ResponceError {
467 pub code: String,
468 pub message: String,
469 }
470}