diff --git a/fitnesstrax/app/src/types.rs b/fitnesstrax/app/src/types.rs index 4af25c1..f017596 100644 --- a/fitnesstrax/app/src/types.rs +++ b/fitnesstrax/app/src/types.rs @@ -1,7 +1,7 @@ use chrono::{Local, NaiveDate}; use dimensioned::si; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ParseError; // This interval doesn't feel right, either. The idea that I have a specific interval type for just @@ -102,65 +102,58 @@ impl From> for Weight { } #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] -pub struct Distance { - value: si::Meter, -} +pub struct Distance(si::Meter); impl Distance { pub fn format(&self, option: FormatOption) -> String { match option { - FormatOption::Abbreviated => format!("{} km", self.value.value_unsafe / 1000.), - FormatOption::Full => format!("{} kilometers", self.value.value_unsafe / 1000.), + FormatOption::Abbreviated => format!("{} km", self.0.value_unsafe / 1000.), + FormatOption::Full => format!("{} kilometers", self.0.value_unsafe / 1000.), } } pub fn parse(s: &str) -> Result { - let digits = take_digits(s.to_owned()); - let value = digits.parse::().map_err(|_| ParseError)?; - Ok(Distance { - value: value * 1000. * si::M, - }) + let value = s.parse::().map_err(|_| ParseError)?; + Ok(Distance(value * 1000. * si::M)) } } impl std::ops::Add for Distance { type Output = Distance; fn add(self, rside: Self) -> Self::Output { - Self::Output::from(self.value + rside.value) + Self::Output::from(self.0 + rside.0) } } impl std::ops::Sub for Distance { type Output = Distance; fn sub(self, rside: Self) -> Self::Output { - Self::Output::from(self.value - rside.value) + Self::Output::from(self.0 - rside.0) } } impl std::ops::Deref for Distance { type Target = si::Meter; fn deref(&self) -> &Self::Target { - &self.value + &self.0 } } impl From> for Distance { fn from(value: si::Meter) -> Self { - Self { value } + Self(value) } } #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] -pub struct Duration { - value: si::Second, -} +pub struct Duration(si::Second); impl Duration { pub fn format(&self, option: FormatOption) -> String { let (hours, minutes) = self.hours_and_minutes(); let (h, m) = match option { FormatOption::Abbreviated => ("h", "m"), - FormatOption::Full => ("hours", "minutes"), + FormatOption::Full => (" hours", " minutes"), }; if hours > 0 { format!("{}{} {}{}", hours, h, minutes, m) @@ -170,15 +163,12 @@ impl Duration { } pub fn parse(s: &str) -> Result { - let digits = take_digits(s.to_owned()); - let value = digits.parse::().map_err(|_| ParseError)?; - Ok(Duration { - value: value * 60. * si::S, - }) + let value = s.parse::().map_err(|_| ParseError)?; + Ok(Duration(value * 60. * si::S)) } fn hours_and_minutes(&self) -> (i64, i64) { - let minutes: i64 = (self.value.value_unsafe / 60.).round() as i64; + let minutes: i64 = (self.0.value_unsafe / 60.).round() as i64; let hours: i64 = minutes / 60; let minutes = minutes - (hours * 60); (hours, minutes) @@ -188,32 +178,94 @@ impl Duration { impl std::ops::Add for Duration { type Output = Duration; fn add(self, rside: Self) -> Self::Output { - Self::Output::from(self.value + rside.value) + Self::Output::from(self.0 + rside.0) } } impl std::ops::Sub for Duration { type Output = Duration; fn sub(self, rside: Self) -> Self::Output { - Self::Output::from(self.value - rside.value) + Self::Output::from(self.0 - rside.0) } } impl std::ops::Deref for Duration { type Target = si::Second; fn deref(&self) -> &Self::Target { - &self.value + &self.0 } } impl From> for Duration { fn from(value: si::Second) -> Self { - Self { value } + Self(value) } } +/* fn take_digits(s: String) -> String { s.chars() .take_while(|t| t.is_ascii_digit()) .collect::() } +*/ + +#[cfg(test)] +mod test { + use super::*; + use dimensioned::si; + + #[test] + fn it_parses_weight_values() { + assert_eq!(Weight::parse("15.3"), Ok(Weight(15.3 * si::KG))); + assert_eq!(Weight::parse("15.ab"), Err(ParseError)); + } + + #[test] + fn it_formats_weight_values() { + assert_eq!( + Weight::from(15.3 * si::KG).format(FormatOption::Abbreviated), + "15.3 kg" + ); + assert_eq!( + Weight::from(15.3 * si::KG).format(FormatOption::Full), + "15.3 kilograms" + ); + } + + #[test] + fn it_parses_distance_values() { + assert_eq!(Distance::parse("70"), Ok(Distance(70000. * si::M))); + assert_eq!(Distance::parse("15.ab"), Err(ParseError)); + } + + #[test] + fn it_formats_distance_values() { + assert_eq!( + Distance::from(70000. * si::M).format(FormatOption::Abbreviated), + "70 km" + ); + assert_eq!( + Distance::from(70000. * si::M).format(FormatOption::Full), + "70 kilometers" + ); + } + + #[test] + fn it_parses_duration_values() { + assert_eq!(Duration::parse("70"), Ok(Duration(4200. * si::S))); + assert_eq!(Duration::parse("15.ab"), Err(ParseError)); + } + + #[test] + fn it_formats_duration_values() { + assert_eq!( + Duration::from(4200. * si::S).format(FormatOption::Abbreviated), + "1h 10m" + ); + assert_eq!( + Duration::from(4200. * si::S).format(FormatOption::Full), + "1 hours 10 minutes" + ); + } +}