use reqwest::{Client, RequestBuilder, StatusCode};
use std::str::FromStr;
use serde::Deserialize;
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub struct Api {
client: Client,
api_url: String,
base_url: String,
credentials: Credentials,
}
pub struct Credentials {
pub user: String,
pub password: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Response {
package_id: u128,
package_name: String,
status: String,
message: String,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PackageStatus {
New,
ExportPending,
Exported,
ExportFailed,
ImportFailed,
Imported,
ImportPending,
}
impl FromStr for PackageStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"New" => Ok(Self::New),
"Export Pending" => Ok(Self::ExportPending),
"Exported" => Ok(Self::Exported),
"Export Failed" => Ok(Self::ExportFailed),
"Import Failed" => Ok(Self::ImportFailed),
"Imported" => Ok(Self::Imported),
"Import Pending" => Ok(Self::ImportPending),
_ => Err(()),
}
}
}
impl Api {
pub fn new(base_url: &str, credentials: Credentials) -> Api {
let api_url = String::from(base_url) + "/api/p/v1/om";
let client = Client::builder().cookie_store(true).build().unwrap();
Api {
client: client,
api_url,
base_url: base_url.to_owned(),
credentials,
}
}
pub async fn login(&self) -> Result<(), Box<dyn Error>> {
let body = serde_json::json!({
"userName": self.credentials.user,
"password": self.credentials.password,
"normal": false,
"api": true,
});
let url = self.base_url.clone() + "/p/websignon/signon";
let response = self.client.post(url).json(&body).send().await?;
match response.status() {
StatusCode::OK => {
println!("🔐 TRIRIGA Authenticated");
Ok(())
}
_ => Err("Unauthenticated")?,
}
}
#[allow(dead_code)]
fn authenticated_post(&self, url: &str) -> RequestBuilder {
self.client.post(url)
}
fn get(&self, url: &str) -> RequestBuilder {
self.client.get(url)
}
#[allow(dead_code)]
pub async fn add_all_application_dependant_objects_to_package_by_id(
&self,
package_id: u128,
date_in_millis: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let url = self.api_url.clone()
+ "/addAllApplicationDependentObjectsToPackage"
+ "/" + &package_id.to_string();
self.get(&url)
.query(&[("dateInMillis", date_in_millis)])
.send()
.await?;
Ok(())
}
#[allow(dead_code)]
pub async fn add_all_application_dependant_objects_to_package_by_name(
&self,
package_name: &str,
date_in_millis: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let url = self.api_url.clone() + "/addAllApplicationDependentObjectsToPackage";
self.get(&url)
.query(&[
("packageName", package_name),
("dateInMillis", date_in_millis),
])
.send()
.await?;
Ok(())
}
#[allow(dead_code)]
pub async fn add_object_to_package_by_name(
&self,
package_name: &str,
object_type: &str,
object_name: &str,
module_name: &str,
bo_name: &str,
) -> Result<(), Box<dyn Error>> {
let url = self.api_url.clone() + "/addObjectToPackage";
let correct_bo_name = if bo_name == "~" { "" } else { bo_name };
let res = self
.get(&url)
.query(&[
("packageName", package_name),
("objectType", object_type),
("objectName", object_name),
("moduleName", module_name),
("boName", correct_bo_name),
])
.send()
.await?;
match res.status() {
StatusCode::OK => Ok(()),
_ => panic!(
"Couldn't add object {} to package {}",
&object_name, &package_name
),
}
}
pub async fn add_object_to_package_by_id(
&self,
package_id: &str,
object_type: &str,
object_name: &str,
module_name: &str,
bo_name: &str,
) -> Result<(), Box<dyn Error>> {
let url = self.api_url.clone() + "/addObjectToPackage" + "/" + package_id;
let correct_bo_name = if bo_name == "~" { "" } else { bo_name };
let res = self
.get(&url)
.query(&[
("objectType", object_type),
("objectName", object_name),
("moduleName", module_name),
("boName", correct_bo_name),
])
.send()
.await?;
match res.status() {
StatusCode::OK => Ok(()),
_ => panic!(
"Couldn't add object {} to package {}. {:#?}",
&object_name, &package_id, &res
),
}
}
pub async fn create_new_package(
&self,
name: &str,
description: &str,
) -> Result<u128, Box<dyn Error>> {
let url = self.api_url.clone() + "/createEmptyPackage";
let res = self
.get(&url)
.query(&[("packageName", name), ("packageDescription", description)])
.send()
.await?;
let parsed_res = res.json::<Response>().await?;
Ok(parsed_res.package_id)
}
pub async fn download_package_as_zip(
&self,
package_name: &str,
path: &Path,
) -> Result<(), Box<dyn Error>> {
let url = self.api_url.clone() + "/downloadPackageFromUserFiles";
let res = self
.get(&url)
.query(&[("packageName", package_name)])
.send()
.await?;
let content = res.bytes().await?;
self.save_as_file(path, &content)?;
Ok(())
}
#[allow(dead_code)]
pub async fn export_package_by_name(
&self,
package_name: &str,
) -> Result<(), Box<dyn Error>> {
let url = self.api_url.clone() + "/export";
let res = self
.get(&url)
.query(&[("packageName", package_name)])
.send()
.await?;
match res.status() {
StatusCode::OK => Ok(()),
e => panic!("Unable to export package. {}", e),
}
}
pub async fn export_package_by_id(&self, package_id: &str) -> Result<(), Box<dyn Error>> {
let url = self.api_url.clone() + "/export/" + package_id;
let res = self.get(&url).send().await?;
match res.status() {
StatusCode::OK => Ok(()),
e => panic!("Unable to export package. {}", e),
}
}
pub async fn get_package_status_by_id(
&self,
package_id: &str,
) -> Result<PackageStatus, Box<dyn Error>> {
let url = self.api_url.clone() + "/packageStatus/" + package_id;
let res = self.get(&url).send().await?;
let parsed_res = self.parse_response(res).await?;
match PackageStatus::from_str(&parsed_res.status) {
Ok(s) => Ok(s),
Err(_) => panic!("Could not parse status"),
}
}
pub async fn get_package_status_by_name(
&self,
package_name: &str,
) -> Result<PackageStatus, Box<dyn Error>> {
let url = self.api_url.clone() + "/packageStatus";
let res = self
.get(&url)
.query(&[("packageName", package_name)])
.send()
.await?;
let parsed_res = self.parse_response(res).await?;
match PackageStatus::from_str(&parsed_res.status) {
Ok(s) => Ok(s),
Err(_) => panic!("Could not parse status"),
}
}
fn save_as_file(&self, path: &Path, content: &[u8]) -> Result<(), Box<dyn Error>> {
if path.exists() {
println!("⚠️ [WARN] File {:?} exists. Overwriting...", path)
} else {
println!("🆕 Creating file {:?}", path)
}
let mut file = File::create(path)?;
file.write_all(content)?;
Ok(())
}
async fn parse_response(&self, r: reqwest::Response) -> Result<Response, Box<dyn Error>> {
match r.json::<Response>().await {
Ok(res) => Ok(res),
Err(e) => panic!("Failed to parse response from API: {}", e,),
}
}
}