basic framework laid out

master
Griffiths Lott 3 years ago
commit f1dd5157d2
  1. 1
      .gitignore
  2. 1184
      Cargo.lock
  3. 16
      Cargo.toml
  4. 89
      src/client.rs
  5. 237
      src/dto.rs
  6. 3
      src/lib.rs
  7. 8
      src/tests.rs

1
.gitignore vendored

@ -0,0 +1 @@
/target

1184
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,16 @@
[package]
name = "IssueTrak"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
reqwest = {features = ["blocking", "json"] }
uuid = { version = "1.1.2", features = ["fast-rng", "v4"]}
hmac = "0.12.1"
sha2 = "0.10.2"
base64 = "0.13.0"
chrono = "0.4.19"
serde_json = "1.0.81"
serde = {version = "1.0.144", features = ["derive"]}

@ -0,0 +1,89 @@
use sha2::Sha512;
use hmac::{Hmac, Mac};
use reqwest::header::HeaderMap;
type HmacSha512 = Hmac<Sha512>;
use uuid::Uuid;
use chrono::{Utc,SecondsFormat};
use std::collections::HashMap;
use serde_json::json;
use crate::dto::{IssueSearchQueryDTO,IssueSearchResponse, Collection};
use std::error::Error as StdError;
pub struct IssueTrakClient<'a> {
api_key : &'a str,
req_client : reqwest::blocking::Client,
base_url : &'a str
}
impl<'a> IssueTrakClient<'a> {
pub fn new(api_key: &'a str, base_url: &'a str) -> Self {
IssueTrakClient {
api_key : api_key,
req_client : reqwest::blocking::Client::new(),
base_url : base_url
}
}
fn gen_hmac(&self, header_data: String) -> String {
let mut mac = HmacSha512::new_from_slice(self.api_key.as_bytes()).unwrap();
mac.update(header_data.as_bytes());
let res: Vec<u8> = mac.finalize().into_bytes().iter().map(|b| *b).collect();
base64::encode(res)
}
fn build_headers(&self, req_type: &str, endpoint_path: &str, body_str: &str) -> HeaderMap {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true);
let guid = Uuid::new_v4().to_string();
let auth_str = format!("{}\n{}\n{}\n{}\n{}\n{}",req_type, &guid, &timestamp, endpoint_path, "",body_str);
println!("auth_str = {:?}", auth_str);
let auth_hash = self.gen_hmac(auth_str);
let mut headers = HeaderMap::new();
headers.insert("Accept", "application/json".parse().unwrap());
headers.insert("Content-Type", "application/json; charset=utf-8".parse().unwrap());
headers.insert("Accept-Language", "en-US".parse().unwrap());
headers.insert("X-IssueTrak-API-Request-ID", guid.parse().unwrap());
headers.insert("X-IssueTrak-API-Timestamp", timestamp.parse().unwrap());
headers.insert("X-IssueTrak-API-Authorization", auth_hash.parse().unwrap());
println!("headers = {:?}\n\n", headers);
headers
}
pub fn issue_by_number(&self, issue_number: i32) -> Result<Collection, Box< dyn StdError>>{
let endpoint_suffix = format!("api/v1/issues/true/{issue_number}");
let full_endpoint = format!("{}api/v1/issues/true/{}", self.base_url, issue_number);
println!("full_endpoint = {:?}\n", full_endpoint);
let header_data = self.build_headers("GET", &endpoint_suffix,"");
let res = self.req_client.get(full_endpoint)
.headers(header_data)
.send();
Ok(serde_json::from_str(&res?.text()?)?)
}
pub fn update_issue(&self, updated_issue: Collection) -> bool {
let endpoint_suffix = format!("api/v1/issues/");
let full_endpoint = format!("{}api/v1/issues/", self.base_url);
let header_data = self.build_headers("POST", &endpoint_suffix,&updated_issue.to_string());
let res = self.req_client.post(full_endpoint)
.headers(header_data)
.send();//.unwrap_or(return false);
let good_resp = match res {
Ok(r) => r,
Err(e) => return false
};
match good_resp.text() {
Ok(t) => return t.contains(&updated_issue.IssueNumber.to_string()),
Err(e) => return false
}
}
pub fn update_issue_by_id(&self, issue_id: i32, issue_field: String, new_issue_value: String) -> bool {
// get the issue then change the collection, then return that collection with an updated
let mut cur_issue = match self.issue_by_number(issue_id) {
Ok(r) => r,
Err(e) => return false
};
let c_string = cur_issue.to_string();
println!("{:?}", c_string);
false
}
}

@ -0,0 +1,237 @@
use serde::{Serialize, Deserialize};
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
//
// Query Set Expression
//
#[derive(Debug, Serialize, Deserialize)]
pub enum QueryExpOperator {
AND,
OR
}
impl std::fmt::Display for QueryExpOperator {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f,"{:?}", self)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum QueryExpOperation {
Equal,
NotEqual,
GreaterThan,
LessThan,
}
impl std::fmt::Display for QueryExpOperation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f,"{:?}", self)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QuerySetExpression {
pub QueryExpressionOperator: QueryExpOperator,
pub QueryExpressionOperation: QueryExpOperation,
pub FieldName: String,
pub FieldFilterValue1: String,
pub FieldFilterValue2: Option<String>,
}
impl QuerySetExpression {
fn field_comp(field: String, value: String, comparison: QueryExpOperation) -> Self {
QuerySetExpression {
QueryExpressionOperator : QueryExpOperator::AND,
QueryExpressionOperation : comparison,
FieldName : field,
FieldFilterValue1: value,
FieldFilterValue2: None
}
}
}
//
// Query Ordering Exp
//
#[derive(Debug, Serialize, Deserialize)]
pub enum QueryOrd {
ASC,
DESC
}
impl std::fmt::Display for QueryOrd {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f,"{:?}", self)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryOrderingDefinitions {
pub QueryOrderingDirection: QueryOrd,
pub FieldName : String
}
impl QueryOrderingDefinitions {
fn new(query_ord_dir: QueryOrd, field_to_ord: String) -> Self {
QueryOrderingDefinitions {
QueryOrderingDirection: query_ord_dir,
FieldName: field_to_ord
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QuerySetDefinitions {
pub QuerySetIndex : usize,
pub QuerySetOperator : QueryExpOperator,
pub QuerySetExpressions: Vec<QuerySetExpression>
}
impl QuerySetDefinitions {
fn new_default(query_set_expressions: Vec<QuerySetExpression>, index: Option<usize>, operator: Option<QueryExpOperator>) -> Self {
QuerySetDefinitions {
QuerySetIndex : index.unwrap_or(0),
QuerySetOperator : operator.unwrap_or(QueryExpOperator::AND),
QuerySetExpressions : query_set_expressions
}
}
}
// Full Search Issue Query
#[derive(Debug, Serialize, Deserialize)]
pub struct IssueSearchQueryDTO {
pub QuerySetDefinitions : Vec<QuerySetDefinitions>,
pub QueryOrderingDefinitions: Vec<QueryOrderingDefinitions>,
pub PageIndex: usize,
pub PageSize: usize,
pub CanIncludeNotes: bool
}
impl IssueSearchQueryDTO {
fn new(query_set_defs: Vec<QuerySetDefinitions>, query_ord: Vec<QueryOrderingDefinitions>, page_size: Option<usize>, page_index: Option<usize>, include_notes: Option<bool>) -> Self {
IssueSearchQueryDTO {
QuerySetDefinitions : query_set_defs,
QueryOrderingDefinitions : query_ord,
PageSize : page_size.unwrap_or(100),
PageIndex : page_index.unwrap_or(0),
CanIncludeNotes : include_notes.unwrap_or(true)
}
}
fn from_file(file_path: String) -> Result<Self, Box< dyn Error>>{
let file = File::open(file_path)?;
let reader = BufReader::new(file);
let json = serde_json::from_reader(reader)?;
Ok(json)
}
fn add_query_expression<T: std::string::ToString>(&mut self, field_name: String, field_value: T, field_value2: Option<String>, operator : Option<QueryExpOperator>, operation: Option<QueryExpOperation>) {
let new_query = QuerySetExpression {
QueryExpressionOperator: operator.unwrap_or(QueryExpOperator::AND),
QueryExpressionOperation: operation.unwrap_or(QueryExpOperation::Equal),
FieldName: field_name,
FieldFilterValue1: field_value.to_string(),
FieldFilterValue2: field_value2,
};
self.QuerySetDefinitions.push(QuerySetDefinitions::new_default(vec![new_query], None, None));
}
fn replace_field_name(&mut self, cur_field_name: String, new_field_name: String) {
for qsd in &mut self.QuerySetDefinitions {
for qes in &mut qsd.QuerySetExpressions {
match &qes.FieldName == &cur_field_name {
true => {qes.FieldName = new_field_name; return ()},
false => {}
}
}
}
}
fn replace_field_values(&mut self, field_name: String, new_field_values: [Option<String>;2]) {
for qsd in &mut self.QuerySetDefinitions {
for qes in &mut qsd.QuerySetExpressions {
match &qes.FieldName == &field_name {
true => {qes.FieldFilterValue1 = new_field_values[0].clone().unwrap().to_string(); qes.FieldFilterValue2 = new_field_values[1].clone(); return ()},
false => {}
}
}
}
}
fn to_string(&self) -> String {
serde_json::to_string(&self).unwrap()
}
}
///
/// These are responses
///
#[derive(Debug, Serialize, Deserialize)]
struct MetaData {
Key: String,
Value: String
}
#[derive(Debug, Serialize, Deserialize)]
struct UserDefinedFields {
ExtensionDate: Vec<String>,
MetaData : Vec<MetaData>,
IssueNumber: i32,
DisplayName: String,
UserDefinedFieldID: i16,
Value: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Collection {
pub IssueNumber: i32,
pub IssueSolition: Option<String>,
pub Status: String,
pub ClosedBy: Option<String>,
pub ClosedDate: Option<String>,
pub SubmittedDate : String,
pub CauseID: Option<String>,
pub Notes: Vec<String>,
pub UserDefinedFields: Vec<String>,
pub Subject: String,
pub Description: String,
pub IsDescriptionRichText: bool,
pub IssueTypeID: usize,
pub IssueSubtypeID: usize,
pub IssueSubtypeI2D: usize,
pub IssueSubtype3ID: usize,
pub IssueSubtype4ID: usize,
pub Priority: String,
pub AssetNumber: Option<String>,
pub LocationID: String,
pub SubmittedBy: String,
pub AssignedTo: String,
pub TargetDate: Option<String>,
pub RequiredByDate: Option<String>,
pub NextActionTo: Option<String>,
pub SubStatusID: usize,
pub ProjectID : usize,
pub OrganizationID: usize,
pub ShouldNeverSendEmailForIssue: Option<bool>,
pub ClassID: usize,
pub DeparmentID: usize,
pub SpecialFunction1: String,
pub SpecialFunction2: String,
pub SpecialFunction3: String,
pub SpecialFunction4: String,
pub SpecialFunction5: String,
}
impl ToString for Collection {
fn to_string(&self) -> String {
serde_json::to_string(&self).unwrap()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IssueSearchResponse {
IsPageBased: bool,
PageIndex: usize,
CountForPage: usize,
PageSize: usize,
TotalCount: usize,
Collection: Vec<Collection>,
}
impl IssueSearchResponse {
fn got_all_results(&self) -> bool {
self.TotalCount == self.CountForPage
}
}

@ -0,0 +1,3 @@
pub mod client;
pub mod dto;
pub mod tests;

@ -0,0 +1,8 @@
use crate::client;
use crate::dto;
#[test]
fn test_update_issue_by_id() {
let client = client::IssueTrakClient::new("", "https://itsupport..com/");
client.update_issue_by_id(177951 , "test", "Test")
}
Loading…
Cancel
Save