#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::needless_lifetimes))]
use core::fmt::{self, Write};
use serde::Deserialize;
mod encoder;
mod ser;
pub const REST_API_URL: &str = "api.twilio.com/2010-04-01/Accounts";
pub const REST_API_SMS_ENDPOINT: &str = "Messages.json";
pub const REST_API_CALL_ENDPOINT: &str = "Calls.json";
const URL_BUFFER_SIZE: usize = 92;
pub type UrlBuffer = str_buf::StrBuf<URL_BUFFER_SIZE>;
pub const fn get_sms_base(account_sid: &str) -> UrlBuffer {
UrlBuffer::new().and("https://").and(REST_API_URL).and("/").and(account_sid).and("/").and("Messages")
}
pub const fn sms_resource_url(account_sid: &str) -> UrlBuffer {
UrlBuffer::new().and("https://").and(REST_API_URL).and("/").and(account_sid).and("/").and(REST_API_SMS_ENDPOINT)
}
pub const fn get_call_base(account_sid: &str) -> UrlBuffer {
UrlBuffer::new().and("https://").and(REST_API_URL).and("/").and(account_sid).and("/").and("Calls")
}
pub const fn call_resource_url(account_sid: &str) -> UrlBuffer {
UrlBuffer::new().and("https://").and(REST_API_URL).and("/").and(account_sid).and("/").and(REST_API_CALL_ENDPOINT)
}
pub enum TwilioMethod {
GET,
POST
}
impl TwilioMethod {
fn as_str(&self) -> &'static str {
match self {
TwilioMethod::GET => "GET",
TwilioMethod::POST => "POST",
}
}
}
impl Default for TwilioMethod {
#[inline(always)]
fn default() -> Self {
TwilioMethod::POST
}
}
pub struct TwilioRequest {
buffer: Vec<u8>,
len: usize,
}
impl TwilioRequest {
pub const CONTENT_TYPE: &'static str = "application/x-www-form-urlencode";
pub const fn new() -> Self {
Self {
buffer: Vec::new(),
len: 0
}
}
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
self.buffer
}
#[inline]
pub fn into_string(self) -> String {
unsafe {
String::from_utf8_unchecked(self.buffer)
}
}
#[inline]
pub fn as_form(&self) -> &str {
unsafe {
core::str::from_utf8_unchecked(&self.buffer)
}
}
fn add_pair(&mut self, field: &str, value: &str) -> &mut Self {
self.len += 1;
encoder::push_pair(field, value, &mut self.buffer);
self
}
#[inline]
pub fn account_sid(&mut self, sid: &str) -> &mut Self {
self.add_pair("AccountSid", sid)
}
#[inline]
pub fn from(&mut self, from: &str) -> &mut Self {
self.add_pair("From", from)
}
#[inline]
pub fn to(&mut self, to: &str) -> &mut Self {
self.add_pair("To", to)
}
#[inline]
pub fn body(&mut self, body: &str) -> &mut Self {
debug_assert!(body.len() <= 1_600, "Text body cannot exceed 1600 characters");
self.add_pair("Body", body)
}
#[inline]
pub fn media_url(&mut self, media_url: &str) -> &mut Self {
self.add_pair("MediaUrl", media_url)
}
#[inline]
pub fn post_status_callback(&mut self, url: &str) -> &mut Self {
self.add_pair("StatusCallback", url)
}
#[inline]
pub fn provide_feedback(&mut self, value: bool) -> &mut Self {
match value {
true => self.add_pair("ProvideFeedback", "true"),
false => self.add_pair("ProvideFeedback", "false"),
}
}
#[inline]
pub fn attempt(&mut self, attempt: u32) -> &mut Self {
let mut buf = str_buf::StrBuf::<10>::new();
let _ = write!(buf, "{}", attempt);
self.add_pair("Attempt", buf.as_str())
}
#[inline]
pub fn validity_period(&mut self, attempt: u16) -> &mut Self {
let mut buf = str_buf::StrBuf::<5>::new();
let _ = write!(buf, "{}", attempt);
self.add_pair("ValidityPeriod", buf.as_str())
}
#[inline]
pub fn send_at(&mut self, date: &str) -> &mut Self {
self.add_pair("SendAt", date)
}
#[inline]
pub fn twiml(&mut self, twiml: &str) -> &mut Self {
self.add_pair("Twiml", twiml)
}
#[inline]
pub fn url(&mut self, url: &str) -> &mut Self {
self.add_pair("Url", url)
}
#[inline]
pub fn url_with_method(&mut self, method: TwilioMethod, url: &str) -> &mut Self {
self.add_pair("Method", method.as_str()).add_pair("Url", url)
}
#[inline]
pub fn status_url(&mut self, url: &str) -> &mut Self {
self.add_pair("StatusCallback", url)
}
#[inline]
pub fn status_url_with_method(&mut self, method: TwilioMethod, url: &str) -> &mut Self {
self.add_pair("StatusCallbackMethod", method.as_str()).add_pair("StatusCallback", url)
}
#[inline]
pub fn caller_id(&mut self, id: &str) -> &mut Self {
self.add_pair("CallerId", id)
}
#[inline]
pub fn send_digits(&mut self, digits: &str) -> &mut Self {
debug_assert!(digits.len() <= 32, "SendDigits cannot exceed 32");
self.add_pair("SendDigits", digits)
}
#[inline]
pub fn page_size(&mut self, size: u32) -> &mut Self {
debug_assert_ne!(size, 0);
let mut buf = str_buf::StrBuf::<10>::new();
let _ = write!(buf, "{}", size);
self.add_pair("PageSize", buf.as_str())
}
#[inline]
pub fn start_date(&mut self, date: &str) -> &mut Self {
self.add_pair("StartDate", date)
}
#[inline]
pub fn end_date(&mut self, date: &str) -> &mut Self {
self.add_pair("EndDate", date)
}
#[inline]
pub fn date_sent(&mut self, date: &str) -> &mut Self {
self.add_pair("DateSent", date)
}
}
#[derive(Debug)]
pub enum CallInstruction<'a> {
Twiml(&'a str),
Url(&'a str),
}
#[derive(Debug)]
pub struct Call<'a> {
pub from: &'a str,
pub to: &'a str,
pub instruction: CallInstruction<'a>,
}
impl<'a> Call<'a> {
#[inline]
pub fn request(&self) -> TwilioRequest {
let mut res = TwilioRequest::new();
res.from(self.from).to(self.to);
match self.instruction {
CallInstruction::Twiml(twiml) => res.twiml(twiml),
CallInstruction::Url(url) => res.url(url),
};
res
}
}
impl<'a> fmt::Display for Call<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
encoder::format_pair("From", self.from, fmt)?;
fmt.write_str(encoder::SEP)?;
encoder::format_pair("To", self.to, fmt)?;
fmt.write_str(encoder::SEP)?;
match self.instruction {
CallInstruction::Twiml(twiml) => encoder::format_pair("Twiml", twiml, fmt)?,
CallInstruction::Url(url) => encoder::format_pair("Url", url, fmt)?,
}
Ok(())
}
}
impl<'a> Into<TwilioRequest> for Call<'a> {
#[inline(always)]
fn into(self) -> TwilioRequest {
self.request()
}
}
#[derive(Debug)]
pub struct Sms<'a> {
pub from: &'a str,
pub to: &'a str,
pub body: &'a str,
}
impl<'a> Sms<'a> {
#[inline]
pub fn request(&self) -> TwilioRequest {
let mut res = TwilioRequest::new();
res.from(self.from).to(self.to).body(self.body);
res
}
}
impl<'a> fmt::Display for Sms<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
encoder::format_pair("From", self.from, fmt)?;
fmt.write_str(encoder::SEP)?;
encoder::format_pair("To", self.to, fmt)?;
fmt.write_str(encoder::SEP)?;
encoder::format_pair("Body", self.body, fmt)?;
Ok(())
}
}
impl<'a> Into<TwilioRequest> for Sms<'a> {
#[inline(always)]
fn into(self) -> TwilioRequest {
self.request()
}
}
#[derive(Debug)]
pub struct Mms<'a> {
pub sms: Sms<'a>,
pub media_url: &'a str
}
impl<'a> Mms<'a> {
#[inline]
pub fn request(&self) -> TwilioRequest {
let mut res = self.sms.request();
res.media_url(self.media_url);
res
}
}
impl<'a> fmt::Display for Mms<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.sms.fmt(fmt)?;
fmt.write_str(encoder::SEP)?;
encoder::format_pair("MediaUrl", self.media_url, fmt)?;
Ok(())
}
}
impl<'a> Into<TwilioRequest> for Mms<'a> {
#[inline(always)]
fn into(self) -> TwilioRequest {
self.request()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SmsStatus {
Queued,
Sending,
Sent,
Failed,
Delivered,
Undelivered,
Receiving,
Received,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CallStatus {
Queued,
Ringing,
InProgress,
Canceled,
Completed,
Busy,
NoAnswer,
Failed,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CallDirection {
Inbound,
OutboundApi,
OutboundDial,
TrunkingTerminating,
TrunkingOriginating,
}
#[derive(Debug, Deserialize)]
pub struct SmsResult {
pub from: String,
pub to: String,
pub body: String,
pub sid: String,
pub status: SmsStatus,
pub media_url: Option<String>,
pub price: Option<String>,
pub price_unit: String,
pub date_created: Option<String>,
pub date_sent: Option<String>,
pub date_updated: String,
}
fn deserialize_number_from_any<'de, D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrInt {
String(String),
Number(i64),
}
match StringOrInt::deserialize(deserializer)? {
StringOrInt::String(s) => s.parse::<i64>().map_err(serde::de::Error::custom),
StringOrInt::Number(i) => Ok(i),
}
}
#[derive(Debug, Deserialize)]
pub struct CallResult {
pub from: String,
pub to: String,
pub sid: String,
pub status: CallStatus,
pub caller_name: Option<String>,
pub duration: Option<i64>,
pub price: Option<String>,
pub price_unit: String,
pub date_created: Option<String>,
pub start_time: Option<String>,
pub end_time: Option<String>,
pub direction: Option<CallDirection>,
#[serde(deserialize_with = "deserialize_number_from_any")]
pub queue_time: i64
}
#[derive(Debug, Deserialize)]
pub struct TwilioError {
pub code: usize,
pub message: String,
pub status: usize,
}
impl fmt::Display for TwilioError {
#[inline(always)]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_fmt(format_args!("Twilio API responded with status={}, code={}, message: {}", self.status, self.code, self.message))
}
}
impl std::error::Error for TwilioError {
}