#![deny(deprecated, drop_with_repr_extern, improper_ctypes,
non_shorthand_field_patterns, overflowing_literals, plugin_as_library,
private_no_mangle_fns, private_no_mangle_statics, stable_features, unconditional_recursion,
unknown_lints, unused, unused_allocation, unused_attributes,
unused_comparisons, unused_features, unused_parens, while_true)]
#![warn(missing_docs, trivial_casts, trivial_numeric_casts, unused, unused_extern_crates,
unused_import_braces, unused_qualifications, unused_results, variant_size_differences)]
extern crate hyper;
extern crate chrono;
extern crate rustc_serialize;
extern crate fractal_dto as dto;
extern crate fractal_utils as utils;
use std::time::Duration;
use std::io::Read;
use hyper::Client as HyperClient;
use hyper::header::{Headers, Authorization, Basic, Accept, qitem};
use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
use hyper::status::StatusCode;
use rustc_serialize::base64::FromBase64;
use rustc_serialize::json;
use dto::{FromDTO, UserDTO, ScopeDTO as Scope, GenerateTransactionDTO as GenerateTransaction,
LoginDTO as Login, RegisterDTO as Register, UpdateUserDTO as UpdateUser,
FractalConnectionDTO as ConnectionInvitation,
ConfirmPendingConnectionDTO as ConfirmConnection, ResetPasswordDTO as ResetPassword,
ResponseDTO};
use chrono::NaiveDate;
pub mod error;
pub mod types;
pub mod oauth;
use error::{Result, Error};
use types::{User, Transaction};
use oauth::AccessToken;
use utils::{WalletAddress, Amount, Address, Relationship};
pub const SECRET_LEN: usize = 20;
const FRACTAL_SERVER: &'static str = "https://api.fractal.global/";
const FRACTAL_DEV_SERVER: &'static str = "https://dev.fractal.global/";
pub struct ClientV1 {
client: HyperClient,
url: String,
}
impl ClientV1 {
pub fn new() -> ClientV1 {
ClientV1 {
client: hyper::Client::new(),
url: format!("{}v1/", FRACTAL_SERVER),
}
}
pub fn new_with_url<S: AsRef<str>>(url: S) -> ClientV1 {
ClientV1 {
client: hyper::Client::new(),
url: format!("{}v1/", url.as_ref()),
}
}
pub fn new_dev() -> ClientV1 {
ClientV1 {
client: hyper::Client::new(),
url: format!("{}v1/", FRACTAL_DEV_SERVER),
}
}
pub fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.client.set_read_timeout(timeout);
}
pub fn set_write_timeout(&mut self, timeout: Option<Duration>) {
self.client.set_write_timeout(timeout);
}
pub fn token<S: AsRef<str>>(&self, app_id: S, secret: S) -> Result<AccessToken> {
match secret.as_ref().from_base64() {
Ok(b) => {
if b.len() == SECRET_LEN {
let mut headers = Headers::new();
headers.set(Accept(vec![
qitem(Mime(TopLevel::Application, SubLevel::Json,
vec![(Attr::Charset, Value::Utf8)])),
]));
headers.set(Authorization(Basic {
username: String::from(app_id.as_ref()),
password: Some(String::from(secret.as_ref())),
}));
let mut response = try!(self.client
.post(&format!("{}token", self.url))
.body("grant_type=client_credentials")
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
Ok(try!(AccessToken::from_dto(try!(json::decode(&response_str)))))
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::InvalidSecret)
}
}
Err(_) => Err(Error::InvalidSecret),
}
}
pub fn get_all_users(&self, access_token: &AccessToken) -> Result<Vec<User>> {
if access_token.scopes().any(|s| s == &Scope::Admin) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let mut response = try!(self.client
.get(&format!("{}all_users", self.url))
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
let dto_users: Vec<UserDTO> = try!(json::decode(&response_str));
Ok(dto_users.into_iter()
.filter_map(|u| match User::from_dto(u) {
Ok(u) => Some(u),
Err(_) => None,
})
.collect())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn get_all_transactions(&self,
access_token: &AccessToken,
first_transaction: u64)
-> Result<Vec<Transaction>> {
if access_token.scopes().any(|s| s == &Scope::Admin) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let mut response = try!(self.client
.get(&format!("{}all_transactions/{}", self.url, first_transaction))
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
let transactions: Vec<Transaction> = try!(json::decode(&response_str));
Ok(transactions.into_iter().collect())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn generate_transaction(&self,
access_token: &AccessToken,
receiver_wallet: WalletAddress,
receiver_id: u64,
amount: Amount)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(_) => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: GenerateTransaction = GenerateTransaction {
origin_id: access_token.scopes().fold(0, |acc, s| match s {
&Scope::User(id) => id,
_ => acc,
}),
destination_address: receiver_wallet,
destination_id: receiver_id,
amount: amount,
};
let mut response = try!(self.client
.post(&format!("{}generate_transaction", self.url))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn login<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_email: S,
password: S,
remember_me: bool)
-> Result<AccessToken> {
if access_token.scopes().any(|s| s == &Scope::Public) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: Login = Login {
user_email: String::from(user_email.as_ref()),
password: String::from(password.as_ref()),
remember_me: remember_me,
};
let mut response = try!(self.client
.post(&format!("{}login", self.url))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
Ok(try!(AccessToken::from_dto(try!(json::decode(&response_str)))))
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn start_reset_password<S: AsRef<str>>(&self,
access_token: &AccessToken,
username: S,
email: S)
-> Result<()> {
if access_token.scopes().any(|s| s == &Scope::Public) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: ResetPassword = ResetPassword {
username: String::from(username.as_ref()),
email: String::from(email.as_ref()),
};
let response = try!(self.client
.post(&format!("{}start_reset_password", self.url))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn resend_email_confirmation(&self, access_token: &AccessToken) -> Result<()> {
let mut user_id = None;
for scope in access_token.scopes() {
match scope {
&Scope::User(id) => user_id = Some(id),
_ => {}
}
}
if user_id.is_some() && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let response = try!(self.client
.get(&format!("{}resend_email_confirmation", self.url))
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn register<S: AsRef<str>>(&self,
access_token: &AccessToken,
username: S,
password: S,
email: S)
-> Result<()> {
if access_token.scopes().any(|s| s == &Scope::Public) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let register: Register = Register {
username: String::from(username.as_ref()),
password: String::from(password.as_ref()),
email: String::from(email.as_ref()),
};
let mut response = try!(self.client
.post(&format!("{}register", self.url))
.body(&json::encode(®ister).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_username<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
username: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: Some(String::from(username.as_ref())),
new_email: None,
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: None,
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_phone<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
phone: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: Some(String::from(phone.as_ref())),
new_birthday: None,
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_birthday<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
birthday: NaiveDate)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: Some(birthday),
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_name<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
first: S,
last: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: Some(String::from(first.as_ref())),
new_last: Some(String::from(last.as_ref())),
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: None,
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_email<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
email: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: Some(String::from(email.as_ref())),
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: None,
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_image<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
image: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: None,
new_image: Some(String::from(image.as_ref())),
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_address<S: AsRef<str>>(&self,
access_token: &AccessToken,
user_id: u64,
password: Option<S>,
address: Address)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(u_id) => u_id == user_id,
&Scope::Admin => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: None,
new_last: None,
old_password: match password {
Some(pass) => Some(String::from(pass.as_ref())),
None => None,
},
new_password: None,
new_phone: None,
new_birthday: None,
new_image: None,
new_address: Some(address),
};
let response = try!(self.client
.post(&format!("{}update_user/{}", self.url, user_id))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn set_password<S: AsRef<str>>(&self,
access_token: &AccessToken,
old_password: S,
new_password: S)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(_) => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: UpdateUser = UpdateUser {
new_username: None,
new_email: None,
new_first: None,
new_last: None,
old_password: Some(String::from(old_password.as_ref())),
new_password: Some(String::from(new_password.as_ref())),
new_phone: None,
new_birthday: None,
new_image: None,
new_address: None,
};
let response = try!(self.client
.post(&format!("{}update_user/{}",
self.url,
access_token.scopes().fold(0, |acc, s| match s {
&Scope::User(id) => id,
_ => acc,
})))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => {
Ok(())
}
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn invite_user_to_connect(&self,
access_token: &AccessToken,
user: u64,
relation: Relationship)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(_) => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: ConnectionInvitation = ConnectionInvitation {
origin_id: access_token.scopes().fold(0, |acc, s| match s {
&Scope::User(id) => id,
_ => acc,
}),
destination_id: user,
relationship: relation,
};
let mut response = try!(self.client
.post(&format!("{}create_pending_connection",
self.url,
))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Unauthorized => Err(Error::Unauthorized),
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn confirm_connection(&self,
access_token: &AccessToken,
connection_id: u64,
user: u64)
-> Result<()> {
if access_token.scopes().any(|s| match s {
&Scope::User(_) => true,
_ => false,
}) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let dto: ConfirmConnection = ConfirmConnection {
origin: user,
destination: access_token.scopes().fold(0, |acc, s| match s {
&Scope::User(id) => id,
_ => acc,
}),
id: connection_id,
};
let mut response = try!(self.client
.post(&format!("{}confirm_pending_connection",
self.url,
))
.body(&json::encode(&dto).unwrap())
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Unauthorized => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
Err(Error::Unauthorized)
}
StatusCode::Accepted => {
let mut response_str = String::new();
try!(response.read_to_string(&mut response_str));
match json::decode::<ResponseDTO>(&response_str) {
Ok(r) => Err(Error::ClientError(r)),
Err(e) => Err(e.into()),
}
}
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
pub fn confirm_email(&self, email_key: String) -> Result<()> {
let response = try!(self.client
.get(&format!("{}confirm_email/{}", self.url, email_key))
.send());
match response.status {
StatusCode::Ok => Ok(()),
_ => Err(Error::ServerError),
}
}
pub fn delete_user(&self, access_token: &AccessToken, user_id: u64) -> Result<()> {
if access_token.scopes().any(|s| s == &Scope::Admin) && !access_token.has_expired() {
let mut headers = Headers::new();
headers.set(Authorization(access_token.get_token()));
let response = try!(self.client
.delete(&format!("{}user/{}", self.url, user_id))
.headers(headers)
.send());
match response.status {
StatusCode::Ok => Ok(()),
StatusCode::Unauthorized => Err(Error::Unauthorized),
_ => Err(Error::ServerError),
}
} else {
Err(Error::Unauthorized)
}
}
}