clickatell_api/one_api/
client.rs

1use crate::one_api::{balance, message_status, send_messages};
2use crate::one_api::{error::Error, result::Result};
3use reqwest::{
4    header::HeaderMap, header::HeaderValue, header::ACCEPT, header::AUTHORIZATION,
5    header::CONTENT_TYPE, Client as HTTPClient,
6};
7use std::default::Default;
8
9pub const HOSTNAME: &str = "https://platform.clickatell.com";
10const MESSAGE_PATH: &str = "/v1/message";
11const BALANCE_PATH: &str = "/v1/balance";
12const APPLICATION_JSON: &str = "application/json";
13
14/// Clickatell One messaging gateway client
15pub struct Client {
16    hostname: String,
17    http_client: HTTPClient,
18}
19
20impl Client {
21    pub fn new(api_key: &str) -> Result<Self> {
22        Self::builder().api_key(api_key).build()
23    }
24
25    pub fn builder() -> ClientBuilder {
26        ClientBuilder::default()
27    }
28
29    /// Send messages via the gateway
30    ///
31    /// A return of `Ok` does not mean the messages have been successfully delivered, only that
32    /// the messages have been accepted by the gateway. Message status can be queried with
33    /// [message_status].
34    ///
35    /// ```rust,ignore
36    /// let mut request = send_messages::Request::new();
37    /// request.add_message(Channel::SMS, to, "This is an SMS message");
38    /// request.add_message(Channel::WhatsApp, to, "This is a WhatsApp message");
39    ///
40    /// let response = client.send_messages(request).await?;
41    /// for message_response in response.messages() {
42    ///     println!("{} {}", message_response.to, message_response.message_id());
43    /// }
44    /// ```
45    pub async fn send_messages(
46        &self,
47        send_messages_request: send_messages::Request,
48    ) -> Result<send_messages::Response> {
49        if send_messages_request.message_count() >= 100 {
50            return Err(Error::TooManyMessages);
51        }
52
53        let response = self
54            .http_client
55            .post(format!("{}{MESSAGE_PATH}", self.hostname))
56            .json(&send_messages_request)
57            .send()
58            .await?;
59
60        Ok(response.json::<send_messages::Response>().await?)
61    }
62
63    /// Query the delivery status of a message
64    ///
65    /// ```rust,ignore
66    /// let request = message_status::Request::new(message_id);
67    /// let status_response = client.message_status(request).await?;
68    /// println!("Status: {}", status_response.status())
69    /// ```
70    pub async fn message_status(
71        &self,
72        request: message_status::Request,
73    ) -> Result<message_status::Response> {
74        let response = self
75            .http_client
76            .get(format!("{}{MESSAGE_PATH}/{}", self.hostname, request))
77            .send()
78            .await?;
79
80        Ok(response.json::<message_status::Response>().await?)
81    }
82
83    /// Return the balance of your Clickatell account
84    ///
85    /// ```rust,ignore
86    /// let balance_response = client.balance().await?;
87    /// println!("Your balance is {} {}", balance_response.currency, balance_response.balance);
88    /// ```
89    pub async fn balance(&self) -> Result<balance::Response> {
90        let response = self
91            .http_client
92            .get(format!("{}{BALANCE_PATH}", self.hostname))
93            .send()
94            .await?;
95
96        Ok(response.json::<balance::Response>().await?)
97    }
98}
99
100/// Used to create a [Client] that can point to a different URL than the default Clickatell One
101/// gateway 
102pub struct ClientBuilder {
103    hostname: Option<String>,
104    api_key: Option<String>,
105}
106
107impl Default for ClientBuilder {
108    fn default() -> Self {
109        ClientBuilder {
110            hostname: Some(HOSTNAME.to_string()),
111            api_key: None,
112        }
113    }
114}
115
116impl ClientBuilder {
117    pub fn api_key(mut self, api_key: &str) -> Self {
118        self.api_key = Some(api_key.to_string());
119        self
120    }
121
122    pub fn hostname(mut self, hostname: &str) -> Self {
123        self.hostname = Some(hostname.to_string());
124        self
125    }
126
127    pub fn build(self) -> Result<Client> {
128        match (self.api_key, self.hostname) {
129            (Some(api_key), Some(hostname)) => {
130                let mut headers = HeaderMap::new();
131
132                let mut auth_value = HeaderValue::from_str(&api_key)?;
133                auth_value.set_sensitive(true);
134
135                headers.insert(AUTHORIZATION, auth_value);
136                headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON)?);
137                headers.insert(ACCEPT, HeaderValue::from_str(APPLICATION_JSON)?);
138
139                let http_client = HTTPClient::builder().default_headers(headers).build()?;
140
141                Ok(Client {
142                    hostname,
143                    http_client,
144                })
145            }
146            (None, _) => Err(Error::ApiKeyNotSet),
147            (Some(_), None) => Err(Error::HostnameNotSet),
148        }
149    }
150}