app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 08650e50a533b5268a59436422117d11aac2a102
parent e656e379939de6b8babfefefb59190358ee9f639
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 07:14:48 +0000

app-utils: add unit helpers

- add area and mass unit enums
- add unit parsing helpers with defaults
- add mass_to_g conversion helper
- add unit tests for parsing and conversions

Diffstat:
Mcrates/utils/src/lib.rs | 5+++++
Acrates/utils/src/unit/mod.rs | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 145 insertions(+), 0 deletions(-)

diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs @@ -11,6 +11,7 @@ pub mod path; pub mod text; pub mod time; pub mod types; +pub mod unit; pub use r#async::exe_iter; pub use binary::{as_array_buffer, RadrootsAppArrayBuffer}; @@ -31,3 +32,7 @@ pub use types::{ ResolveError, ResultBool, ResultId, ResultObj, ResultPass, ResultPublicKey, ResultSecretKey, ResultsList, ValStr, WebFilePath, }; +pub use unit::{ + mass_to_g, parse_area_unit, parse_area_unit_default, parse_mass_unit, parse_mass_unit_default, + AreaUnit, MassUnit, AREA_UNITS, MASS_UNITS, +}; diff --git a/crates/utils/src/unit/mod.rs b/crates/utils/src/unit/mod.rs @@ -0,0 +1,140 @@ +#![forbid(unsafe_code)] + +use crate::error::RadrootsAppUtilsError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AreaUnit { + Ac, + Ha, + Ft2, + M2, +} + +impl AreaUnit { + pub const fn as_str(self) -> &'static str { + match self { + AreaUnit::Ac => "ac", + AreaUnit::Ha => "ha", + AreaUnit::Ft2 => "ft2", + AreaUnit::M2 => "m2", + } + } +} + +pub const AREA_UNITS: [AreaUnit; 4] = [AreaUnit::Ac, AreaUnit::Ha, AreaUnit::Ft2, AreaUnit::M2]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MassUnit { + Kg, + Lb, + G, +} + +impl MassUnit { + pub const fn as_str(self) -> &'static str { + match self { + MassUnit::Kg => "kg", + MassUnit::Lb => "lb", + MassUnit::G => "g", + } + } +} + +pub const MASS_UNITS: [MassUnit; 3] = [MassUnit::Kg, MassUnit::Lb, MassUnit::G]; + +pub fn parse_mass_unit(val: Option<&str>) -> Option<MassUnit> { + match val { + Some("kg") => Some(MassUnit::Kg), + Some("lb") => Some(MassUnit::Lb), + Some("g") => Some(MassUnit::G), + _ => None, + } +} + +pub fn parse_mass_unit_default(val: Option<&str>) -> MassUnit { + parse_mass_unit(val).unwrap_or(MassUnit::Kg) +} + +pub fn mass_to_g(value: f64, unit: &str) -> Result<f64, RadrootsAppUtilsError> { + let mass_unit = parse_mass_unit(Some(unit)).ok_or(RadrootsAppUtilsError::InvalidInput)?; + let grams = match mass_unit { + MassUnit::Kg => value * 1000.0, + MassUnit::Lb => value * 453.592, + MassUnit::G => value, + }; + Ok(grams) +} + +pub fn parse_area_unit(val: Option<&str>) -> Option<AreaUnit> { + match val { + Some("ac") => Some(AreaUnit::Ac), + Some("ha") => Some(AreaUnit::Ha), + Some("ft2") => Some(AreaUnit::Ft2), + Some("m2") => Some(AreaUnit::M2), + _ => None, + } +} + +pub fn parse_area_unit_default(val: Option<&str>) -> AreaUnit { + parse_area_unit(val).unwrap_or(AreaUnit::Ac) +} + +#[cfg(test)] +mod tests { + use super::{ + mass_to_g, parse_area_unit, parse_area_unit_default, parse_mass_unit, + parse_mass_unit_default, AreaUnit, MassUnit, AREA_UNITS, MASS_UNITS, + }; + + #[test] + fn area_units_are_sorted() { + assert_eq!(AREA_UNITS.len(), 4); + assert_eq!(AREA_UNITS[0], AreaUnit::Ac); + assert_eq!(AREA_UNITS[1], AreaUnit::Ha); + assert_eq!(AREA_UNITS[2], AreaUnit::Ft2); + assert_eq!(AREA_UNITS[3], AreaUnit::M2); + } + + #[test] + fn mass_units_are_sorted() { + assert_eq!(MASS_UNITS.len(), 3); + assert_eq!(MASS_UNITS[0], MassUnit::Kg); + assert_eq!(MASS_UNITS[1], MassUnit::Lb); + assert_eq!(MASS_UNITS[2], MassUnit::G); + } + + #[test] + fn parse_mass_units() { + assert_eq!(parse_mass_unit(Some("kg")), Some(MassUnit::Kg)); + assert_eq!(parse_mass_unit(Some("lb")), Some(MassUnit::Lb)); + assert_eq!(parse_mass_unit(Some("g")), Some(MassUnit::G)); + assert_eq!(parse_mass_unit(Some("other")), None); + } + + #[test] + fn parse_mass_unit_defaults_to_kg() { + assert_eq!(parse_mass_unit_default(None), MassUnit::Kg); + } + + #[test] + fn parse_area_units() { + assert_eq!(parse_area_unit(Some("ac")), Some(AreaUnit::Ac)); + assert_eq!(parse_area_unit(Some("ha")), Some(AreaUnit::Ha)); + assert_eq!(parse_area_unit(Some("ft2")), Some(AreaUnit::Ft2)); + assert_eq!(parse_area_unit(Some("m2")), Some(AreaUnit::M2)); + assert_eq!(parse_area_unit(Some("other")), None); + } + + #[test] + fn parse_area_unit_defaults_to_ac() { + assert_eq!(parse_area_unit_default(None), AreaUnit::Ac); + } + + #[test] + fn mass_to_g_handles_units() { + let grams = mass_to_g(2.0, "kg").expect("kg"); + assert_eq!(grams, 2000.0); + let grams = mass_to_g(2.0, "g").expect("g"); + assert_eq!(grams, 2.0); + } +}