aboutsummaryrefslogtreecommitdiff
path: root/src/client.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/client.rs178
1 files changed, 178 insertions, 0 deletions
diff --git a/src/client.rs b/src/client.rs
new file mode 100644
index 0000000..2e38ba9
--- /dev/null
+++ b/src/client.rs
@@ -0,0 +1,178 @@
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fmt::{self, Display, Formatter};
+
+#[derive(Debug, Clone)]
+pub struct TwilioClient<'a> {
+ // the HTTP client
+ pub client: reqwest::blocking::Client,
+ // the base URL
+ pub base_url: reqwest::Url,
+ // account SID
+ pub account_sid: &'a String,
+ // authentication token
+ pub auth_token: &'a String,
+}
+
+const TWILIO_API_PATH: &str = "/2010-04-01/Accounts";
+
+// list of possible values: https://www.twilio.com/docs/sms/api/message-resource#message-status-values
+#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
+#[allow(non_camel_case_types)]
+pub enum MessageStatus {
+ accepted,
+ scheduled,
+ queued,
+ sending,
+ sent,
+ receiving,
+ received,
+ delivered,
+ undelivered,
+ failed,
+ read,
+ canceled,
+}
+
+impl fmt::Display for MessageStatus {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+// response from twilio's API
+#[derive(Deserialize, Debug, Clone)]
+pub struct Message {
+ pub status: MessageStatus,
+}
+
+// error from twilio's API
+#[derive(Deserialize, Debug, Clone)]
+pub struct Error {
+ pub status: u16,
+ pub code: Option<u16>,
+ pub message: String,
+ pub more_info: Option<String>,
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "({}) {}", self.status, self.message)?;
+ Ok(())
+ }
+}
+
+impl<'a> TwilioClient<'a> {
+ pub fn new(account_sid: &'a String, auth_token: &'a String, base_url: reqwest::Url) -> Self {
+ let client = reqwest::blocking::Client::builder()
+ .user_agent("fcuny/send-sms")
+ .build()
+ .unwrap();
+
+ Self {
+ client,
+ base_url,
+ account_sid,
+ auth_token,
+ }
+ }
+
+ // send an SMS through twilio
+ pub fn send(&self, message: crate::message::Message) -> Result<Message, Error> {
+ let path = format!("{}/{}/Messages.json", TWILIO_API_PATH, self.account_sid);
+
+ let mut url = self.base_url.clone();
+ url.set_path(&path);
+
+ let mut form = HashMap::new();
+ form.insert("From", message.from);
+ form.insert("To", message.to);
+ form.insert("Body", message.body);
+
+ let response = self
+ .client
+ .post(url.as_str())
+ .basic_auth(self.account_sid, Some(self.auth_token))
+ .form(&form)
+ .send()
+ .unwrap();
+
+ match response.status() {
+ reqwest::StatusCode::CREATED | reqwest::StatusCode::OK => {}
+ _ => {
+ let error_response: Error = response.json().unwrap();
+ return Err(error_response);
+ }
+ };
+
+ Ok(response.json().unwrap())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::client::TwilioClient;
+ use crate::message::Message;
+ use httpmock::prelude::*;
+ use serde_json::json;
+
+ fn get_message() -> Message {
+ Message {
+ to: Some("1234".to_string()),
+ from: Some("1234".to_string()),
+ body: Some("test".to_string()),
+ }
+ }
+
+ #[test]
+ fn test_send_message() {
+ let server = MockServer::start();
+ let mock = server.mock(|when, then| {
+ when.method(POST)
+ .path("/2010-04-01/Accounts/test/Messages.json");
+ then.status(200).json_body(json!({"status": "queued"}));
+ });
+
+ let message = get_message();
+
+ let account_sid = "test".to_string();
+ let auth_token = "test".to_string();
+ let client = TwilioClient::new(
+ &account_sid,
+ &auth_token,
+ reqwest::Url::parse(&server.base_url()).unwrap(),
+ );
+
+ let result = client.send(message);
+ mock.assert();
+ let msg: crate::client::Message = result.unwrap();
+ assert_eq!(msg.status, crate::client::MessageStatus::queued);
+ }
+
+ #[test]
+ fn test_send_message_error() {
+ let server = MockServer::start();
+ let mock_invalid = server.mock(|when, then| {
+ when.method(POST)
+ .path("/2010-04-01/Accounts/test/Messages.json");
+ then.status(400).json_body(json!({
+ "status": 400,
+ "message": "The 'From' number +15005550001 is not a valid phone number, shortcode, or alphanumeric sender ID",
+ }));
+ });
+
+ let message = get_message();
+
+ let account_sid = "test".to_string();
+ let auth_token = "test".to_string();
+ let client = TwilioClient::new(
+ &account_sid,
+ &auth_token,
+ reqwest::Url::parse(&server.base_url()).unwrap(),
+ );
+
+ let result = client.send(message);
+ mock_invalid.assert();
+ assert!(matches!(result, Err(crate::client::Error { .. })));
+ }
+}