use std::time::Duration;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs::File;
use std::io::stdin;
use ureq::{Agent, AgentBuilder};
pub use ureq::{Response, Error};
use super::config::Config;
use super::json::JsonMap;
pub fn build_request_url(cfg: &Config, route: &str) -> String {
let mut url = cfg.server.get_api_url();
if !url.ends_with('/') { url.push('/'); }
url.push_str("v1/");
url.push_str(if route.starts_with('/') { &route[1..] } else { route });
url
}
pub fn init_agent(cfg: &Config) -> Agent {
AgentBuilder::new()
.timeout_connect(Duration::from_secs_f32(cfg.client.get_timeout()))
.build()
}
#[derive(Clone)]
pub enum ClientRequestMethod {
GET,
POST,
PUT,
DELETE,
}
pub type ClientRequestHeaders = HashMap<String, String>;
#[derive(Clone)]
pub enum ClientRequestBody {
None,
Json(JsonMap),
FileContent(PathBuf),
StdIn,
}
#[derive(Clone)]
pub struct ClientRequest {
pub method: ClientRequestMethod,
pub route: String,
pub headers: ClientRequestHeaders,
pub body: ClientRequestBody,
}
impl ClientRequest {
pub fn new(method: ClientRequestMethod, route: String) -> ClientRequest {
ClientRequest {
method: method,
route: route,
headers: HashMap::new(),
body: ClientRequestBody::None,
}
}
pub fn set_header<K, V>(&mut self, name: K, value: V)
where K : Into<String>, V : Into<String>
{
self.headers.insert(name.into(), value.into());
}
pub fn unset_header<K>(&mut self, name: K)
where K: Into<String>
{
self.headers.remove(&name.into());
}
pub fn set_json_body(&mut self, data: JsonMap) {
self.body = ClientRequestBody::Json(data);
}
pub fn set_file_body(&mut self, filename: PathBuf) {
self.body = ClientRequestBody::FileContent(filename);
}
pub fn set_stdin_body(&mut self) {
self.body = ClientRequestBody::StdIn;
}
pub fn get(route: String) -> ClientRequest { Self::new(ClientRequestMethod::GET, route) }
pub fn post(route: String) -> ClientRequest { Self::new(ClientRequestMethod::POST, route) }
pub fn put(route: String) -> ClientRequest { Self::new(ClientRequestMethod::PUT, route) }
pub fn delete(route: String) -> ClientRequest { Self::new(ClientRequestMethod::DELETE, route) }
}
pub fn send_request(cfg: &Config, client_request: &ClientRequest) -> Result<Response, Error> {
let request_url = build_request_url(cfg, &client_request.route);
let agent = init_agent(cfg);
let mut req = match client_request.method {
ClientRequestMethod::GET => agent.get(&request_url),
ClientRequestMethod::POST => agent.post(&request_url),
ClientRequestMethod::PUT => agent.put(&request_url),
ClientRequestMethod::DELETE => agent.delete(&request_url),
};
for (name, value) in &client_request.headers {
req = req.set(name, value);
}
match &client_request.body {
ClientRequestBody::None => req.call(),
ClientRequestBody::Json(data) => req.send_json(data),
ClientRequestBody::FileContent(filename) => {
let file_in = File::open(filename).expect("Could not open input file");
req.send(file_in)
},
ClientRequestBody::StdIn => req.send(stdin()),
}
}
pub fn send_request_with_retry<FnAttempt, FnFailure, FnRetry>(
cfg: &Config, client_request: &ClientRequest,
mut attempt_cb: Option<FnAttempt>,
mut failure_cb: Option<FnFailure>,
mut retry_cb: Option<FnRetry>)
-> Option<Response>
where
FnAttempt: FnMut(u32),
FnFailure: FnMut(u32, Error),
FnRetry: FnMut(u32)
{
for attempt in 1..=(cfg.client.get_retry() + 1) {
if let Some(attempt_cb) = &mut attempt_cb { attempt_cb(attempt); }
let result = send_request(cfg, &client_request);
match result {
Ok(response) => {
return Some(response);
},
Err(error) => {
if let Some(failure_cb) = &mut failure_cb { failure_cb(attempt, error); }
if matches!(client_request.body, ClientRequestBody::StdIn) {
break;
} else {
if attempt < cfg.client.get_retry() + 1 {
if let Some(retry_cb) = &mut retry_cb { retry_cb(attempt); }
}
}
}
}
}
None
}