use std::thread;
use std::io::Read;
use std::time::Duration;
use reqwest::{self, StatusCode, Method, Url};
use reqwest::header::{Headers, Authorization, Basic, ContentType, Location};
use serde;
use serde_json::{self, Value};
use serde_url_params;
use std::env;
use dotenv::dotenv;
use error::HelpScoutError;
#[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 code: Option<i32>,
pub error: String,
}
impl Client {
pub fn new(api_key: &str) -> Client {
Client {
retry_count: 3,
retry_wait: 250,
api_url: "https://api.helpscout.net/v1".into(),
api_key: api_key.into(),
reqwest: reqwest::Client::new(),
}
}
#[doc(hidden)]
pub fn example() -> Client {
dotenv().ok();
let api_key: String = env::var("HELPSCOUT_API_KEY").expect("to have HELPSCOUT_API_KEY set");
Client::new(&api_key)
}
pub fn get<T>(&self, path: &str, url_params: T) -> Result<Value, HelpScoutError>
where T: serde::Serialize
{
self.request(Method::Get, self.url(path, url_params)?, None)
}
pub fn post<T>(&self, path: &str, url_params: T, body: Option<String>) -> Result<Value, HelpScoutError>
where T: serde::Serialize
{
self.request(Method::Post, self.url(path, url_params)?, body)
}
pub fn put<T>(&self, path: &str, url_params: T, body: Option<String>) -> Result<Value, HelpScoutError>
where T: serde::Serialize
{
self.request(Method::Put, self.url(path, url_params)?, body)
}
fn url<T>(&self, path: &str, params: T) -> Result<Url, HelpScoutError>
where T: serde::Serialize
{
let mut base = format!("{api_url}/{path}",
api_url = self.api_url,
path = path);
let encoded = serde_url_params::to_string(¶ms)?;
base = format!("{}?{}", base, encoded);
Ok(Url::parse(&base)?)
}
fn request(&self, method: Method, url: Url, request_body: Option<String>) -> Result<Value, HelpScoutError> {
let mut count = self.retry_count;
loop {
let url = url.clone();
debug!("Attempting request - Method: {}. Url: {}", method, url);
let mut headers = Headers::new();
let credentials = Basic {
username: self.api_key.clone(),
password: Some("X".into()),
};
headers.set(Authorization(credentials));
headers.set(ContentType::json());
let mut res = match request_body.clone() {
Some(b) => {
debug!("Request body - {}", b);
self.reqwest.request(method.clone(), url).headers(headers).body(b).send()?
},
None => self.reqwest.request(method.clone(), url).headers(headers).send()?,
};
let mut body = String::new();
res.read_to_string(&mut body)?;
debug!("Response body: {}", body);
debug!("Response status: {}", res.status());
match serde_json::from_str::<Value>(&body) {
Ok(value) => {
let status = serde_json::from_value(value.clone());
match res.status() {
StatusCode::Ok => return Ok(value),
StatusCode::Created => return Ok(value),
StatusCode::BadRequest => return Err(HelpScoutError::BadRequest(status?)),
StatusCode::Unauthorized => return Err(HelpScoutError::UnauthorizedKey(status?)),
StatusCode::Forbidden => return Err(HelpScoutError::Forbidden(status?)),
StatusCode::TooManyRequests => return Err(HelpScoutError::TooManyRequests(status?)),
StatusCode::NotFound => return Err(HelpScoutError::UserNotFound(status?)),
StatusCode::InternalServerError => return Err(HelpScoutError::InternalServerError(status?)),
s => panic!("Status code not covered in HelpScout REST specification: {}", s),
};
},
Err(_) => {
debug!("response headers: {}",res.headers());
if res.headers().has::<Location>() {
return Ok(serde_json::Value::String("Created".into()));
} else if(res.status()==StatusCode::Ok) {
return Ok(serde_json::Value::String("Ok".into()));
}else {
match res.status() {
StatusCode::ServiceUnavailable => {
count -= 1;
if count == 0 {
return Err(HelpScoutError::ServiceUnavailable);
}
else {
thread::sleep(Duration::from_millis(self.retry_wait.into()));
continue;
}
},
_ => return Err(HelpScoutError::InvalidServerResponse),
}
}
},
};
}
}
}