twilio/
lib.rs

1mod call;
2mod message;
3pub mod twiml;
4mod webhook;
5
6pub use call::{Call, OutboundCall};
7use headers::authorization::{Authorization, Basic};
8use headers::{ContentType, HeaderMapExt};
9use hyper::client::connect::HttpConnector;
10use hyper::{Body, Method, StatusCode};
11use hyper_tls::HttpsConnector;
12pub use message::{Message, OutboundMessage};
13use std::collections::BTreeMap;
14use std::error::Error;
15use std::fmt::{self, Display, Formatter};
16use url::form_urlencoded;
17
18pub const GET: Method = Method::GET;
19pub const POST: Method = Method::POST;
20pub const PUT: Method = Method::PUT;
21
22#[derive(Clone)]
23pub struct Client {
24    account_id: String,
25    auth_token: String,
26    auth_header: Authorization<Basic>,
27    http_client: hyper::Client<HttpsConnector<HttpConnector>>,
28}
29
30fn url_encode(params: &[(&str, &str)]) -> String {
31    let mut url = form_urlencoded::Serializer::new(String::new());
32    for (k, v) in params {
33        url.append_pair(k, v);
34    }
35
36    url.finish()
37}
38
39#[derive(Debug)]
40pub enum TwilioError {
41    NetworkError(hyper::Error),
42    HTTPError(StatusCode),
43    ParsingError,
44    AuthError,
45    BadRequest,
46}
47
48impl Display for TwilioError {
49    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
50        match *self {
51            TwilioError::NetworkError(ref e) => e.fmt(f),
52            TwilioError::HTTPError(ref s) => write!(f, "Invalid HTTP status code: {}", s),
53            TwilioError::ParsingError => f.write_str("Parsing error"),
54            TwilioError::AuthError => f.write_str("Missing `X-Twilio-Signature` header in request"),
55            TwilioError::BadRequest => f.write_str("Bad request"),
56        }
57    }
58}
59
60impl Error for TwilioError {
61    fn source(&self) -> Option<&(dyn Error + 'static)> {
62        match *self {
63            TwilioError::NetworkError(ref e) => Some(e),
64            _ => None,
65        }
66    }
67}
68
69pub trait FromMap {
70    fn from_map(m: BTreeMap<String, String>) -> Result<Box<Self>, TwilioError>;
71}
72
73impl Client {
74    pub fn new(account_id: &str, auth_token: &str) -> Client {
75        Client {
76            account_id: account_id.to_string(),
77            auth_token: auth_token.to_string(),
78            auth_header: Authorization::basic(account_id, auth_token),
79            http_client: hyper::Client::builder().build(HttpsConnector::new()),
80        }
81    }
82
83    async fn send_request<T>(
84        &self,
85        method: hyper::Method,
86        endpoint: &str,
87        params: &[(&str, &str)],
88    ) -> Result<T, TwilioError>
89    where
90        T: serde::de::DeserializeOwned,
91    {
92        let url = format!(
93            "https://api.twilio.com/2010-04-01/Accounts/{}/{}.json",
94            self.account_id, endpoint
95        );
96        let mut req = hyper::Request::builder()
97            .method(method)
98            .uri(&*url)
99            .body(Body::from(url_encode(params)))
100            .unwrap();
101
102        let mime: mime::Mime = "application/x-www-form-urlencoded".parse().unwrap();
103        req.headers_mut().typed_insert(ContentType::from(mime));
104        req.headers_mut().typed_insert(self.auth_header.clone());
105
106        let resp = self
107            .http_client
108            .request(req)
109            .await
110            .map_err(TwilioError::NetworkError)?;
111
112        match resp.status() {
113            StatusCode::CREATED | StatusCode::OK => {}
114            other => return Err(TwilioError::HTTPError(other)),
115        };
116
117        let decoded: T = hyper::body::to_bytes(resp.into_body())
118            .await
119            .map_err(TwilioError::NetworkError)
120            .and_then(|bytes| {
121                serde_json::from_slice(&bytes).map_err(|_| TwilioError::ParsingError)
122            })?;
123
124        Ok(decoded)
125    }
126
127    pub async fn respond_to_webhook<T: FromMap, F>(
128        &self,
129        req: hyper::Request<Body>,
130        mut logic: F,
131    ) -> hyper::Response<Body>
132    where
133        F: FnMut(T) -> twiml::Twiml,
134    {
135        let o: T = match self.parse_request::<T>(req).await {
136            Ok(obj) => *obj,
137            Err(_) => {
138                let mut res = hyper::Response::new(Body::from("Error.".as_bytes()));
139                *res.status_mut() = StatusCode::BAD_REQUEST;
140                return res;
141            }
142        };
143
144        let t = logic(o);
145        let body = t.as_twiml();
146        let len = body.len() as u64;
147        let mut res = hyper::Response::new(Body::from(body));
148        res.headers_mut().typed_insert(headers::ContentType::xml());
149        res.headers_mut().typed_insert(headers::ContentLength(len));
150        res
151    }
152}