postmark_client/
client.rs

1use reqwest::{Response, StatusCode};
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4
5use crate::errors::PostmarkClientError;
6use crate::types::APIError;
7use crate::{errors, Result};
8
9const POSTMARK_API_URL_BASE: &str = "https://api.postmarkapp.com";
10const POSTMARK_TOKEN_HEADER: &str = "X-Postmark-Server-Token";
11
12/// Client exposes the Postmark functionality to be easily used
13/// within rust applications.
14///
15/// The client can create its own internal reqwest::Client or
16/// one can be provided if there are specific configurations
17/// desired by the application.
18pub struct Client {
19    http_client: reqwest::Client,
20    token: String,
21}
22
23impl Client {
24    /// Create a new postmark client with the provided token
25    pub fn new(token: String) -> Client {
26        Client {
27            http_client: reqwest::Client::new(),
28            token,
29        }
30    }
31    /// Create a new postmark client with the provided reqwest client and token
32    pub fn new_with_client(token: String, http_client: reqwest::Client) -> Client {
33        Client { http_client, token }
34    }
35    /// Extracts the error based on the status code from postmark when the code
36    /// is not a successful status code.
37    ///
38    /// Based on <https://postmarkapp.com/developer/api/overview#response-codes>
39    async fn extract_error(response: Response) -> errors::PostmarkClientError {
40        let status = response.status();
41        match status {
42            StatusCode::UNAUTHORIZED => PostmarkClientError::Unauthorized,
43            StatusCode::NOT_FOUND => PostmarkClientError::RequestToLarge,
44            StatusCode::UNPROCESSABLE_ENTITY => {
45                let data = response.json::<APIError>().await;
46                match data {
47                    Err(e) => PostmarkClientError::Reqwest(e),
48                    Ok(e) => PostmarkClientError::UnprocessableEntity(e),
49                }
50            }
51            StatusCode::TOO_MANY_REQUESTS => PostmarkClientError::RateLimitExceeded,
52            StatusCode::INTERNAL_SERVER_ERROR => PostmarkClientError::InternalServerError,
53            StatusCode::SERVICE_UNAVAILABLE => PostmarkClientError::ServiceUnavailable,
54            _ => PostmarkClientError::UnknownPostmarkStatus(status),
55        }
56    }
57
58    pub(crate) async fn get<R>(&self, path: &str) -> Result<R>
59    where
60        R: DeserializeOwned,
61    {
62        let response = self
63            .http_client
64            .get(format!("{:}{:}", POSTMARK_API_URL_BASE, path))
65            .header(POSTMARK_TOKEN_HEADER, &self.token)
66            .send()
67            .await?;
68
69        if !response.status().is_success() {
70            return Err(Client::extract_error(response).await);
71        }
72
73        Ok(response.json::<R>().await?)
74    }
75
76    pub(crate) async fn get_with_query<Q, R>(&self, path: &str, query: &Q) -> Result<R>
77    where
78        Q: Serialize + ?Sized,
79        R: DeserializeOwned,
80    {
81        let response = self
82            .http_client
83            .get(format!("{:}{:}", POSTMARK_API_URL_BASE, path))
84            .header(POSTMARK_TOKEN_HEADER, &self.token)
85            .query(query)
86            .send()
87            .await?;
88
89        if !response.status().is_success() {
90            return Err(Client::extract_error(response).await);
91        }
92
93        Ok(response.json::<R>().await?)
94    }
95
96    pub(crate) async fn post<B, R>(&self, path: &str, body: &B) -> Result<R>
97    where
98        B: Serialize + ?Sized,
99        R: DeserializeOwned,
100    {
101        let response = self
102            .http_client
103            .post(format!("{:}{:}", POSTMARK_API_URL_BASE, path))
104            .header(POSTMARK_TOKEN_HEADER, &self.token)
105            .json(body)
106            .send()
107            .await?;
108
109        if !response.status().is_success() {
110            return Err(Client::extract_error(response).await);
111        }
112
113        Ok(response.json::<R>().await?)
114    }
115
116    pub(crate) async fn put<R>(&self, path: &str) -> Result<R>
117    where
118        R: DeserializeOwned,
119    {
120        let response = self
121            .http_client
122            .post(format!("{:}{:}", POSTMARK_API_URL_BASE, path))
123            .header(POSTMARK_TOKEN_HEADER, &self.token)
124            .send()
125            .await?;
126
127        if !response.status().is_success() {
128            return Err(Client::extract_error(response).await);
129        }
130
131        Ok(response.json::<R>().await?)
132    }
133}