#![warn(missing_docs)]
#![warn(rustdoc::private_doc_tests)]
use crate::common::{note::Note, related_entity::EntityRef};
use base32::encode;
use chrono::{Days, Utc};
use common::{attachment::AttachmentRefOrValue, related_party::RelatedParty, tmf_error::TMFError};
use hex::decode;
use serde::{Deserialize, Serialize};
use sha256::digest;
use uuid::Uuid;
pub const LIB_PATH: &str = "tmf-api";
pub const TMF_PATH: Option<&str> = option_env!("TMF_PATH");
pub const CODE_DEFAULT_LENGTH: usize = 6;
pub type Cardinality = u16;
pub type TimeStamp = String;
pub type DateTime = String;
pub type Uri = String;
pub type Priority = u16;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TimePeriod {
pub start_date_time: TimeStamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_date_time: Option<TimeStamp>,
}
impl TimePeriod {
pub fn period_30days() -> TimePeriod {
TimePeriod::period_days(30)
}
pub fn period_days(days: u64) -> TimePeriod {
let now = Utc::now() + Days::new(days);
let time =
chrono::DateTime::from_timestamp(now.timestamp(), 0).expect("Invalid now() output");
TimePeriod {
end_date_time: Some(time.to_rfc3339()),
..Default::default()
}
}
pub fn started(&self) -> bool {
let now = Utc::now();
let start = chrono::DateTime::parse_from_rfc3339(&self.start_date_time)
.expect("Could not start parse time from now()");
if start < now {
return true;
}
false
}
pub fn finished(&self) -> bool {
match &self.end_date_time {
Some(f) => {
let now = Utc::now();
let finish = chrono::DateTime::parse_from_rfc3339(f)
.expect("Could not parse finish time from now()");
if finish < now {
return true;
}
false
}
None => false,
}
}
}
impl Default for TimePeriod {
fn default() -> Self {
let now = Utc::now();
let time =
chrono::DateTime::from_timestamp(now.timestamp(), 0).expect("Invalid input timestamp");
TimePeriod {
start_date_time: time.to_rfc3339(),
end_date_time: None,
}
}
}
impl From<DateTime> for TimePeriod {
fn from(value: TimeStamp) -> Self {
TimePeriod {
start_date_time: value.clone(),
end_date_time: None,
}
}
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct Quantity {
pub amount: f64,
pub units: String,
}
impl Quantity {
pub fn kg(amount: f64) -> Quantity {
Quantity {
amount,
units: "kg".to_string(),
}
}
pub fn cartons(amount: f64) -> Quantity {
Quantity {
amount,
units: "cartons".to_string(),
}
}
}
pub fn gen_code(
name: String,
id: String,
offset: Option<u32>,
prefix: Option<String>,
length: Option<usize>,
) -> (String, String) {
let hash_input = format!("{}:{}:{}", name, id, offset.unwrap_or_default());
let sha = digest(hash_input);
let hex = decode(sha);
let base32 = encode(
base32::Alphabet::Rfc4648 { padding: false },
hex.expect("Could not parse HEX string from digest()")
.as_ref(),
);
let sha_slice = base32.as_str()[..length.unwrap_or(CODE_DEFAULT_LENGTH)]
.to_string()
.to_ascii_uppercase();
(
format!("{}{}", prefix.unwrap_or_default(), sha_slice),
base32,
)
}
pub fn serde_value_to_type(value: &serde_json::Value) -> &str {
match value {
serde_json::Value::Null => "Null",
serde_json::Value::Bool(_) => "Bool",
serde_json::Value::Number(_) => "Number",
serde_json::Value::String(_) => "String",
serde_json::Value::Array(_) => "Array",
serde_json::Value::Object(_) => "Object",
}
}
pub fn vec_insert<T>(ov: &mut Option<Vec<T>>, item: T) {
match ov.as_mut() {
Some(v) => {
v.push(item);
}
None => {
let _old_i = ov.replace(vec![item]);
}
}
}
pub fn get_lib_path() -> String {
match TMF_PATH {
Some(p) => p.to_string(),
None => LIB_PATH.to_string(),
}
}
pub trait HasEntity: Default {
fn get_uuid() -> String {
Uuid::new_v4().simple().to_string()
}
fn generate_id(&mut self);
fn generate_href(&mut self);
fn get_id(&self) -> String;
fn get_href(&self) -> String;
fn get_full_href(&self, hostname: impl Into<String>) -> String {
format!("{}{}", hostname.into(), self.get_href())
}
fn get_class() -> String;
fn get_class_href() -> String;
fn get_mod_path() -> String;
fn set_id(&mut self, id: impl Into<String>);
fn create() -> Self {
let mut item = Self::default();
item.generate_id();
item
}
fn id(self, id: impl Into<String>) -> Self;
}
pub trait HasId: Default {
fn get_uuid() -> String {
Uuid::new_v4().simple().to_string()
}
fn generate_id(&mut self);
fn generate_href(&mut self);
fn get_id(&self) -> String;
fn get_href(&self) -> String;
fn get_full_href(&self, hostname: impl Into<String>) -> String {
format!("{}{}", hostname.into(), self.get_href())
}
fn get_class() -> String;
fn get_class_href() -> String;
fn get_mod_path() -> String;
fn set_id(&mut self, id: impl Into<String>);
fn create() -> Self {
let mut item = Self::default();
item.generate_id();
item
}
fn id(self, id: impl Into<String>) -> Self;
}
pub trait HasLastUpdate: HasId {
fn get_timestamp() -> String {
let now = Utc::now();
let time = chrono::DateTime::from_timestamp(now.timestamp(), 0)
.expect("Invalid timestamp from now()");
time.to_string()
}
fn get_last_update(&self) -> Option<String>;
fn set_last_update(&mut self, time: impl Into<String>);
fn create_with_time() -> Self {
let mut item = Self::create();
item.generate_id();
item.set_last_update(Self::get_timestamp());
item
}
fn last_update(self, time: Option<String>) -> Self;
}
pub trait HasValidity {
fn set_validity(&mut self, validity: TimePeriod);
fn get_validity(&self) -> Option<TimePeriod>;
fn get_validity_start(&self) -> Option<TimeStamp>;
fn get_validity_end(&self) -> Option<TimeStamp>;
fn set_validity_start(&mut self, start: TimeStamp) -> TimePeriod;
fn set_validity_end(&mut self, end: TimeStamp) -> TimePeriod;
fn is_valid(&self) -> bool;
fn validity(self, validity: TimePeriod) -> Self;
}
pub trait HasName: HasId {
fn get_name(&self) -> String;
fn find(&self, pattern: &str) -> bool {
self.get_name().contains(pattern.trim())
}
fn set_name(&mut self, name: impl Into<String>);
fn name(self, name: impl Into<String>) -> Self;
fn as_entity(&self) -> EntityRef {
EntityRef {
id: self.get_id(),
href: self.get_href(),
name: self.get_name(),
}
}
}
pub trait HasNote: HasId {
fn get_note(&self, idx: usize) -> Option<&Note>;
fn add_note(&mut self, note: Note);
fn remove_note(&mut self, idx: usize) -> Result<Note, TMFError>;
fn note(self, note: Note) -> Self;
}
pub trait HasRelatedParty: HasId {
fn get_party(&self, idx: usize) -> Option<&RelatedParty>;
fn add_party(&mut self, party: RelatedParty);
fn remove_party(&mut self, idx: usize) -> Result<RelatedParty, TMFError>;
fn get_by_role(&self, role: String) -> Option<Vec<&RelatedParty>>;
fn party(self, party: RelatedParty) -> Self;
}
pub trait TMFEvent<T>: HasId {
fn event(&self) -> T;
}
pub trait HasAttachment {
fn add(&mut self, attachment: &AttachmentRefOrValue);
fn position(&self, name: impl Into<String>) -> Option<usize>;
fn find(&self, name: impl Into<String>) -> Option<&AttachmentRefOrValue>;
fn get(&self, position: usize) -> Option<AttachmentRefOrValue>;
fn remove(&mut self, position: usize) -> Option<AttachmentRefOrValue>;
fn attachment(self, attachment: AttachmentRefOrValue) -> Self;
}
pub trait HasDescription {
fn description(self, description: impl Into<String>) -> Self;
fn get_description(&self) -> String;
fn set_description(&mut self, description: impl Into<String>) -> Option<String>;
}
pub trait HasReference: HasId + HasName {
type RefType: Serialize;
fn as_entity_ref(&self) -> crate::common::related_entity::RelatedEntity {
crate::common::related_entity::RelatedEntity {
id: self.get_id(),
href: self.get_href(),
name: self.get_name(),
referred_type: Self::get_class(),
role: None,
}
}
fn as_ref(&self) -> Option<Self::RefType> {
None
}
}
pub mod common;
#[cfg(feature = "tmf620")]
pub mod tmf620;
#[cfg(feature = "tmf621")]
pub mod tmf621;
#[cfg(feature = "tmf622")]
pub mod tmf622;
#[cfg(feature = "tmf628")]
pub mod tmf628;
#[cfg(feature = "tmf629")]
pub mod tmf629;
#[cfg(feature = "tmf632")]
pub mod tmf632;
#[cfg(feature = "tmf633")]
pub mod tmf633;
#[cfg(feature = "tmf634")]
pub mod tmf634;
#[cfg(feature = "tmf635")]
pub mod tmf635;
#[cfg(feature = "tmf637")]
pub mod tmf637;
#[cfg(feature = "tmf638")]
pub mod tmf638;
#[cfg(feature = "tmf639")]
pub mod tmf639;
#[cfg(feature = "tmf640")]
pub mod tmf640;
#[cfg(feature = "tmf641")]
pub mod tmf641;
#[cfg(feature = "tmf642")]
pub mod tmf642;
#[cfg(feature = "tmf644")]
pub mod tmf644;
#[cfg(feature = "tmf645")]
pub mod tmf645;
#[cfg(feature = "tmf646")]
pub mod tmf646;
#[cfg(feature = "tmf648")]
pub mod tmf648;
#[cfg(feature = "tmf651")]
pub mod tmf651;
#[cfg(feature = "tmf653")]
pub mod tmf653;
#[cfg(feature = "tmf663")]
pub mod tmf663;
#[cfg(feature = "tmf664")]
pub mod tmf664;
#[cfg(feature = "tmf666")]
pub mod tmf666;
#[cfg(feature = "tmf667")]
pub mod tmf667;
#[cfg(feature = "tmf669")]
pub mod tmf669;
#[cfg(feature = "tmf671")]
pub mod tmf671;
#[cfg(feature = "tmf672")]
pub mod tmf672;
#[cfg(feature = "tmf673")]
pub mod tmf673;
#[cfg(feature = "tmf674")]
pub mod tmf674;
#[cfg(feature = "tmf676")]
pub mod tmf676;
#[cfg(feature = "tmf678")]
pub mod tmf678;
#[cfg(feature = "tmf679")]
pub mod tmf679;
#[cfg(feature = "tmf680")]
pub mod tmf680;
#[cfg(feature = "tmf681")]
pub mod tmf681;
#[cfg(feature = "tmf687")]
pub mod tmf687;
#[cfg(feature = "tmf696")]
pub mod tmf696;
#[cfg(feature = "tmf697")]
pub mod tmf697;
#[cfg(feature = "tmf699")]
pub mod tmf699;
#[cfg(feature = "tmf700")]
pub mod tmf700;
#[cfg(feature = "tmf717")]
pub mod tmf717;
#[cfg(feature = "tmf723")]
pub mod tmf723;
#[cfg(feature = "tmf724")]
pub mod tmf724;
#[cfg(feature = "tmf760")]
pub mod tmf760;
#[cfg(feature = "tmf764")]
pub mod tmf764;
#[cfg(feature = "tmf909")]
pub mod tmf909;
#[cfg(feature = "tmf921")]
pub mod tmf921;
#[cfg(test)]
mod test {
use crate::{HasName, Quantity, TimePeriod};
use super::gen_code;
use super::vec_insert;
use crate::common::related_party::RelatedParty;
#[cfg(all(feature = "tmf632", feature = "build-V4"))]
use crate::tmf632::organization_v4::Organization;
#[cfg(all(feature = "tmf632", feature = "build-V5"))]
use crate::tmf632::organization_v5::Organization;
const CODE: &str = "T-DXQR65";
const HASH: &str = "DXQR656VE3FIKEZZWJX6C3WC27NSRTJVMYR7ILA5XNDLSJXQPDVQ";
const CARTON_QTY: f64 = 12.34;
const ORG_NAME: &str = "Organisation";
const QUANTITY_JSON: &str = "{
\"amount\" : 12.34,
\"units\" : \"units\"
}";
const PERIOD_JSON: &str = "{
\"startDateTime\" : \"2024-07-29T23:07:57Z\"
}";
#[test]
fn test_gen_code() {
let (code, hash) = gen_code("NAME".into(), "CODE".into(), None, Some("T-".into()), None);
assert_eq!(code, CODE.to_string());
assert_eq!(hash, HASH.to_string());
}
#[test]
fn test_quantity_kg() {
let quantity = Quantity::kg(10.5);
assert_eq!(quantity.amount, 10.5);
assert_eq!(quantity.units, "kg".to_string());
}
#[test]
fn test_timeperiod_30days() {
let days = TimePeriod::period_30days();
assert_eq!(days.started(), true);
assert_eq!(days.finished(), false);
}
#[test]
fn test_timeperiod_default() {
let default_period = TimePeriod::default();
assert_eq!(default_period.started(), true);
assert_eq!(default_period.end_date_time.is_none(), true);
}
#[test]
fn test_timeperiod_finished() {
let mut finished = TimePeriod::default();
assert_eq!(finished.finished(), false);
finished.end_date_time = Some(finished.start_date_time.clone());
assert_eq!(finished.finished(), true);
}
#[test]
fn test_quantity_cartons() {
let quantity = Quantity::cartons(CARTON_QTY);
assert_eq!(quantity.amount, CARTON_QTY);
assert_eq!(quantity.units.as_str(), "cartons");
}
#[test]
fn test_hasname_find() {
let cust = Organization::new(ORG_NAME);
let find_match = cust.find("Org");
assert_eq!(find_match, true);
}
#[test]
fn test_quantity_deserialize() {
let quantity: Quantity =
serde_json::from_str(QUANTITY_JSON).expect("Could not parse Quantity JSON");
assert_eq!(quantity.amount, 12.34);
assert_eq!(quantity.units.as_str(), "units");
}
#[test]
fn test_timeperiod_deserialize() {
let period: TimePeriod =
serde_json::from_str(PERIOD_JSON).expect("Could not parse Period JSON");
assert_eq!(period.start_date_time.as_str(), "2024-07-29T23:07:57Z");
assert_eq!(period.end_date_time.is_none(), true);
}
#[test]
fn test_timeperiod_not_started() {
let old_period = TimePeriod::period_30days();
let mut new_period = TimePeriod::default();
new_period.start_date_time = old_period
.end_date_time
.expect("perdio_30days() did not set end date")
.clone();
assert_eq!(new_period.started(), false);
}
#[test]
fn test_vecinsert_none() {
let rp = RelatedParty::default();
let mut ov: Option<Vec<RelatedParty>> = None;
vec_insert(&mut ov, rp);
assert_eq!(ov.is_some(), true);
assert_eq!(ov.unwrap().len(), 1);
}
#[test]
fn test_vecinsert_some() {
let mut rp = RelatedParty::default();
let _prev = rp.name.insert(String::from("one"));
let mut ov: Option<Vec<RelatedParty>> = Some(vec![rp]);
let rp2 = RelatedParty::default();
vec_insert(&mut ov, rp2);
assert_eq!(ov.is_some(), true);
assert_eq!(ov.unwrap().len(), 2);
}
#[test]
fn test_hasid_fullhref() {
use super::HasId;
let cust = Organization::new(ORG_NAME);
let full_href = cust.get_full_href("https://api.example.com");
assert_eq!(
full_href,
format!("https://api.example.com{}", cust.get_href())
);
}
}