use std::time::Duration;
use reqwest::StatusCode;
use reqwest::blocking::{Client as HttpClient, Response};
use serde::de::DeserializeOwned;
use crate::{errors::SignalWireError, types::*};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone)]
pub struct BlockingClient {
space_name: String,
project_id: String,
api_key: String,
http: HttpClient,
}
impl BlockingClient {
pub fn new(space_name: impl Into<String>, project_id: impl Into<String>, api_key: impl Into<String>) -> Result<Self, SignalWireError> {
let http = HttpClient::builder().timeout(DEFAULT_TIMEOUT).build()?;
Ok(Self {
space_name: space_name.into(),
project_id: project_id.into(),
api_key: api_key.into(),
http,
})
}
pub fn with_http_client(mut self, http: HttpClient) -> Self {
self.http = http;
self
}
pub fn project_id(&self) -> &str {
&self.project_id
}
pub fn space_name(&self) -> &str {
&self.space_name
}
fn url(&self, path: &str) -> String {
format!("https://{}.signalwire.com{}", self.space_name, path)
}
fn auth(&self, b: reqwest::blocking::RequestBuilder) -> reqwest::blocking::RequestBuilder {
b.basic_auth(&self.project_id, Some(&self.api_key))
}
fn handle<T: DeserializeOwned>(resp: Response) -> Result<T, SignalWireError> {
let status = resp.status();
if status == StatusCode::TOO_MANY_REQUESTS {
let retry = resp
.headers()
.get(reqwest::header::RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.map(Duration::from_secs);
return Err(SignalWireError::RateLimited(retry));
}
let body = resp.text()?;
match status {
StatusCode::UNAUTHORIZED => Err(SignalWireError::Unauthorized),
StatusCode::NOT_FOUND => Err(SignalWireError::NotFound(body)),
s if s.is_client_error() || s.is_server_error() => Err(SignalWireError::Api { code: s.as_u16(), body }),
_ => serde_json::from_str(&body).map_err(|source| SignalWireError::Decode { source, body }),
}
}
fn handle_no_content(resp: Response) -> Result<(), SignalWireError> {
let status = resp.status();
if status == StatusCode::TOO_MANY_REQUESTS {
let retry = resp
.headers()
.get(reqwest::header::RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.map(Duration::from_secs);
return Err(SignalWireError::RateLimited(retry));
}
match status {
StatusCode::UNAUTHORIZED => Err(SignalWireError::Unauthorized),
StatusCode::NOT_FOUND => Err(SignalWireError::NotFound(resp.text()?)),
s if s.is_client_error() || s.is_server_error() => Err(SignalWireError::Api { code: s.as_u16(), body: resp.text()? }),
_ => Ok(()),
}
}
pub fn get_jwt(&self) -> Result<JwtResponse, SignalWireError> {
let resp = self.auth(self.http.post(self.url("/api/relay/rest/jwt"))).header(reqwest::header::CONTENT_LENGTH, "0").send()?;
Self::handle(resp)
}
pub fn get_phone_numbers_available(&self, iso_country: &str, query: &[(String, String)]) -> Result<PhoneNumbersAvailableResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}/AvailablePhoneNumbers/{}/Local", self.project_id, iso_country);
let resp = self.auth(self.http.get(self.url(&path))).query(query).send()?;
Self::handle(resp)
}
pub fn get_phone_numbers_owned(&self, query: &[(String, String)]) -> Result<PhoneNumbersOwnedResponse, SignalWireError> {
let resp = self.auth(self.http.get(self.url("/api/relay/rest/phone_numbers"))).query(query).send()?;
Self::handle(resp)
}
pub fn buy_phone_number(&self, phone_number: &str) -> Result<BuyPhoneNumberResponse, SignalWireError> {
let body = BuyPhoneNumberRequest { number: phone_number.to_string() };
let resp = self.auth(self.http.post(self.url("/api/relay/rest/phone_numbers"))).json(&body).send()?;
Self::handle(resp)
}
pub fn update_phone_number(&self, id: &str, request: &UpdatePhoneNumberRequest) -> Result<BuyPhoneNumberResponse, SignalWireError> {
let path = format!("/api/relay/rest/phone_numbers/{}", id);
let resp = self.auth(self.http.put(self.url(&path))).json(request).send()?;
Self::handle(resp)
}
pub fn send_sms(&self, message: &SmsMessage) -> Result<SmsResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}/Messages", self.project_id);
let form = [("From", &message.from), ("To", &message.to), ("Body", &message.body)];
let resp = self.auth(self.http.post(self.url(&path))).form(&form).send()?;
Self::handle(resp)
}
pub fn get_message_status(&self, message_sid: &str) -> Result<SmsResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}/Messages/{}", self.project_id, message_sid);
let resp = self.auth(self.http.get(self.url(&path))).send()?;
Self::handle(resp)
}
pub fn list_subprojects(&self, query: &[(String, String)]) -> Result<SubprojectsListResponse, SignalWireError> {
let resp = self.auth(self.http.get(self.url("/api/laml/2010-04-01/Accounts"))).query(query).send()?;
Self::handle(resp)
}
pub fn get_subproject(&self, subproject_sid: &str) -> Result<SubprojectResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}", subproject_sid);
let resp = self.auth(self.http.get(self.url(&path))).send()?;
Self::handle(resp)
}
pub fn create_subproject(&self, friendly_name: &str) -> Result<SubprojectResponse, SignalWireError> {
let form = [("FriendlyName", friendly_name)];
let resp = self.auth(self.http.post(self.url("/api/laml/2010-04-01/Accounts"))).form(&form).send()?;
Self::handle(resp)
}
pub fn update_subproject(&self, subproject_sid: &str, friendly_name: &str, status: Option<&str>) -> Result<SubprojectResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}", subproject_sid);
let mut form = vec![("FriendlyName", friendly_name)];
if let Some(s) = status {
form.push(("Status", s));
}
let resp = self.auth(self.http.post(self.url(&path))).form(&form).send()?;
Self::handle(resp)
}
pub fn delete_subproject(&self, subproject_sid: &str) -> Result<(), SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}", subproject_sid);
let resp = self.auth(self.http.delete(self.url(&path))).send()?;
Self::handle_no_content(resp)
}
pub fn get_subproject_phone_numbers(&self, subproject_sid: &str, query: &[(String, String)]) -> Result<SubprojectPhoneNumbersResponse, SignalWireError> {
let path = format!("/api/laml/2010-04-01/Accounts/{}/IncomingPhoneNumbers", subproject_sid);
let resp = self.auth(self.http.get(self.url(&path))).query(query).send()?;
Self::handle(resp)
}
pub fn lookup_phone_number(&self, phone_number: &str) -> Result<PhoneLookupResponse, SignalWireError> {
self.lookup(phone_number, LookupKind::Basic)
}
pub fn lookup_phone_number_with_carrier(&self, phone_number: &str) -> Result<PhoneLookupResponse, SignalWireError> {
self.lookup(phone_number, LookupKind::Carrier)
}
pub fn lookup_phone_number_with_caller_name(&self, phone_number: &str) -> Result<PhoneLookupResponse, SignalWireError> {
self.lookup(phone_number, LookupKind::CallerName)
}
pub fn lookup(&self, phone_number: &str, kind: LookupKind) -> Result<PhoneLookupResponse, SignalWireError> {
let path = format!("/api/relay/rest/lookup/phone_number/{}", phone_number);
let mut req = self.auth(self.http.get(self.url(&path)));
if let Some((k, v)) = kind.as_query() {
req = req.query(&[(k, v)]);
}
Self::handle(req.send()?)
}
}