use reqwest::header::{HeaderMap, HeaderValue};
use serde::de::DeserializeOwned;
use std::convert::{TryInto};
use std::time::{SystemTime};
use std::collections::{HashMap};
use httpdate::fmt_http_date;
use encoding_rs::WINDOWS_1252;
use crate::cursor::Cursor;
pub struct AppHash {
pub request_id: u32,
pub hash: String,
pub date_formatted: String
}
pub struct WebwareClient {
pub webware_url: String,
pub webservice_path: String,
pub vendor_hash: String,
pub app_hash: String,
pub secret: String,
pub revision: u32,
pub app_id: Option<String>,
pub current_request: u32,
pub result_max_lines: u32,
pub service_pass: Option<String>,
pub client: reqwest::Client,
pub cursor: Option<Cursor>,
}
impl WebwareClient {
#[allow(clippy::too_many_arguments)]
pub fn new(vendor_hash: String, app_hash: String, secret: String, revision: u32, host: String, port: u16, wwsvc_path: String, allow_unsafe_certs: bool) -> Self {
let ww_url = format!("https://{}:{}{}", host, port, wwsvc_path);
Self {
webservice_path: wwsvc_path,
webware_url: ww_url,
vendor_hash,
app_hash,
secret,
revision,
app_id: None,
current_request: 0,
result_max_lines: 100000,
service_pass: None,
client: reqwest::Client::builder()
.danger_accept_invalid_certs(allow_unsafe_certs)
.build()
.unwrap(),
cursor: None,
}
}
pub fn create_cursor(&mut self, max_lines: u32) {
self.cursor = Some(Cursor::new(max_lines));
}
pub fn get_default_headers(&mut self) -> 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 headers: HashMap<String, String> = header_vec.iter()
.map(|(s1, s2)|(s1.to_string(), s2.to_string()))
.collect();
(&headers).try_into().expect("invalid headers")
}
pub fn get_bin_headers(&mut self) -> HeaderMap {
let mut headers = self.get_default_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();
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: String, version: u32, parameters: HashMap<String, String>) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
return self.request_generic::<serde_json::Value>(method, function, version, parameters).await;
}
pub async fn request_generic<T>(&mut self, method: reqwest::Method, function: String, version: u32, parameters: HashMap<String, String>) -> Result<T, Box<dyn std::error::Error>>
where
T:DeserializeOwned
{
let target_url = self.build_url(vec!["EXECJSON".to_string()]);
let headers = self.get_default_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);
map.insert("PCONTENT".to_string(), p_value);
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());
}
}
let response_obj = response.json::<T>().await?;
Ok(response_obj)
}
}
impl AppHash {
pub fn new(request_id: u32, app_secret: String) -> AppHash {
let now = fmt_http_date(SystemTime::now());
let new_request_id = request_id + 1;
let combined = format!("{}{}", app_secret, now);
let (cow, _encoding_used, _had_errors) = WINDOWS_1252.encode(&combined[..]);
let md5_hash = format!("{:x}", md5::compute(cow));
AppHash {
request_id: new_request_id,
hash: md5_hash,
date_formatted: now
}
}
}
impl std::fmt::LowerHex for AppHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.hash)
}
}