1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::thread;
use std::io::Read;
use std::time::Duration;
use reqwest::{self, StatusCode, Method, Url};
use reqwest::header::Headers;
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().expect("A reqwest client"),
}
}
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 headers = Headers::new();
headers.set_raw("X-Authy-API-Key", vec![self.api_key.clone().into()]);
let mut res = match params.clone() {
Some(p) => self.reqwest.request(method.clone(), url).headers(headers).form(&p).send()?,
None => self.reqwest.request(method.clone(), url).headers(headers).send()?,
};
let mut body = String::new();
res.read_to_string(&mut body)?;
println!("{}", body);
match serde_json::from_str::<Value>(&body) {
Ok(value) => {
let status: Status = serde_json::from_str(&body)?;
match res.status() {
&StatusCode::Ok => return Ok((status, value)),
&StatusCode::BadRequest => return Err(AuthyError::BadRequest(status)),
&StatusCode::Unauthorized => return Err(AuthyError::UnauthorizedKey(status)),
&StatusCode::Forbidden => return Err(AuthyError::Forbidden(status)),
&StatusCode::TooManyRequests => return Err(AuthyError::TooManyRequests(status)),
&StatusCode::NotFound => return Err(AuthyError::UserNotFound(status)),
&StatusCode::InternalServerError => return Err(AuthyError::InternalServerError(status)),
s => panic!("Status code not covered in authy REST specification: {}", s),
};
},
Err(_) => {
match res.status() {
&StatusCode::ServiceUnavailable => {
count -= 1;
if count == 0 {
return Err(AuthyError::ServiceUnavailable);
}
else {
thread::sleep(Duration::from_millis(self.retry_wait.into()));
continue;
}
},
_ => return Err(AuthyError::InvalidServerResponse),
}
},
};
}
}
}