use reqwest::Response;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::de::DeserializeOwned;
use std::convert::{TryInto};
use std::collections::{HashMap};
use crate::{Cursor, Credentials};
use crate::AppHash;
pub struct WebwareClientBuilder {
host: String,
port: u16,
webservice_path: String,
vendor_hash: String,
app_hash: String,
secret: String,
revision: u32,
allow_unsafe_certs: bool,
timeout: u64,
result_max_lines: u32,
service_pass: Option<String>,
app_id: Option<String>,
}
impl Default for WebwareClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl WebwareClientBuilder {
pub fn new() -> Self {
WebwareClientBuilder {
host: String::new(),
port: 443,
webservice_path: "/WWSVC/".to_string(),
vendor_hash: String::new(),
app_hash: String::new(),
secret: String::new(),
revision: 0,
allow_unsafe_certs: false,
timeout: 60,
result_max_lines: 1000,
service_pass: None,
app_id: None,
}
}
pub fn host(mut self, host: &str) -> Self {
self.host = host.to_string();
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn webservice_path(mut self, path: &str) -> Self {
self.webservice_path = path.to_string();
self
}
pub fn vendor_hash(mut self, hash: &str) -> Self {
self.vendor_hash = hash.to_string();
self
}
pub fn app_hash(mut self, hash: &str) -> Self {
self.app_hash = hash.to_string();
self
}
pub fn secret(mut self, secret: &str) -> Self {
self.secret = secret.to_string();
self
}
pub fn revision(mut self, revision: u32) -> Self {
self.revision = revision;
self
}
pub fn allow_unsafe_certs(mut self, allow: bool) -> Self {
self.allow_unsafe_certs = allow;
self
}
pub fn timeout(mut self, timeout: u64) -> Self {
self.timeout = timeout;
self
}
pub fn result_max_lines(mut self, lines: u32) -> Self {
self.result_max_lines = lines;
self
}
pub fn service_pass(mut self, pass: &str) -> Self {
self.service_pass = Some(pass.to_string());
self
}
pub fn app_id(mut self, id: &str) -> Self {
self.app_id = Some(id.to_string());
self
}
pub fn credentials(mut self, credentials: Credentials) -> Self {
self.app_id = Some(credentials.app_id);
self.service_pass = Some(credentials.service_pass);
self
}
pub fn build(self) -> WebwareClient {
WebwareClient {
webware_url: format!("https://{}:{}{}", self.host, self.port, self.webservice_path),
vendor_hash: self.vendor_hash,
app_hash: self.app_hash,
secret: self.secret,
revision: self.revision,
result_max_lines: self.result_max_lines,
app_id: self.app_id,
current_request: 0,
service_pass: self.service_pass,
cursor: None,
client: reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(self.timeout))
.danger_accept_invalid_certs(self.allow_unsafe_certs)
.build()
.unwrap(),
}
}
}
pub struct WebwareClient {
webware_url: String,
vendor_hash: String,
app_hash: String,
secret: String,
revision: u32,
app_id: Option<String>,
current_request: u32,
result_max_lines: u32,
service_pass: Option<String>,
client: reqwest::Client,
cursor: Option<Cursor>,
}
impl WebwareClient {
pub fn builder() -> WebwareClientBuilder {
WebwareClientBuilder::new()
}
pub fn create_cursor(&mut self, max_lines: u32) {
self.cursor = Some(Cursor::new(max_lines));
}
pub fn cursor_closed(&self) -> bool {
if let Some(c) = self.cursor.as_ref() {
c.closed()
} else {
true
}
}
pub fn set_result_max_lines(&mut self, max_lines: u32) {
self.result_max_lines = max_lines;
}
pub fn get_default_headers(&mut self, additional_headers: Option<HashMap<&str, &str>>) -> HeaderMap {
let mut max_lines = self.result_max_lines;
let mut header_vec = vec![
("WWSVC-EXECUTE-MODE", "SYNCHRON".to_string()),
("WWSVC-ACCEPT-RESULT-TYPE", "JSON".to_string())
];
if self.app_id.is_some() {
let app_id = self.app_id.as_deref().expect("msg");
let app_hash = AppHash::new(self.current_request, app_id.to_string());
self.current_request = app_hash.request_id;
header_vec.append(&mut vec![
("WWSVC-REQID", format!("{}", self.current_request)),
("WWSVC-TS", app_hash.date_formatted.to_string()),
("WWSVC-HASH", format!("{:x}", app_hash))
]);
if let Some(cursor) = &self.cursor {
if !cursor.closed() {
header_vec.append(&mut vec![
("WWSVC-CURSOR", cursor.cursor_id.to_string())
]);
max_lines = cursor.max_lines;
}
}
}
header_vec.push(("WWSVC-ACCEPT-RESULT-MAX-LINES", format!("{}", max_lines)));
let mut headers: HashMap<String, String> = header_vec.iter()
.map(|(s1, s2)|(s1.to_string(), s2.to_string()))
.collect();
if let Some(additional_headers) = additional_headers {
for (key, value) in additional_headers {
headers.insert(key.to_string(), value.to_string());
}
}
(&headers).try_into().expect("invalid headers")
}
pub fn get_bin_headers(&mut self, additional_headers: Option<HashMap<&str, &str>>) -> HeaderMap {
let mut headers = self.get_default_headers(additional_headers);
headers.remove("WWSVC-ACCEPT-RESULT-TYPE");
headers.append("WWSVC-ACCEPT-RESULT-TYPE", HeaderValue::from_str("BIN").expect("valid header"));
headers
}
pub fn build_url(&self, parts: Vec<String>) -> String {
let append = parts.join("/");
return format!("{}{}", self.webware_url, append);
}
pub async fn register(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
let target_url = self.build_url(vec!["WWSERVICE".to_string(), "REGISTER".to_string(), self.vendor_hash.clone(), self.app_hash.clone(), self.secret.clone(), self.revision.clone().to_string()]);
let response = self.client.get(target_url).send().await?;
let response_obj = response.json::<HashMap<String, serde_json::Value>>().await?;
if !response_obj.contains_key("SERVICEPASS") {
return Ok(false);
}
let service_pass = response_obj["SERVICEPASS"].as_object().unwrap();
self.service_pass = Some(service_pass["PASSID"].as_str().unwrap().to_string());
self.app_id = Some(service_pass["APPID"].as_str().unwrap().to_string());
Ok(true)
}
pub async fn deregister(&mut self) -> bool {
if self.service_pass.is_none() {
return true;
}
let target_url = self.build_url(vec!["WWSERVICE".to_string(), "DEREGISTER".to_string(), self.service_pass.clone().unwrap()]);
let headers = self.get_default_headers(None);
let _ = self.client.get(target_url).headers(headers).send().await;
self.service_pass = None;
self.app_id = None;
true
}
pub async fn request(&mut self, method: reqwest::Method, function: &str, version: u32, parameters: HashMap<&str, &str>, additional_headers: Option<HashMap<&str, &str>>) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
return self.request_generic::<serde_json::Value>(method, function, version, parameters, additional_headers).await;
}
pub async fn request_as_response(&mut self, method: reqwest::Method, function: &str, version: u32, parameters: HashMap<&str, &str>, additional_headers: Option<HashMap<&str, &str>>) -> Result<Response, Box<dyn std::error::Error>> {
let target_url = self.build_url(vec!["EXECJSON".to_string()]);
let headers = self.get_default_headers(additional_headers);
let mut param_vec: Vec<HashMap<String, String>> = Vec::new();
let app_hash_header = headers.get("WWSVC-HASH");
let timestamp_header = headers.get("WWSVC-TS");
let app_hash: String = app_hash_header.unwrap_or(&HeaderValue::from_str("").unwrap()).to_str().expect("msg").to_string();
let timestamp: String = timestamp_header.unwrap_or(&HeaderValue::from_str("").unwrap()).to_str().expect("msg").to_string();
for (p_key, p_value) in parameters {
let mut map: HashMap<String, String> = HashMap::new();
map.insert("PNAME".to_string(), p_key.to_string());
map.insert("PCONTENT".to_string(), p_value.to_string());
param_vec.push(map);
}
let body = json!({
"WWSVC_FUNCTION": {
"FUNCTIONNAME": function,
"PARAMETER": param_vec,
"REVISION": version
},
"WWSVC_PASSINFO": {
"SERVICEPASS": self.service_pass.as_deref().unwrap_or(""),
"APPHASH": app_hash,
"TIMESTAMP": timestamp,
"REQUESTID": self.current_request,
"EXECUTE_MODE": "SYNCHRON"
}
});
let response = self.client.request(method, target_url)
.headers(headers)
.json(&body)
.send().await?;
if let Some(cursor) = &mut self.cursor {
if !cursor.closed() && response.headers().contains_key("WWSVC-CURSOR") {
cursor.set_cursor_id(response.headers().get("WWSVC-CURSOR").unwrap().to_str().unwrap().to_string());
}
}
Ok(response)
}
pub async fn request_generic<T>(&mut self, method: reqwest::Method, function: &str, version: u32, parameters: HashMap<&str, &str>, additional_headers: Option<HashMap<&str, &str>>) -> Result<T, Box<dyn std::error::Error>>
where
T:DeserializeOwned
{
let response = self.request_as_response(method, function, version, parameters, additional_headers).await?;
let response_obj = response.json::<T>().await?;
Ok(response_obj)
}
pub fn credentials(&self) -> Option<Credentials> {
if self.service_pass.is_none() || self.app_id.is_none() {
return None;
}
Some(Credentials::new(self.service_pass.clone().unwrap(), self.app_id.clone().unwrap()))
}
}