use std::thread;
use std::io::Read;
use std::time::Duration;
use reqwest::{self, StatusCode, Method, Url};
use serde_json::{self, Value};
use error::AuthyError;
#[derive(Debug)]
pub struct Client {
pub retry_count: u8,
pub retry_wait: u16,
api_url: String,
api_key: String,
reqwest: reqwest::Client,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Status {
pub success: bool,
pub message: String,
pub error_code: Option<String>,
}
impl Client {
pub fn new(api_url: &str, api_key: &str) -> Client {
Client {
retry_count: 3,
retry_wait: 250,
api_url: api_url.into(),
api_key: api_key.into(),
reqwest: reqwest::Client::new(),
}
}
pub fn get(&self, prefix: &str, path: &str, url_params: Option<Vec<(String, String)>>) -> Result<(Status, Value), AuthyError> {
self.request(Method::GET, self.url(prefix, path, url_params), None)
}
pub fn post(&self, prefix: &str, path: &str, url_params: Option<Vec<(String, String)>>, post_params: Option<Vec<(String, String)>>) -> Result<(Status, Value), AuthyError> {
self.request(Method::POST, self.url(prefix, path, url_params), post_params)
}
fn url(&self, prefix: &str, path: &str, params: Option<Vec<(String, String)>>) -> Url {
let base = format!("{api_url}/{prefix}/json/{path}",
api_url = self.api_url,
prefix = prefix,
path = path);
match params {
Some(params) => Url::parse_with_params(&base, params),
None => Url::parse(&base),
}.expect("Url to be valid")
}
fn request(&self, method: Method, url: Url, params: Option<Vec<(String, String)>>) -> Result<(Status, Value), AuthyError> {
let mut count = self.retry_count;
loop {
let url = url.clone();
let mut res = match params.clone() {
Some(p) => self.reqwest.request(method.clone(), url).header("X-Authy-API-Key", self.api_key.clone()).form(&p).send()?,
None => self.reqwest.request(method.clone(), url).header("X-Authy-API-Key", self.api_key.clone()).send()?,
};
let mut body = String::new();
res.read_to_string(&mut body)?;
match serde_json::from_str::<Value>(&body) {
Ok(mut value) => {
value["success"] = match value.clone()["success"] {
Value::Bool(v) => Value::Bool(v),
Value::String(ref v) => match v.as_ref() {
"true" => Value::Bool(true),
_ => Value::Bool(false),
},
_ => Value::Bool(false),
};
let status: Status = serde_json::from_value(value.clone())?;
match res.status() {
StatusCode::OK => return Ok((status, value)),
StatusCode::BAD_REQUEST => return Err(AuthyError::BadRequest(status)),
StatusCode::UNAUTHORIZED => return Err(AuthyError::UnauthorizedKey(status)),
StatusCode::FORBIDDEN => return Err(AuthyError::Forbidden(status)),
StatusCode::TOO_MANY_REQUESTS => return Err(AuthyError::TooManyRequests(status)),
StatusCode::NOT_FOUND => return Err(AuthyError::UserNotFound(status)),
StatusCode::INTERNAL_SERVER_ERROR => return Err(AuthyError::InternalServerError(status)),
s => return Err(AuthyError::UnknownServerResponse(format!("Status code not covered in authy REST specification: {}", s))),
};
},
Err(_) => {
match res.status() {
StatusCode::SERVICE_UNAVAILABLE => {
count -= 1;
if count == 0 {
return Err(AuthyError::ServiceUnavailable);
}
else {
thread::sleep(Duration::from_millis(self.retry_wait.into()));
continue;
}
},
_ => return Err(AuthyError::InvalidServerResponse),
}
},
};
}
}
}