diff --git a/.rustfmt.toml b/.rustfmt.toml index b204247..9b52fc0 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,4 @@ hard_tabs = true -use_field_init_shorthand = true \ No newline at end of file +use_field_init_shorthand = true +unstable_features = true +imports_granularity = "Crate" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d9a740..2d0de34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -63,7 +63,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -72,12 +72,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "bumpalo" version = "3.16.0" @@ -172,54 +166,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "csv" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - [[package]] name = "getrandom" version = "0.2.15" @@ -237,12 +183,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "iana-time-zone" version = "0.1.61" @@ -266,17 +206,6 @@ dependencies = [ "cc", ] -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -308,36 +237,17 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -dependencies = [ - "serde", -] [[package]] name = "memchr" @@ -369,20 +279,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettytable-rs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" -dependencies = [ - "csv", - "encode_unicode", - "is-terminal", - "lazy_static", - "term", - "unicode-width", -] - [[package]] name = "proc-macro2" version = "1.0.89" @@ -399,8 +295,6 @@ dependencies = [ "chrono", "clap", "journal", - "log", - "prettytable-rs", "psychonaut_journal_types", "serde", "serde_json", @@ -452,23 +346,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "rustversion" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" - [[package]] name = "ryu" version = "1.0.18" @@ -530,49 +407,12 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "utf8parse" version = "0.2.2" @@ -640,28 +480,6 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.52.0" @@ -671,15 +489,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/journal/src/helpers.rs b/journal/src/helpers.rs deleted file mode 100644 index f6ab8bf..0000000 --- a/journal/src/helpers.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::types::doses::{CustomUnitDose, StandardDose}; - -pub fn calulate_custom_unit_dose( - dose: &CustomUnitDose, - custom_unit_dose: &CustomUnitDose, -) -> StandardDose { - let dose: StandardDose = dose.clone().into(); - let custom_unit_dose: StandardDose = custom_unit_dose.clone().into(); - - custom_unit_dose * dose -} diff --git a/journal/src/journal.rs b/journal/src/journal.rs index b77ea4c..13663a0 100644 --- a/journal/src/journal.rs +++ b/journal/src/journal.rs @@ -2,8 +2,8 @@ use chrono::{DateTime, TimeZone, Utc}; use rand::Rng; use crate::types::{ - doses::{CustomUnitDose, Dose, StandardDose, UnknownDose}, - Consumer, CustomUnit, Estimation, Experience, ExportFormat, Ingestion, + Consumer, CustomUnit, Dose, Estimation, Experience, ExportFormat, Ingestion, StandardDeviation, + Unit, }; pub type JournalType = Box; @@ -14,7 +14,7 @@ pub trait Journal { fn get_experience_ingestions(&self, id: i64) -> Option<&Vec>; fn first_experience_by_title(&self, title: &str) -> Option<&Experience>; - fn maybe_custom_unit(&self, ingestion: &Ingestion) -> Option<&CustomUnit>; + fn resolve_unit(&self, unit: &Unit) -> Unit; fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData); } @@ -34,10 +34,19 @@ impl Journal for InMemJournal { self.ingestions.get(&id) } - fn maybe_custom_unit(&self, ingestion: &Ingestion) -> Option<&CustomUnit> { - match &ingestion.dose { - Dose::CustomUnit(dose) => self.get_custom_unit(dose.custom_unit_id), - _ => None, + fn resolve_unit(&self, unit: &Unit) -> Unit { + match unit { + Unit::Simple(_) => unit.clone(), + Unit::Custom { id, unit } => match unit { + Some(unit) => Unit::Custom { + id: *id, + unit: Some(unit.clone()), + }, + None => Unit::Custom { + id: *id, + unit: self.get_custom_unit(*id).cloned(), + }, + }, } } @@ -48,101 +57,19 @@ impl Journal for InMemJournal { } fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData) { - fn from_unix_millis(time: u64) -> DateTime { - Utc.timestamp_millis_opt(time as i64).unwrap() - } - let mut rng = rand::thread_rng(); for custom_unit in data.custom_units.into_iter() { - let estimation = if custom_unit.is_estimate { - if let Some(standard_deviation) = custom_unit.estimate_standard_deviation { - Estimation::StandardDeviation(standard_deviation) - } else { - Estimation::Estimate - } - } else { - Estimation::Precise - }; - - let dose = CustomUnitDose { - dose: custom_unit.dose, - unit: custom_unit.unit, - original_unit: custom_unit.original_unit, - custom_unit_id: custom_unit.id, - estimation, - }; - - self.custom_units.insert( - custom_unit.id, - CustomUnit { - id: custom_unit.id, - name: custom_unit.name, - substance_name: custom_unit.substance_name, - - administration_route: custom_unit.administration_route, - dose, - - creation_time: from_unix_millis(custom_unit.creation_time), - is_archived: custom_unit.is_archived, - }, - ); + self.custom_units + .insert(custom_unit.id, CustomUnit::from(custom_unit)); } for experience in data.experiences.into_iter() { let experience_id = rng.gen::(); let mut ingestions: Vec = Vec::new(); - for ingestion in experience.ingestions.into_iter() { - let estimation = if !ingestion.is_estimate { - Estimation::Precise - } else if let Some(standard_deviation) = ingestion.estimate_standard_deviation { - Estimation::StandardDeviation(standard_deviation) - } else { - Estimation::Estimate - }; - - let dose = if let Some(dose) = ingestion.dose { - if let Some(custom_unit_id) = ingestion.custom_unit_id { - let custom_unit = self - .custom_units - .get(&custom_unit_id) - .expect("custom unit not found"); - - Dose::CustomUnit(CustomUnitDose { - dose, - unit: custom_unit.dose.unit.clone(), - original_unit: custom_unit.dose.original_unit.clone(), - estimation, - custom_unit_id, - }) - } else { - Dose::Standard(StandardDose { - dose, - unit: ingestion.unit, - estimation, - contains_unknown: false, - }) - } - } else { - Dose::Unknown(UnknownDose { - unit: ingestion.unit, - }) - }; - - ingestions.push(Ingestion { - substance_name: ingestion.substance_name, - ingestion_time: from_unix_millis(ingestion.ingestion_time), - creation_time: from_unix_millis(ingestion.creation_time), - dose, - roa: ingestion.roa, - consumer: match ingestion.consumer_name { - Some(name) => Consumer::Named(name), - None => Consumer::Default, - }, - notes: ingestion.notes, - stomach_fullness: ingestion.stomach_fullness, - }) + for ingestion in experience.ingestions.iter() { + ingestions.push(Ingestion::from(ingestion.clone())) } self.ingestions.insert(experience_id, ingestions); @@ -151,10 +78,7 @@ impl Journal for InMemJournal { experience_id, Experience { id: experience_id, - title: experience.title, - text: experience.text, - creation_time: from_unix_millis(experience.creation_time), - modified_time: from_unix_millis(experience.modified_time), + ..Experience::from(experience) }, ); } diff --git a/journal/src/lib.rs b/journal/src/lib.rs index 48e324e..f158672 100644 --- a/journal/src/lib.rs +++ b/journal/src/lib.rs @@ -1,3 +1,2 @@ -pub mod helpers; pub mod journal; pub mod types; diff --git a/journal/src/types/consumer.rs b/journal/src/types/consumer.rs index d71d541..d719c9d 100644 --- a/journal/src/types/consumer.rs +++ b/journal/src/types/consumer.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, str::FromStr}; +use std::fmt::Display; #[derive(PartialEq, Debug, Clone)] pub enum Consumer { @@ -6,23 +6,49 @@ pub enum Consumer { Named(String), } -impl FromStr for Consumer { - type Err = core::convert::Infallible; - - fn from_str(s: &str) -> Result { - if s == "default" { - Ok(Consumer::Default) - } else { - Ok(Consumer::Named(s.to_string())) - } +impl Default for Consumer { + fn default() -> Self { + Self::Default } } impl Display for Consumer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Consumer::Default => f.write_str("default"), - Consumer::Named(name) => f.write_str(name), + Self::Default => f.write_str("default"), + Self::Named(name) => f.write_str(name), } } } + +impl From<&str> for Consumer { + fn from(value: &str) -> Self { + if value == "default" { + Self::Default + } else { + Self::Named(value.to_string()) + } + } +} + +#[cfg(test)] +mod tests { + use super::Consumer::{self, *}; + + #[test] + fn default() { + assert_eq!(Consumer::default(), Default); + } + + #[test] + fn to_string() { + assert_eq!(Default.to_string(), "default"); + assert_eq!(Consumer::from("name").to_string(), "name"); + } + + #[test] + fn from_string() { + assert_eq!(Consumer::from("default"), Default); + assert_eq!(Consumer::from("name"), Named("name".to_string())); + } +} diff --git a/journal/src/types/custom_unit.rs b/journal/src/types/custom_unit.rs index f399f50..12aea3e 100644 --- a/journal/src/types/custom_unit.rs +++ b/journal/src/types/custom_unit.rs @@ -1,15 +1,102 @@ -use crate::types::{doses::CustomUnitDose, AdministrationRoute}; +use super::{from_unix_millis, AdministrationRoute, Dose, Estimation, StandardDeviation}; use chrono::{DateTime, Utc}; -#[derive(Debug, Clone)] +#[derive(Default, PartialEq, Debug, Clone)] pub struct CustomUnit { pub id: i64, + pub name: String, pub substance_name: String, + pub unit: String, + pub original_unit: String, + pub dose: Option, pub administration_route: AdministrationRoute, - pub dose: CustomUnitDose, pub creation_time: DateTime, pub is_archived: bool, } + +impl From for CustomUnit { + fn from(custom_unit: psychonaut_journal_types::CustomUnit) -> Self { + CustomUnit { + id: custom_unit.id, + name: custom_unit.name, + substance_name: custom_unit.substance_name, + unit: custom_unit.unit, + original_unit: custom_unit.original_unit, + + dose: match custom_unit.dose { + Some(dose) => Some(Dose { + value: dose, + contains_unknown: false, + estimation: { + if custom_unit.is_estimate { + if let Some(deviation) = custom_unit.estimate_standard_deviation { + Estimation::StandardDeviation(StandardDeviation { + expectation: custom_unit.dose.unwrap_or_default(), + deviation, + }) + } else { + Estimation::Estimate + } + } else { + Estimation::Precise + } + }, + }), + None => None, + }, + + administration_route: custom_unit.administration_route, + + creation_time: from_unix_millis(custom_unit.creation_time), + is_archived: custom_unit.is_archived, + } + } +} + +#[cfg(test)] +mod tests { + use crate::types::{from_unix_millis, Dose, StandardDeviation}; + + use super::{AdministrationRoute, CustomUnit}; + use psychonaut_journal_types::CustomUnit as PsychonautCustomUnit; + + #[test] + fn conversion() { + assert_eq!( + CustomUnit::from(PsychonautCustomUnit { + id: 0, + substance_name: "Caffeine".to_string(), + name: "Beverage".to_string(), + creation_time: 0, + administration_route: AdministrationRoute::Oral, + dose: Some(10.0), + unit: "sip".to_string(), + original_unit: "mg".to_string(), + is_estimate: true, + estimate_standard_deviation: Some(10.0), + is_archived: false + }), + CustomUnit { + id: 0, + name: "Beverage".to_string(), + substance_name: "Caffeine".to_string(), + unit: "sip".to_string(), + original_unit: "mg".to_string(), + dose: Some(Dose { + value: 10.0, + contains_unknown: false, + estimation: crate::types::Estimation::StandardDeviation(StandardDeviation { + expectation: 10.0, + deviation: 10.0 + }) + }), + administration_route: AdministrationRoute::Oral, + creation_time: from_unix_millis(0), + is_archived: false + } + ); + } +} diff --git a/journal/src/types/dose.rs b/journal/src/types/dose.rs new file mode 100644 index 0000000..312fa09 --- /dev/null +++ b/journal/src/types/dose.rs @@ -0,0 +1,229 @@ +use std::ops::{Add, Mul}; + +use super::Estimation; + +#[derive(Default, PartialEq, Debug, Clone)] +pub struct Dose { + pub value: f64, + pub contains_unknown: bool, + pub estimation: Estimation, +} + +impl Add for Dose { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + self + &rhs + } +} + +impl Add for &Dose { + type Output = Dose; + + fn add(self, rhs: Self) -> Self::Output { + self.clone() + rhs + } +} + +impl Add for &Dose { + type Output = Dose; + + fn add(self, lhs: Dose) -> Self::Output { + lhs + self + } +} + +impl Add<&Dose> for Dose { + type Output = Self; + + fn add(self, rhs: &Self) -> Self::Output { + let contains_unknown = self.contains_unknown || rhs.contains_unknown; + let estimation = match self.estimation + &rhs.estimation { + Estimation::Precise => { + if contains_unknown { + Estimation::Estimate + } else { + Estimation::Precise + } + } + value => value, + }; + + Dose { + value: self.value + rhs.value, + contains_unknown, + estimation, + } + } +} + +impl Mul for Dose { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + self * &rhs + } +} + +impl Mul for &Dose { + type Output = Dose; + + fn mul(self, rhs: Self) -> Self::Output { + self.clone() * rhs + } +} + +impl Mul for &Dose { + type Output = Dose; + + fn mul(self, lhs: Dose) -> Self::Output { + lhs * self + } +} + +impl Mul<&Dose> for Dose { + type Output = Self; + + fn mul(self, rhs: &Self) -> Self::Output { + let contains_unknown = self.contains_unknown || rhs.contains_unknown; + let estimation = match self.estimation * &rhs.estimation { + Estimation::Precise => { + if contains_unknown { + Estimation::Estimate + } else { + Estimation::Precise + } + } + value => value, + }; + + Dose { + value: self.value * rhs.value, + contains_unknown, + estimation, + } + } +} + +#[cfg(test)] +mod tests { + use super::{Dose, Estimation}; + + #[test] + fn add() { + let lhs = Dose { + value: 10.0, + ..Dose::default() + }; + let rhs = Dose { + value: 10.0, + ..Dose::default() + }; + let result = Dose { + value: 20.0, + ..Dose::default() + }; + + assert_eq!(lhs.clone() + rhs.clone(), result); + assert_eq!(lhs.clone() + &rhs.clone(), result); + assert_eq!(&lhs.clone() + rhs.clone(), result); + assert_eq!(&lhs.clone() + &rhs.clone(), result); + } + + #[test] + fn add_with_estimate() { + assert_eq!( + Dose { + value: 10.0, + ..Dose::default() + } + Dose { + value: 10.0, + estimation: Estimation::Estimate, + ..Dose::default() + }, + Dose { + value: 20.0, + estimation: Estimation::Estimate, + ..Dose::default() + } + ); + } + + #[test] + fn add_with_unknown() { + assert_eq!( + Dose { + value: 10.0, + ..Dose::default() + } + Dose { + value: 10.0, + contains_unknown: true, + ..Dose::default() + }, + Dose { + value: 20.0, + contains_unknown: true, + estimation: Estimation::Estimate + } + ); + } + + #[test] + fn mul() { + let lhs = Dose { + value: 10.0, + ..Dose::default() + }; + let rhs = Dose { + value: 10.0, + ..Dose::default() + }; + let result = Dose { + value: 100.0, + ..Dose::default() + }; + + assert_eq!(lhs.clone() * rhs.clone(), result); + assert_eq!(lhs.clone() * &rhs.clone(), result); + assert_eq!(&lhs.clone() * rhs.clone(), result); + assert_eq!(&lhs.clone() * &rhs.clone(), result); + } + + #[test] + fn mul_with_estimate() { + assert_eq!( + Dose { + value: 10.0, + ..Dose::default() + } * Dose { + value: 10.0, + estimation: Estimation::Estimate, + ..Dose::default() + }, + Dose { + value: 100.0, + estimation: Estimation::Estimate, + ..Dose::default() + } + ); + } + + #[test] + fn mul_with_unknown() { + assert_eq!( + Dose { + value: 10.0, + ..Dose::default() + } * Dose { + value: 10.0, + contains_unknown: true, + ..Dose::default() + }, + Dose { + value: 100.0, + contains_unknown: true, + estimation: Estimation::Estimate + } + ); + } +} diff --git a/journal/src/types/doses.rs b/journal/src/types/doses.rs deleted file mode 100644 index 1eac045..0000000 --- a/journal/src/types/doses.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::ops::{Add, Mul}; - -use crate::types::Estimation; - -#[derive(Debug, Clone)] -pub enum Dose { - Unknown(UnknownDose), - Standard(StandardDose), - CustomUnit(CustomUnitDose), -} - -#[derive(Debug, Clone)] -pub struct UnknownDose { - pub unit: String, -} - -#[derive(Debug, Clone)] -pub struct StandardDose { - pub dose: f64, - pub unit: String, - pub contains_unknown: bool, - pub estimation: Estimation, -} - -#[derive(Debug, Clone)] -pub struct CustomUnitDose { - pub dose: f64, - pub unit: String, - pub original_unit: String, - pub estimation: Estimation, - pub custom_unit_id: i64, -} - -impl Add for StandardDose { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - StandardDose { - dose: self.dose + rhs.dose, - unit: self.unit, - contains_unknown: self.contains_unknown || rhs.contains_unknown, - estimation: self.estimation + rhs.estimation, - } - } -} - -impl Mul for StandardDose { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - StandardDose { - dose: self.dose * rhs.dose, - unit: self.unit, - contains_unknown: self.contains_unknown || rhs.contains_unknown, - estimation: { - let estimation = self.estimation + rhs.estimation; - if let Estimation::StandardDeviation(deviation) = estimation { - Estimation::StandardDeviation(deviation * rhs.dose) - } else { - estimation - } - }, - } - } -} - -impl From for StandardDose { - fn from(value: CustomUnitDose) -> StandardDose { - StandardDose { - dose: value.dose, - unit: value.original_unit, - contains_unknown: false, - estimation: value.estimation, - } - } -} diff --git a/journal/src/types/estimate.rs b/journal/src/types/estimate.rs index d0d502a..eb1a606 100644 --- a/journal/src/types/estimate.rs +++ b/journal/src/types/estimate.rs @@ -4,7 +4,19 @@ use std::ops::{Add, Mul}; pub enum Estimation { Precise, Estimate, - StandardDeviation(f64), + StandardDeviation(StandardDeviation), +} + +impl Default for Estimation { + fn default() -> Self { + Estimation::Precise + } +} + +#[derive(PartialEq, Default, Debug, Copy, Clone)] +pub struct StandardDeviation { + pub expectation: f64, + pub deviation: f64, } impl Estimation { @@ -19,16 +31,22 @@ impl Estimation { } } -impl Add for Estimation { +impl Add<&Estimation> for Estimation { type Output = Self; - fn add(self, rhs: Self) -> Self::Output { + fn add(self, rhs: &Self) -> Self::Output { match (self, rhs) { - (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { - Estimation::StandardDeviation(x + y) + (Estimation::StandardDeviation(lhs), Estimation::StandardDeviation(rhs)) => { + Estimation::StandardDeviation(StandardDeviation { + expectation: lhs.expectation + rhs.expectation, + deviation: lhs.deviation + rhs.deviation, + }) } - (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { - Estimation::StandardDeviation(x) + (Estimation::StandardDeviation(deviation), _) => { + Estimation::StandardDeviation(deviation) + } + (_, Estimation::StandardDeviation(deviation)) => { + Estimation::StandardDeviation(deviation.clone()) } (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, (Estimation::Precise, Estimation::Precise) => Estimation::Precise, @@ -36,19 +54,124 @@ impl Add for Estimation { } } -impl Mul for Estimation { +impl Mul<&Estimation> for Estimation { type Output = Self; - fn mul(self, rhs: Self) -> Self::Output { + fn mul(self, rhs: &Self) -> Self::Output { match (self, rhs) { - (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { - Estimation::StandardDeviation(x * y) + (Estimation::StandardDeviation(lhs), Estimation::StandardDeviation(rhs)) => { + let lhs_sum = lhs.deviation.powi(2) + lhs.expectation.powi(2); + let rhs_sum = rhs.deviation.powi(2) + rhs.expectation.powi(2); + + let expectations = lhs.expectation.powi(2) * rhs.expectation.powi(2); + + let product_variance = (lhs_sum * rhs_sum) - expectations; + + if product_variance > 0.0000001 { + let deviation = product_variance.sqrt(); + let expectation = lhs.expectation * rhs.expectation; + + Estimation::StandardDeviation(StandardDeviation { + expectation, + deviation: deviation.round(), + }) + } else { + Estimation::Estimate + } } - (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { - Estimation::StandardDeviation(x) + (Estimation::StandardDeviation(deviation), _) => { + Estimation::StandardDeviation(deviation) + } + (_, Estimation::StandardDeviation(deviation)) => { + Estimation::StandardDeviation(deviation.clone()) } (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, (Estimation::Precise, Estimation::Precise) => Estimation::Precise, } } } + +#[cfg(test)] +mod tests { + use super::{Estimation::*, StandardDeviation as Deviation}; + + #[test] + fn is() { + let deviation = StandardDeviation(Deviation::default()); + + assert_eq!(Precise.is_precise(), true); + assert_eq!(Precise.is_estimate(), false); + assert_eq!(Precise.is_standard_deviation(), false); + + assert_eq!(Estimate.is_precise(), false); + assert_eq!(Estimate.is_estimate(), true); + assert_eq!(Estimate.is_standard_deviation(), false); + + assert_eq!(deviation.is_precise(), false); + assert_eq!(deviation.is_estimate(), true); + assert_eq!(deviation.is_standard_deviation(), true); + } + + #[test] + fn add() { + let deviation = StandardDeviation(Deviation { + expectation: 10.0, + deviation: 10.0, + }); + + assert_eq!(Precise + &Precise, Precise); + assert_eq!(Precise + &Estimate, Estimate); + assert_eq!(Precise + &deviation, deviation); + + assert_eq!(Estimate + &Precise, Estimate); + assert_eq!(Estimate + &Estimate, Estimate); + assert_eq!(Estimate + &deviation, deviation); + + assert_eq!(deviation + &Precise, deviation); + assert_eq!(deviation + &Estimate, deviation); + assert_eq!( + deviation + &deviation, + StandardDeviation(Deviation { + expectation: 20.0, + deviation: 20.0, + }) + ); + } + + #[test] + fn mul() { + let deviation = StandardDeviation(Deviation { + expectation: 10.0, + deviation: 10.0, + }); + + assert_eq!(Precise * &Precise, Precise); + assert_eq!(Precise * &Estimate, Estimate); + assert_eq!(Precise * &deviation, deviation); + + assert_eq!(Estimate * &Precise, Estimate); + assert_eq!(Estimate * &Estimate, Estimate); + assert_eq!(Estimate * &deviation, deviation); + + assert_eq!(deviation * &Precise, deviation); + assert_eq!(deviation * &Estimate, deviation); + assert_eq!( + deviation * &deviation, + StandardDeviation(Deviation { + expectation: 100.0, + deviation: 173.0, + }) + ); + + assert_eq!( + StandardDeviation(Deviation { + expectation: 100.0, + deviation: 0.0, + }) * &StandardDeviation(Deviation { + expectation: 100.0, + deviation: 0.0, + }), + Estimate + ) + } +} diff --git a/journal/src/types/experience.rs b/journal/src/types/experience.rs index 482fd56..c3650a9 100644 --- a/journal/src/types/experience.rs +++ b/journal/src/types/experience.rs @@ -1,6 +1,8 @@ use chrono::{DateTime, Utc}; -#[derive(Debug, Clone)] +use super::from_unix_millis; + +#[derive(PartialEq, Debug, Clone)] pub struct Experience { pub id: i64, pub title: String, @@ -8,3 +10,43 @@ pub struct Experience { pub creation_time: DateTime, pub modified_time: DateTime, } + +impl From for Experience { + fn from(experience: psychonaut_journal_types::Experience) -> Self { + Experience { + id: 0, + title: experience.title, + text: experience.text, + creation_time: from_unix_millis(experience.creation_time), + modified_time: from_unix_millis(experience.modified_time), + } + } +} + +#[cfg(test)] +mod tests { + use crate::types::from_unix_millis; + + use super::Experience; + use psychonaut_journal_types::Experience as PsychonautExperience; + + #[test] + fn conversion() { + assert_eq!( + Experience::from(PsychonautExperience { + title: "Experience".to_string(), + text: "Some Text".to_string(), + creation_time: 0, + modified_time: 0, + ingestions: vec![] + }), + Experience { + id: 0, + title: "Experience".to_string(), + text: "Some Text".to_string(), + creation_time: from_unix_millis(0), + modified_time: from_unix_millis(0) + } + ); + } +} diff --git a/journal/src/types/export.rs b/journal/src/types/export.rs index c57749c..00b636f 100644 --- a/journal/src/types/export.rs +++ b/journal/src/types/export.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::types::{CustomUnit, Experience, Ingestion}; +use super::{CustomUnit, Experience, Ingestion}; #[derive(Debug, Default, Clone)] pub struct ExportFormat { diff --git a/journal/src/types/ingestion.rs b/journal/src/types/ingestion.rs index 446eddb..638dfcd 100644 --- a/journal/src/types/ingestion.rs +++ b/journal/src/types/ingestion.rs @@ -1,15 +1,107 @@ use chrono::{DateTime, Utc}; -use crate::types::{doses::Dose, AdministrationRoute, Consumer}; +use super::{ + dose::Dose, from_unix_millis, AdministrationRoute, Consumer, Estimation, StandardDeviation, + Unit, +}; -#[derive(Debug, Clone)] +#[derive(PartialEq, Debug, Clone)] pub struct Ingestion { pub substance_name: String, pub ingestion_time: DateTime, pub creation_time: DateTime, - pub dose: Dose, + pub dose: Option, + pub unit: Unit, pub roa: AdministrationRoute, pub consumer: Consumer, pub notes: String, pub stomach_fullness: Option, } + +impl From for Ingestion { + fn from(ingestion: psychonaut_journal_types::Ingestion) -> Self { + Ingestion { + substance_name: ingestion.substance_name, + ingestion_time: from_unix_millis(ingestion.ingestion_time), + creation_time: from_unix_millis(ingestion.creation_time), + + dose: match ingestion.dose { + Some(value) => Some(Dose { + value, + contains_unknown: false, + estimation: { + if !ingestion.is_estimate { + Estimation::Precise + } else if let Some(deviation) = ingestion.estimate_standard_deviation { + Estimation::StandardDeviation(StandardDeviation { + expectation: ingestion.dose.unwrap_or_default(), + deviation, + }) + } else { + Estimation::Estimate + } + }, + }), + None => None, + }, + + unit: match ingestion.custom_unit_id { + Some(id) => Unit::Custom { id, unit: None }, + None => Unit::Simple(ingestion.unit), + }, + + roa: ingestion.roa, + + consumer: match ingestion.consumer_name { + Some(name) => Consumer::Named(name), + None => Consumer::Default, + }, + + notes: ingestion.notes, + stomach_fullness: ingestion.stomach_fullness, + } + } +} + +#[cfg(test)] +mod tests { + use crate::types::{from_unix_millis, AdministrationRoute, Consumer, Dose, Estimation, Unit}; + + use super::Ingestion; + use psychonaut_journal_types::Ingestion as PsychonautIngestion; + + #[test] + fn conversion() { + assert_eq!( + Ingestion::from(PsychonautIngestion { + substance_name: "Caffeine".to_string(), + ingestion_time: 0, + creation_time: 0, + dose: Some(10.0), + unit: "mg".to_string(), + is_estimate: false, + estimate_standard_deviation: None, + custom_unit_id: None, + roa: AdministrationRoute::Oral, + consumer_name: None, + notes: "".to_string(), + stomach_fullness: None, + }), + Ingestion { + substance_name: "Caffeine".to_string(), + ingestion_time: from_unix_millis(0), + creation_time: from_unix_millis(0), + dose: Some(Dose { + value: 10.0, + contains_unknown: false, + estimation: Estimation::Precise + }), + unit: Unit::Simple("mg".to_string()), + roa: AdministrationRoute::Oral, + consumer: Consumer::Default, + notes: "".to_string(), + stomach_fullness: None + } + ); + } +} diff --git a/journal/src/types/mod.rs b/journal/src/types/mod.rs index 1d356cf..2dd8a50 100644 --- a/journal/src/types/mod.rs +++ b/journal/src/types/mod.rs @@ -1,7 +1,9 @@ mod estimate; -pub use estimate::Estimation; +use chrono::{DateTime, TimeZone, Utc}; +pub use estimate::{Estimation, StandardDeviation}; -pub mod doses; +mod dose; +pub use dose::Dose; mod consumer; pub use consumer::Consumer; @@ -21,4 +23,11 @@ pub use custom_unit::CustomUnit; mod export; pub use export::ExportFormat; +mod unit; +pub use unit::Unit; + pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; + +pub(crate) fn from_unix_millis(time: u64) -> DateTime { + Utc.timestamp_millis_opt(time as i64).unwrap() +} diff --git a/journal/src/types/unit.rs b/journal/src/types/unit.rs new file mode 100644 index 0000000..0b30f77 --- /dev/null +++ b/journal/src/types/unit.rs @@ -0,0 +1,7 @@ +use super::CustomUnit; + +#[derive(PartialEq, Debug, Clone)] +pub enum Unit { + Simple(String), + Custom { id: i64, unit: Option }, +} diff --git a/journal_cli/Cargo.toml b/journal_cli/Cargo.toml index f4028d3..bccb28d 100644 --- a/journal_cli/Cargo.toml +++ b/journal_cli/Cargo.toml @@ -13,8 +13,8 @@ psychonaut_journal_types = { path = "../psychonaut_journal_types" } chrono = { version = "0.4.38", features = ["serde"] } clap = { version = "4.5.21", features = ["derive", "env"] } -log = { version = "0.4.22", features = ["std", "serde"] } -prettytable-rs = "0.10.0" +#log = { version = "0.4.22", features = ["std", "serde"] } +#prettytable-rs = "0.10.0" serde = { version = "1.0.215", features = ["std", "derive", "serde_derive"] } serde_json = "1.0.132" #serde_with = "3.11.0" diff --git a/journal_cli/src/args.rs b/journal_cli/src/args.rs index a6dae2f..6db9436 100644 --- a/journal_cli/src/args.rs +++ b/journal_cli/src/args.rs @@ -28,5 +28,5 @@ pub struct Args { pub command: Commands, #[command(flatten)] - pub journal_location: Option, + pub journal_location: JournalLocationArgs, } diff --git a/journal_cli/src/commands/print_experience.rs b/journal_cli/src/commands/print_experience.rs index 4b48fe0..6da5ac6 100644 --- a/journal_cli/src/commands/print_experience.rs +++ b/journal_cli/src/commands/print_experience.rs @@ -1,11 +1,9 @@ -use std::str::FromStr; - use journal::types::Consumer; -use crate::args::Args; -use crate::display::print_ingestion_log; -use crate::formatting::format_experience_title; -use crate::utils::load_journal; +use crate::{ + args::Args, display::print_ingestion_log, formatting::format_experience_title, + utils::load_journal, +}; #[derive(Debug, Clone, clap::Args)] #[group(requires = "journal_location")] @@ -23,12 +21,12 @@ pub fn parse_consumer_filter(consumer_filter: Option>) -> Option { let mut consumers: Vec = Vec::new(); for consumer in consumer_filter.into_iter() { - let mut consumer_name = consumer.as_str(); - if consumer_name.is_empty() { - consumer_name = "default"; + let mut consumer = consumer; + if consumer.is_empty() { + consumer = "default".to_string(); } - consumers.push(Consumer::from_str(consumer_name).unwrap()); + consumers.push(Consumer::from(consumer)); } Some(consumers) } @@ -43,8 +41,6 @@ pub fn print_experience( let journal = load_journal( &global_args .journal_location - .clone() - .unwrap() .psychonaut_export_file .clone() .unwrap(), diff --git a/journal_cli/src/display.rs b/journal_cli/src/display.rs index be3a2cf..3403d71 100644 --- a/journal_cli/src/display.rs +++ b/journal_cli/src/display.rs @@ -28,14 +28,14 @@ pub fn print_ingestion_log( } } - let custom_unit = journal.maybe_custom_unit(ingestion); + let unit = journal.resolve_unit(&ingestion.unit); // println!("{:#?} {:#?}", &ingestion, &custom_unit); println!( "{}|{}|{}|{}|{}", ingestion.substance_name, - format_ingestion_dose(&ingestion.dose, custom_unit.map(|f| &f.dose)), - format_ingestion_roa(ingestion, custom_unit), + format_ingestion_dose(ingestion.dose.as_ref(), &unit), + format_ingestion_roa(ingestion, &unit), ingestion.consumer, format_ingestion_time(ingestion) ) diff --git a/journal_cli/src/formatting.rs b/journal_cli/src/formatting.rs index a46522a..6824b28 100644 --- a/journal_cli/src/formatting.rs +++ b/journal_cli/src/formatting.rs @@ -1,58 +1,59 @@ -use journal::{ - helpers::calulate_custom_unit_dose, - types::{ - doses::{CustomUnitDose, Dose, StandardDose}, - CustomUnit, Estimation, Experience, Ingestion, - }, -}; +use journal::types::{Dose, Estimation, Experience, Ingestion, Unit}; pub fn format_experience_title(experience: &Experience) -> String { format!("{}: {}", experience.title, experience.creation_time) } -pub fn format_ingestion_dose(dose: &Dose, custom_unit_dose: Option<&CustomUnitDose>) -> String { +pub fn format_ingestion_dose(dose: Option<&Dose>, unit: &Unit) -> String { match dose { - Dose::Unknown(dose) => format!("Unknown {}", dose.unit), - Dose::Standard(dose) => { - let is_estimate = dose.estimation.is_estimate(); + Some(dose) => match unit { + Unit::Simple(unit) => { + let is_estimate = dose.estimation.is_estimate(); - let estimate = if is_estimate { "~" } else { "" }; - let standard_deviation = { - if let Estimation::StandardDeviation(standard_deviation) = dose.estimation { - format!("±{}", (standard_deviation * 100.0).round() / 100.0) + let estimate = if is_estimate { "~" } else { "" }; + let standard_deviation = { + if let Estimation::StandardDeviation(deviation) = dose.estimation { + format!("±{}", (deviation.deviation * 100.0).round() / 100.0) + } else { + "".to_string() + } + }; + let unknown = if dose.contains_unknown { + "+Unknown" } else { - "".to_string() - } - }; - let dose_value = (dose.dose * 100.0).round() / 100.0; - let unit = dose.unit.clone(); + "" + }; + let dose = (dose.value * 100.0).round() / 100.0; - format!("{estimate}{dose_value}{standard_deviation} {unit}") - } - Dose::CustomUnit(dose) => { - let custom_unit_dose = custom_unit_dose.expect("custom unit dose required"); + format!("{estimate}{dose}{standard_deviation}{unknown} {unit}") + } + Unit::Custom { id: _id, unit } => { + let unit = unit.clone().unwrap(); + let unit_unit = Unit::Simple(unit.unit.clone()); - let ingestion_dose = calulate_custom_unit_dose(dose, custom_unit_dose); + let ingestion_dose = match &unit.dose { + Some(unit_dose) => dose * unit_dose, + None => dose.clone(), + }; - let ingestion_dose = format_ingestion_dose(&Dose::Standard(ingestion_dose), None); + let ingestion_unit = Unit::Simple(unit.original_unit.clone()); - let dose_per_unit = format_ingestion_dose( - &Dose::Standard(StandardDose::from(custom_unit_dose.clone())), - None, - ); + let ingestion_dose = format_ingestion_dose(Some(&ingestion_dose), &ingestion_unit); - let custom_unit_dose = format_ingestion_dose( - &Dose::Standard(StandardDose { - dose: dose.dose, - unit: custom_unit_dose.unit.clone(), - contains_unknown: false, - estimation: dose.estimation, - }), - None, - ); + let dose_per_unit = format_ingestion_dose(unit.dose.as_ref(), &ingestion_unit); - format!("{ingestion_dose} ({dose_per_unit} * {custom_unit_dose})") - } + let custom_unit_dose = format_ingestion_dose(Some(&dose), &unit_unit); + + format!("{ingestion_dose} ({dose_per_unit} * {custom_unit_dose})") + } + }, + None => format!( + "Unknown {}", + match unit { + Unit::Simple(unit) => unit, + Unit::Custom { id: _id, unit } => &unit.as_ref().unwrap().original_unit, + } + ), } } @@ -60,10 +61,38 @@ pub fn format_ingestion_time(ingestion: &Ingestion) -> String { ingestion.ingestion_time.format("%a %I:%M %p").to_string() } -pub fn format_ingestion_roa(ingestion: &Ingestion, custom_unit: Option<&CustomUnit>) -> String { - if let Some(custom_unit) = custom_unit { - format!("{:?} ({})", ingestion.roa, custom_unit.name) +pub fn format_ingestion_roa(ingestion: &Ingestion, unit: &Unit) -> String { + if let Unit::Custom { id: _id, unit } = unit { + format!("{:?} ({})", ingestion.roa, &unit.as_ref().unwrap().name) } else { format!("{:?}", ingestion.roa) } } + +#[cfg(test)] +mod tests { + use journal::types::CustomUnit; + + use super::*; + + #[test] + fn format_unknown_dose() { + let result = format_ingestion_dose(None, &Unit::Simple("mg".to_string())); + assert_eq!(result, "Unknown mg"); + } + + #[test] + fn format_unknown_dose_custom_unit() { + let result = format_ingestion_dose( + None, + &Unit::Custom { + id: 0, + unit: Some(CustomUnit { + original_unit: "mg".to_string(), + ..CustomUnit::default() + }), + }, + ); + assert_eq!(result, "Unknown mg"); + } +} diff --git a/psychonaut_journal_types/Cargo.toml b/psychonaut_journal_types/Cargo.toml index 52a5957..e9eae11 100644 --- a/psychonaut_journal_types/Cargo.toml +++ b/psychonaut_journal_types/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -serde = { version = "1.0.215", features = ["std", "derive", "serde_derive"] } +serde = { version = "1.0.215", features = ["derive"] } diff --git a/psychonaut_journal_types/src/lib.rs b/psychonaut_journal_types/src/lib.rs index af5a8a6..8554be4 100644 --- a/psychonaut_journal_types/src/lib.rs +++ b/psychonaut_journal_types/src/lib.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::fmt::Display; +use std::fmt::{Debug, Display}; -/// Some of the fields are renamed to make more clear what it is +mod tests; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[serde(rename_all = "UPPERCASE")] pub enum AdministrationRoute { Oral, @@ -20,9 +19,27 @@ pub enum AdministrationRoute { Inhaled, } +impl Default for AdministrationRoute { + fn default() -> Self { + Self::Oral + } +} + impl Display for AdministrationRoute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + match *self { + Self::Oral => f.write_str("Oral"), + Self::Sublingual => f.write_str("Sublingual"), + Self::Buccal => f.write_str("Buccal"), + Self::Insufflated => f.write_str("Insufflated"), + Self::Rectal => f.write_str("Rectal"), + Self::Transdermal => f.write_str("Transdermal"), + Self::Subcutaneous => f.write_str("Subcutaneous"), + Self::Intramuscular => f.write_str("Intramuscular"), + Self::Intravenous => f.write_str("Intravenous"), + Self::Smoked => f.write_str("Smoked"), + Self::Inhaled => f.write_str("Inhaled"), + } } } @@ -85,7 +102,7 @@ pub struct CustomUnit { #[serde(rename = "creationDate")] pub creation_time: u64, pub administration_route: AdministrationRoute, - pub dose: f64, + pub dose: Option, pub unit: String, pub original_unit: String, pub is_estimate: bool, diff --git a/psychonaut_journal_types/src/tests.rs b/psychonaut_journal_types/src/tests.rs new file mode 100644 index 0000000..7f6e481 --- /dev/null +++ b/psychonaut_journal_types/src/tests.rs @@ -0,0 +1,24 @@ +#[cfg(test)] +mod tests { + use crate::AdministrationRoute::{self, *}; + + #[test] + fn default_administration_dose() { + assert_eq!(AdministrationRoute::default(), AdministrationRoute::Oral); + } + + #[test] + fn administration_dose_to_string() { + assert_eq!(Oral.to_string(), "Oral"); + assert_eq!(Sublingual.to_string(), "Sublingual"); + assert_eq!(Buccal.to_string(), "Buccal"); + assert_eq!(Insufflated.to_string(), "Insufflated"); + assert_eq!(Rectal.to_string(), "Rectal"); + assert_eq!(Transdermal.to_string(), "Transdermal"); + assert_eq!(Subcutaneous.to_string(), "Subcutaneous"); + assert_eq!(Intramuscular.to_string(), "Intramuscular"); + assert_eq!(Intravenous.to_string(), "Intravenous"); + assert_eq!(Smoked.to_string(), "Smoked"); + assert_eq!(Inhaled.to_string(), "Inhaled"); + } +}