twilio_async/
lib.rs

1#![allow(dead_code)]
2#![doc(html_root_url = "https://docs.rs/twilio-async/0.5.0")]
3#![allow(
4    clippy::cognitive_complexity,
5    clippy::large_enum_variant,
6    clippy::needless_doctest_main,
7    clippy::needless_lifetimes
8)]
9#![warn(
10    missing_debug_implementations,
11    // missing_docs,
12    rust_2018_idioms,
13    unreachable_pub
14)]
15#![deny(rustdoc::broken_intra_doc_links)]
16#![doc(test(
17    no_crate_inject,
18    attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
19))]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21
22//! An async client library for interaction with twilio APIs
23//! An async and ergonomic wrapper around Twilio API & TwiML.
24//!
25//! All types can run `run()` or a similar function. They return a value that
26//! implements `Deserialize`.
27//!
28//! The `examples/` dir has up to date working example code.
29//!
30//! Messages:
31//!
32//! ```rust,no_run
33//!
34//! use std::env;
35//! use twilio_async::{Twilio, TwilioRequest};
36//!
37//! #[tokio::main]
38//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     let twilio = Twilio::new(env::var("TWILIO_SID")?, env::var("TWILIO_TOKEN")?)?;
40//!     // sending a message
41//!     twilio.send_msg("from", "to", "Hello World").run().await?;
42//!     // sending a body-less message with media
43//!     twilio
44//!         .send_msg("from", "to", "body")
45//!         .media("http://i0.kym-cdn.com/photos/images/newsfeed/000/377/946/0b9.jpg")
46//!         .run().await?;
47//!     // get details about a message
48//!     twilio.msg("messagesid").run().await?;
49//!     // redact a message
50//!     twilio.msg("messagesid").redact().await?;
51//!     // get a msg media url
52//!     twilio.msg("messagesid").media().await?;
53//!     // delete a msg
54//!     twilio.msg("messagesid").delete().await?;
55//!     // get all messages
56//!     twilio.msgs().run().await?;
57//!     // get all messages between some time
58//!     twilio.msgs().between("start date", "end date").run().await?;
59//!     // get all messages on a specific date
60//!     twilio.msgs().on("date").run().await?;
61//!     Ok(())
62//! }
63//! ```
64//!
65//! Calls:
66//!
67//! ```rust,no_run
68//!
69//! # use std::{error::Error, env};
70//! # use twilio_async::{Twilio, TwilioJson, TwilioRequest};
71//!
72//! # #[tokio::main]
73//! # async fn main() -> Result<(), Box<dyn Error>> {
74//! let twilio = Twilio::new(env::var("TWILIO_SID")?, env::var("TWILIO_TOKEN")?)?;
75//! if let TwilioJson::Success(call) = twilio
76//!     .call("from", "to", "http://demo.twilio.com/docs/voice.xml")
77//!     .run().await? {
78//!     // do something with `call`
79//! }
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! Twiml:
85//!
86//! ```rust
87//! # fn main() {
88//! use twilio_async::twiml::{Response, Twiml};
89//!
90//! let resp = Response::new()
91//!     .say("Hello World") // builder pattern also supports say(Say::new("Hello World").lang("de")...)
92//!     .play("https://api.twilio.com/Cowbell.mp3")
93//!     .build();
94//! let s = "<Response><Say voice=\"man\" language=\"en\" loop=\"1\">Hello World</Say><Play loop=\"1\">https://api.twilio.com/Cowbell.mp3</Play></Response>";
95//! assert_eq!(resp.unwrap(), s.to_string());
96//! # }
97//! ```
98
99#[macro_use]
100mod macros;
101mod call;
102mod conference;
103pub mod error;
104mod message;
105mod recording;
106pub mod twiml;
107
108pub use crate::{call::*, conference::*, error::*, message::*, recording::*};
109
110use async_trait::async_trait;
111use hyper::{client::HttpConnector, Body, Client, Method, Request};
112use hyper_tls::HttpsConnector;
113use serde::Deserialize;
114use std::borrow::Borrow;
115
116pub use typed_headers::{Authorization, Credentials};
117pub use url::{form_urlencoded, Url};
118
119#[derive(Debug)]
120pub struct Twilio {
121    sid: String,
122    auth: Authorization,
123    client: Client<HttpsConnector<HttpConnector>, hyper::Body>,
124}
125
126pub type TwilioResp<T> = Result<T, TwilioErr>;
127
128impl Twilio {
129    pub fn new<S, P>(sid: S, token: P) -> TwilioResult<Twilio>
130    where
131        S: Into<String>,
132        P: AsRef<str>,
133    {
134        let sid = sid.into();
135        let client = Client::builder().build::<_, hyper::Body>(HttpsConnector::new());
136
137        Ok(Twilio {
138            auth: Authorization(Credentials::basic(&sid, token.as_ref())?),
139            sid,
140            client,
141        })
142    }
143
144    pub fn send_msg<'a>(&'a self, from: &'a str, to: &'a str, body: &'a str) -> SendMsg<'a> {
145        SendMsg {
146            msg: Msg::new(from, to, body),
147            client: self,
148        }
149    }
150
151    pub fn msg<'a>(&'a self, message_sid: &'a str) -> GetMessage<'a> {
152        GetMessage {
153            message_sid,
154            client: self,
155        }
156    }
157
158    pub fn msgs(&self) -> Messages<'_> {
159        Messages { client: self }
160    }
161
162    pub fn call<'a>(&'a self, from: &'a str, to: &'a str, url: &'a str) -> SendCall<'a> {
163        SendCall {
164            call: Call::new(from, to, url),
165            client: self,
166        }
167    }
168
169    pub fn conference<'a>(&'a self, sid: &'a str) -> GetConference<'a> {
170        GetConference {
171            conference: Conference::new(sid),
172            client: self,
173        }
174    }
175
176    pub fn conferences(&self) -> Conferences<'_> {
177        Conferences { client: self }
178    }
179
180    pub fn recording<'a>(&'a self, sid: &'a str) -> GetRecording<'a> {
181        GetRecording {
182            recording: Recording::new(sid),
183            client: self,
184        }
185    }
186
187    pub fn recordings(&self) -> Recordings<'_> {
188        Recordings { client: self }
189    }
190}
191
192#[derive(Debug, Deserialize)]
193#[serde(untagged)]
194pub enum TwilioJson<T> {
195    Success(T),
196    Fail {
197        code: usize,
198        message: String,
199        status: usize,
200    },
201}
202
203#[async_trait]
204pub trait Execute {
205    fn request<U>(
206        &self,
207        method: Method,
208        url: U,
209        body: Option<String>,
210    ) -> Result<Request<Body>, TwilioErr>
211    where
212        U: AsRef<str>;
213    async fn execute<U, D>(
214        &self,
215        method: Method,
216        url: U,
217        body: Option<String>,
218    ) -> TwilioResp<TwilioJson<D>>
219    where
220        U: AsRef<str> + Send,
221        D: for<'de> serde::Deserialize<'de>;
222}
223
224#[async_trait]
225pub trait TwilioRequest: Execute {
226    type Resp: for<'de> serde::Deserialize<'de>;
227    async fn run(&self) -> TwilioResp<TwilioJson<Self::Resp>>;
228}
229
230pub fn encode_pairs<I, K, V>(pairs: I) -> Option<String>
231where
232    K: AsRef<str>,
233    V: AsRef<str>,
234    I: IntoIterator,
235    I::Item: Borrow<(K, V)>,
236{
237    let mut partial = form_urlencoded::Serializer::new(String::new());
238    for pair in pairs {
239        let &(ref k, ref v) = pair.borrow();
240        partial.append_pair(k.as_ref(), v.as_ref());
241    }
242    let encoded = partial.finish();
243    Some(encoded)
244}
245
246pub fn url_encode<I, K, V>(pairs: I) -> String
247where
248    K: AsRef<str>,
249    V: AsRef<str>,
250    I: IntoIterator,
251    I::Item: Borrow<(K, V)>,
252{
253    pairs
254        .into_iter()
255        .map(|pair| {
256            let &(ref k, ref v) = pair.borrow();
257            format!("{}={}", k.as_ref(), v.as_ref())
258        })
259        .fold(String::new(), |mut acc, item| {
260            acc.push_str(&item);
261            acc.push('&');
262            acc
263        })
264}