use std::io::Read;
use serde_json;
use hyper::{Error as HyperError, Url};
use hyper::client::{Client as HyperClient, Body, Response};
use hyper::header::{Accept,
Authorization,
Basic,
Bearer,
Headers,
Location,
qitem,
UserAgent};
use hyper::method::Method;
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper::status::StatusCode;
use error;
use auth::auth::Auth;
static DEFAULT_API_URL: &'static str = "https://api.github.com";
#[derive(Debug)]
pub struct Client {
pub http_client: HyperClient,
pub api_url: String,
pub user_agent: String,
pub authentication: Auth,
}
impl Client {
pub fn new(user_agent: &str, auth: Auth) -> Client {
Client::with_url(DEFAULT_API_URL, user_agent, auth)
}
pub fn with_url(url: &str, user_agent: &str, auth: Auth) -> Client {
Client {
http_client: HyperClient::new(),
api_url: url.to_string(),
user_agent: user_agent.to_string(),
authentication: auth
}
}
pub fn get_default_headers(&self) -> Headers {
let mut headers = Headers::new();
headers.set(Accept(vec![qitem(Mime(TopLevel::Application, SubLevel::Ext("vnd.github.v3+json".to_string()), vec![]))])); headers.set(UserAgent(self.user_agent[..].to_owned()));
return headers;
}
fn get_redirect(source_url: &String, response: &Response) -> Option<String> {
match response.status {
StatusCode::MovedPermanently |
StatusCode::TemporaryRedirect |
StatusCode::PermanentRedirect =>
response.headers.get().map(|&Location(ref loc)| {
if response.status == StatusCode::PermanentRedirect {
info!("{} as been permanently redirected, please notify
the rustyhub developer that it has been moved to {}", source_url, loc);
}
loc.clone()
}),
_ => None
}
}
fn get_error(response: &mut Response) -> Option<error::Error> {
match response.status {
StatusCode::BadRequest |
StatusCode::UnprocessableEntity |
StatusCode::NotFound => {
let body_data = match Client::response_to_string(response) {
Ok(data) => data,
Err(err) => return Some(err)
};
match serde_json::from_str(&body_data) {
Ok(err_response) => return Some(error::Error::Github(err_response)),
Err(err) => return Some(error::Error::Parsing(err))
}
},
_ => None
}
}
pub fn response_to_string(response: &mut Response) -> Result<String, error::Error> {
let mut body_data = String::new();
response.read_to_string(&mut body_data)
.map(|_| body_data)
.map_err(error::Error::STDIO)
}
fn set_request_authentication(auth: &Auth, url: String, headers: &mut Headers) -> Result<String, error::Error> {
match auth {
&Auth::NoAuth => Ok(url),
&Auth::OAuth2Token(ref token) => {
headers.set(Authorization(Bearer{token: token[..].to_owned()}));
Ok(url)
},
&Auth::OAuth2KeySecret(ref client_id, ref client_secret) => {
let mut url_parsed = match Url::parse(&url[..]) {
Ok(url) => url,
Err(err) => return Err(error::Error::HTTP(HyperError::Uri(err)))
};
{
let mut query_pairs = url_parsed.query_pairs_mut();
query_pairs.append_pair("client_id", &client_id[..]);
query_pairs.append_pair("client_secret", &client_secret[..]);
}
Ok(url_parsed.into_string())
},
&Auth::Basic(ref username, ref password, ref otp_token) => {
headers.set(Authorization(Basic{username: username.clone(), password: Some(password.clone())}));
if let &Some(ref token) = otp_token {
let mut url_parsed = match Url::parse(&url[..]) {
Ok(url) => url,
Err(err) => return Err(error::Error::HTTP(HyperError::Uri(err)))
};
{
let mut query_pairs = url_parsed.query_pairs_mut();
query_pairs.append_pair("X-GitHub-OTP", &token[..]);
}
return Ok(url_parsed.into_string());
}
return Ok(url);
}
}
}
fn make_request(&self, method: Method, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
let mut request_header = headers.unwrap_or_else(|| self.get_default_headers());
let mut url = format!("{}{}", self.api_url, endpoint);
url = try!(Client::set_request_authentication(&self.authentication, url, &mut request_header));
let request_header_copy = request_header.clone();
let mut response = try!(self.http_client.request(method, &url[..])
.headers(request_header)
.send()
.map_err(error::Error::HTTP));
while let Some(loc) = Client::get_redirect(&url, &mut response) {
response = try!(self.http_client.get(&loc[..])
.headers(request_header_copy.clone())
.send()
.map_err(error::Error::HTTP));
}
if let Some(err) = Client::get_error(&mut response) {
return Err(err)
}
Ok(response)
}
fn make_request_body(&self,
method: Method,
endpoint: String,
headers: Option<Headers>,
body: String) -> Result<Response, error::Error> {
let mut request_header = headers.unwrap_or_else(|| self.get_default_headers());
let mut url = format!("{}{}", self.api_url, endpoint);
url = try!(Client::set_request_authentication(&self.authentication, url, &mut request_header));
let request_header_copy = request_header.clone();
let body_len = body.clone().len();
let body_copy = body.clone();
let mut response = try!(self.http_client.request(method.clone(), &url[..])
.headers(request_header)
.body(Body::BufBody(&body.into_bytes()[..], body_len))
.send()
.map_err(error::Error::HTTP));
while let Some(loc) = Client::get_redirect(&url, &mut response) {
response = try!(self.http_client.request(method.clone(), &loc[..])
.headers(request_header_copy.clone())
.body(Body::BufBody(&body_copy.clone().into_bytes()[..], body_len))
.send()
.map_err(error::Error::HTTP));
}
if let Some(err) = Client::get_error(&mut response) {
return Err(err)
}
Ok(response)
}
pub fn get(&self, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
self.make_request(Method::Get, endpoint, headers)
}
pub fn post(&self, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
self.make_request(Method::Post, endpoint, headers)
}
pub fn put(&self, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
self.make_request(Method::Put, endpoint, headers)
}
pub fn patch(&self, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
self.make_request(Method::Patch, endpoint, headers)
}
pub fn delete(&self, endpoint: String, headers: Option<Headers>) -> Result<Response, error::Error> {
self.make_request(Method::Delete, endpoint, headers)
}
pub fn get_body(&self, endpoint: String, headers: Option<Headers>, body: String) -> Result<Response, error::Error> {
self.make_request_body(Method::Get, endpoint, headers, body)
}
pub fn post_body(&self, endpoint: String, headers: Option<Headers>, body: String) -> Result<Response, error::Error> {
self.make_request_body(Method::Post, endpoint, headers, body)
}
pub fn put_body(&self, endpoint: String, headers: Option<Headers>, body: String) -> Result<Response, error::Error> {
self.make_request_body(Method::Put, endpoint, headers, body)
}
pub fn patch_body(&self, endpoint: String, headers: Option<Headers>, body: String) -> Result<Response, error::Error> {
self.make_request_body(Method::Patch, endpoint, headers, body)
}
pub fn delete_body(&self, endpoint: String, headers: Option<Headers>, body: String) -> Result<Response, error::Error> {
self.make_request_body(Method::Delete, endpoint, headers, body)
}
}
#[cfg(test)]
mod client_test {
#[test]
fn client_new() {
let client = super::Client::new("rustyhub-test/0.0.0", Some("test-token".to_string()));
assert!(client.api_url == String::from("https://api.github.com"));
assert!(client.user_agent == String::from("rustyhub-test/0.0.0"));
assert!(client.auth_token == Some(String::from("test-token")));
}
#[test]
fn client_with_url() {
let client = super::Client::with_url("https://api.github.com/", "rustyhub-test/0.0.0", Some("test-token".to_string()));
assert!(client.api_url == String::from("https://api.github.com/"));
assert!(client.user_agent == String::from("rustyhub-test/0.0.0"));
assert!(client.auth_token == Some(String::from("test-token")));
}
}