use futures::future::BoxFuture;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::Response;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use typed_builder::TypedBuilder;
use url::Url;
use crate::client::states::*;
use crate::error::WWSVCError;
use crate::responses::RegisterResponse;
use crate::{AppHash, Credentials, Cursor, WWClientResult};
#[derive(TypedBuilder)]
#[builder(build_method(into = WebwareClient::<Unregistered>))]
pub struct InternalWebwareClient {
#[builder(setter(transform = |url: &str| {
Url::parse(url).expect("Failed to parse URL").join("/WWSVC/").expect("Failed to join URL")
}))]
webware_url: Url,
#[builder(setter(transform = |vendor_hash: &str| vendor_hash.to_string()))]
vendor_hash: String,
#[builder(setter(transform = |app_hash: &str| app_hash.to_string()))]
app_hash: String,
#[builder(setter(transform = |app_secret: &str| app_secret.to_string()))]
secret: String,
revision: u32,
#[builder(default, setter(transform = |credentials: Credentials| Some(credentials)))]
credentials: Option<Credentials>,
#[builder(default = 1000)]
result_max_lines: u32,
#[builder(default = false)]
allow_insecure: bool,
#[builder(default = std::time::Duration::from_secs(60))]
timeout: std::time::Duration,
}
pub mod states {
#[derive(Clone)]
pub struct Unregistered;
#[derive(Clone)]
pub struct Registered;
pub struct OpenCursor;
pub trait Ready {}
impl Ready for Registered {}
impl Ready for OpenCursor {}
}
#[derive(Clone)]
pub struct WebwareClient<State = Unregistered> {
webware_url: Url,
vendor_hash: String,
app_hash: String,
secret: String,
revision: u32,
credentials: Option<Credentials>,
result_max_lines: u32,
cursor: Option<Cursor>,
current_request: u32,
client: reqwest::Client,
suspend_cursor: bool,
state: std::marker::PhantomData<State>,
}
impl From<InternalWebwareClient> for WebwareClient<Unregistered> {
fn from(client: InternalWebwareClient) -> Self {
let req_client = reqwest::Client::builder()
.danger_accept_invalid_certs(client.allow_insecure)
.timeout(client.timeout)
.build()
.expect("Failed to build client");
WebwareClient {
webware_url: client.webware_url,
vendor_hash: client.vendor_hash,
app_hash: client.app_hash,
secret: client.secret,
revision: client.revision,
credentials: client.credentials,
result_max_lines: client.result_max_lines,
cursor: None,
current_request: 0,
client: req_client,
suspend_cursor: false,
state: std::marker::PhantomData::<Unregistered>,
}
}
}
impl TryFrom<InternalWebwareClient> for WebwareClient<Registered> {
type Error = WWSVCError;
fn try_from(client: InternalWebwareClient) -> Result<Self, Self::Error> {
let req_client = reqwest::Client::builder()
.danger_accept_invalid_certs(client.allow_insecure)
.timeout(client.timeout)
.build()
.expect("Failed to build client");
if client.credentials.is_none() {
return Err(WWSVCError::MissingCredentials);
}
Ok(WebwareClient {
webware_url: client.webware_url,
vendor_hash: client.vendor_hash,
app_hash: client.app_hash,
secret: client.secret,
revision: client.revision,
credentials: client.credentials,
result_max_lines: client.result_max_lines,
cursor: None,
current_request: 0,
client: req_client,
suspend_cursor: false,
state: std::marker::PhantomData::<Registered>,
})
}
}
impl WebwareClient {
pub fn builder() -> InternalWebwareClientBuilder {
InternalWebwareClient::builder()
}
pub async fn register(self) -> WWClientResult<WebwareClient<Registered>> {
if self.credentials.is_some() {
return Ok(WebwareClient {
webware_url: self.webware_url,
vendor_hash: self.vendor_hash,
app_hash: self.app_hash,
secret: self.secret,
revision: self.revision,
credentials: self.credentials,
result_max_lines: self.result_max_lines,
cursor: self.cursor,
current_request: self.current_request,
client: self.client,
suspend_cursor: self.suspend_cursor,
state: std::marker::PhantomData::<Registered>,
});
}
let target_url = self
.webware_url
.join("WWSERVICE/")?
.join("REGISTER/")?
.join(&format!("{}/", self.vendor_hash))?
.join(&format!("{}/", self.app_hash))?
.join(&format!("{}/", self.secret))?
.join(&format!("{}/", self.revision))?;
let response = self.client.get(target_url).send().await?;
let response_obj = response.json::<RegisterResponse>().await?;
Ok(WebwareClient {
webware_url: self.webware_url,
vendor_hash: self.vendor_hash,
app_hash: self.app_hash,
secret: self.secret,
revision: self.revision,
credentials: Some(Credentials {
service_pass: response_obj.service_pass.pass_id,
app_id: response_obj.service_pass.app_id,
}),
result_max_lines: self.result_max_lines,
cursor: self.cursor,
current_request: self.current_request,
client: self.client,
suspend_cursor: self.suspend_cursor,
state: std::marker::PhantomData::<Registered>,
})
}
pub async fn with_registered<F, T>(self, f: F) -> WWClientResult<T>
where
F: for<'a> FnOnce(&'a mut WebwareClient<Registered>) -> BoxFuture<'a, T>,
{
let mut client = self.register().await?;
let result = f(&mut client).await;
let _ = client.deregister().await?;
Ok(result)
}
}
impl<State: Ready> WebwareClient<State> {
pub fn create_cursor(self, max_lines: u32) -> WebwareClient<OpenCursor> {
let cursor = Cursor::new(max_lines);
WebwareClient {
webware_url: self.webware_url,
vendor_hash: self.vendor_hash,
app_hash: self.app_hash,
secret: self.secret,
revision: self.revision,
credentials: self.credentials,
result_max_lines: self.result_max_lines,
cursor: Some(cursor),
current_request: self.current_request,
client: self.client,
suspend_cursor: self.suspend_cursor,
state: std::marker::PhantomData::<OpenCursor>,
}
}
pub fn credentials(&self) -> &Credentials {
self.credentials.as_ref().unwrap()
}
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>>,
) -> WWClientResult<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 let Some(credentials) = &self.credentials {
let app_hash = AppHash::new(self.current_request, &credentials.app_id);
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 !self.suspend_cursor {
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", max_lines.to_string()));
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 {
headers.extend(
additional_headers
.iter()
.map(|(s1, s2)| (s1.to_string(), s2.to_string())),
);
}
(&headers).try_into().map_err(|_| WWSVCError::InvalidHeader)
}
pub fn get_bin_headers(
&mut self,
additional_headers: Option<HashMap<&str, &str>>,
) -> WWClientResult<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")?);
Ok(headers)
}
pub async fn deregister(mut self) -> WWClientResult<WebwareClient<Unregistered>> {
if let Some(credentials) = &self.credentials {
let target_url = self
.webware_url
.join("WWSERVICE/")?
.join("DEREGISTER/")?
.join(&format!("{}/", &credentials.service_pass))?;
let headers = self.get_default_headers(None)?;
let _ = self.client.get(target_url).headers(headers).send().await;
}
Ok(WebwareClient {
webware_url: self.webware_url,
vendor_hash: self.vendor_hash,
app_hash: self.app_hash,
secret: self.secret,
revision: self.revision,
credentials: None,
result_max_lines: self.result_max_lines,
cursor: self.cursor,
current_request: self.current_request,
client: self.client,
suspend_cursor: self.suspend_cursor,
state: std::marker::PhantomData::<Unregistered>,
})
}
pub async fn request(
&mut self,
method: reqwest::Method,
function: &str,
version: u32,
parameters: HashMap<&str, &str>,
additional_headers: Option<HashMap<&str, &str>>,
) -> WWClientResult<serde_json::Value> {
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>>,
) -> WWClientResult<Response> {
if self.credentials.is_none() {
return Err(WWSVCError::NotAuthenticated);
}
let target_url = self.webware_url.join("EXECJSON")?;
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()
.map_err(|_| WWSVCError::HeaderValueToStrError)?
.to_string();
let timestamp: String = timestamp_header
.unwrap_or(&HeaderValue::from_str("").unwrap())
.to_str()
.map_err(|_| WWSVCError::HeaderValueToStrError)?
.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.credentials.as_ref().unwrap().service_pass,
"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 !self.suspend_cursor {
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>>,
) -> WWClientResult<T>
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)
}
}
impl WebwareClient<OpenCursor> {
pub fn suspend_cursor(&mut self) {
self.suspend_cursor = true;
}
pub fn resume_cursor(&mut self) {
self.suspend_cursor = false;
}
pub fn cursor_closed(&self) -> bool {
self.cursor.as_ref().unwrap().closed()
}
}