lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

unit.rs (8729B)


      1 mod common;
      2 
      3 use core::str::FromStr;
      4 
      5 use radroots_core::{
      6     RadrootsCoreUnit, RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError,
      7     convert_mass_decimal, convert_unit_decimal, convert_volume_decimal, parse_mass_unit,
      8     parse_volume_unit,
      9 };
     10 
     11 #[test]
     12 fn parses_units_and_synonyms() {
     13     use RadrootsCoreUnit::*;
     14     let cases = [
     15         ("each", Each),
     16         ("ea", Each),
     17         ("count", Each),
     18         ("kg", MassKg),
     19         ("kilograms", MassKg),
     20         ("g", MassG),
     21         ("grams", MassG),
     22         ("oz", MassOz),
     23         ("ounces", MassOz),
     24         ("lb", MassLb),
     25         ("pounds", MassLb),
     26         ("l", VolumeL),
     27         ("liters", VolumeL),
     28         ("ml", VolumeMl),
     29         ("milliliters", VolumeMl),
     30     ];
     31     for (input, expected) in cases {
     32         assert_eq!(RadrootsCoreUnit::from_str(input).unwrap(), expected);
     33     }
     34 }
     35 
     36 #[test]
     37 fn rejects_unknown_units() {
     38     assert_eq!(
     39         RadrootsCoreUnit::from_str("unknown"),
     40         Err(RadrootsCoreUnitParseError::UnknownUnit)
     41     );
     42 }
     43 
     44 #[test]
     45 fn same_dimension_matches_mass_and_volume_groups() {
     46     use RadrootsCoreUnit::*;
     47     assert!(RadrootsCoreUnit::same_dimension(MassKg, MassG));
     48     assert!(RadrootsCoreUnit::same_dimension(VolumeL, VolumeMl));
     49     assert!(RadrootsCoreUnit::same_dimension(Each, Each));
     50     assert!(!RadrootsCoreUnit::same_dimension(MassKg, VolumeL));
     51     assert!(!RadrootsCoreUnit::same_dimension(Each, MassG));
     52 }
     53 
     54 #[test]
     55 fn canonical_unit_maps_dimensions() {
     56     use RadrootsCoreUnit::*;
     57     assert_eq!(Each.canonical_unit(), Each);
     58     assert_eq!(MassKg.canonical_unit(), MassG);
     59     assert_eq!(MassLb.canonical_unit(), MassG);
     60     assert_eq!(VolumeL.canonical_unit(), VolumeMl);
     61 }
     62 
     63 #[test]
     64 fn code_and_predicate_helpers_cover_all_variants() {
     65     use RadrootsCoreUnit::*;
     66     assert_eq!(Each.code(), "each");
     67     assert_eq!(MassKg.code(), "kg");
     68     assert_eq!(MassG.code(), "g");
     69     assert_eq!(MassOz.code(), "oz");
     70     assert_eq!(MassLb.code(), "lb");
     71     assert_eq!(VolumeL.code(), "l");
     72     assert_eq!(VolumeMl.code(), "ml");
     73 
     74     assert!(Each.is_count());
     75     assert!(!Each.is_mass());
     76     assert!(!Each.is_volume());
     77     assert!(MassLb.is_mass());
     78     assert!(!MassLb.is_count());
     79     assert!(VolumeMl.is_volume());
     80 }
     81 
     82 #[test]
     83 fn parse_mass_unit_enforces_mass_only() {
     84     assert_eq!(parse_mass_unit("kg"), Ok(RadrootsCoreUnit::MassKg));
     85     assert_eq!(
     86         parse_mass_unit("each"),
     87         Err(RadrootsCoreUnitParseError::NotAMassUnit)
     88     );
     89     assert_eq!(
     90         parse_mass_unit("bogus"),
     91         Err(RadrootsCoreUnitParseError::UnknownUnit)
     92     );
     93 }
     94 
     95 #[test]
     96 fn parse_volume_unit_enforces_volume_only() {
     97     assert_eq!(parse_volume_unit("l"), Ok(RadrootsCoreUnit::VolumeL));
     98     assert_eq!(
     99         parse_volume_unit("kg"),
    100         Err(RadrootsCoreUnitParseError::NotAVolumeUnit)
    101     );
    102     assert_eq!(
    103         parse_volume_unit("bogus"),
    104         Err(RadrootsCoreUnitParseError::UnknownUnit)
    105     );
    106 }
    107 
    108 #[test]
    109 fn convert_mass_decimal_converts_between_mass_units() {
    110     use RadrootsCoreUnit::*;
    111     let kg_to_g = convert_mass_decimal(common::dec("1"), MassKg, MassG).unwrap();
    112     let g_to_kg = convert_mass_decimal(common::dec("1000"), MassG, MassKg).unwrap();
    113     let lb_to_g = convert_mass_decimal(common::dec("1"), MassLb, MassG).unwrap();
    114     let oz_to_g = convert_mass_decimal(common::dec("1"), MassOz, MassG).unwrap();
    115     let g_to_oz = convert_mass_decimal(common::dec("28.349523125"), MassG, MassOz).unwrap();
    116     let g_to_lb = convert_mass_decimal(common::dec("453.59237"), MassG, MassLb).unwrap();
    117 
    118     assert_eq!(kg_to_g, common::dec("1000"));
    119     assert_eq!(g_to_kg, common::dec("1"));
    120     assert_eq!(lb_to_g, common::dec("453.59237"));
    121     assert_eq!(oz_to_g, common::dec("28.349523125"));
    122     assert_eq!(g_to_oz, common::dec("1"));
    123     assert_eq!(g_to_lb, common::dec("1"));
    124 }
    125 
    126 #[test]
    127 fn convert_mass_decimal_rejects_non_mass_units() {
    128     let err = convert_mass_decimal(
    129         common::dec("1"),
    130         RadrootsCoreUnit::Each,
    131         RadrootsCoreUnit::MassG,
    132     )
    133     .unwrap_err();
    134     assert_eq!(
    135         err,
    136         RadrootsCoreUnitConvertError::NotMassUnit {
    137             from: RadrootsCoreUnit::Each,
    138             to: RadrootsCoreUnit::MassG
    139         }
    140     );
    141 
    142     let err = convert_mass_decimal(
    143         common::dec("1"),
    144         RadrootsCoreUnit::MassKg,
    145         RadrootsCoreUnit::Each,
    146     )
    147     .unwrap_err();
    148     assert_eq!(
    149         err,
    150         RadrootsCoreUnitConvertError::NotMassUnit {
    151             from: RadrootsCoreUnit::MassKg,
    152             to: RadrootsCoreUnit::Each
    153         }
    154     );
    155 }
    156 
    157 #[test]
    158 fn convert_volume_decimal_converts_between_volume_units() {
    159     use RadrootsCoreUnit::*;
    160     let l_to_ml = convert_volume_decimal(common::dec("1"), VolumeL, VolumeMl).unwrap();
    161     let ml_to_l = convert_volume_decimal(common::dec("250"), VolumeMl, VolumeL).unwrap();
    162     assert_eq!(l_to_ml, common::dec("1000"));
    163     assert_eq!(ml_to_l, common::dec("0.25"));
    164 }
    165 
    166 #[test]
    167 fn convert_volume_decimal_rejects_non_volume_units() {
    168     let err = convert_volume_decimal(
    169         common::dec("1"),
    170         RadrootsCoreUnit::Each,
    171         RadrootsCoreUnit::VolumeMl,
    172     )
    173     .unwrap_err();
    174     assert_eq!(
    175         err,
    176         RadrootsCoreUnitConvertError::NotVolumeUnit {
    177             from: RadrootsCoreUnit::Each,
    178             to: RadrootsCoreUnit::VolumeMl
    179         }
    180     );
    181 
    182     let err = convert_volume_decimal(
    183         common::dec("1"),
    184         RadrootsCoreUnit::VolumeMl,
    185         RadrootsCoreUnit::Each,
    186     )
    187     .unwrap_err();
    188     assert_eq!(
    189         err,
    190         RadrootsCoreUnitConvertError::NotVolumeUnit {
    191             from: RadrootsCoreUnit::VolumeMl,
    192             to: RadrootsCoreUnit::Each
    193         }
    194     );
    195 }
    196 
    197 #[test]
    198 fn convert_unit_decimal_converts_matching_dimensions() {
    199     use RadrootsCoreUnit::*;
    200     let kg_to_g = convert_unit_decimal(common::dec("1"), MassKg, MassG).unwrap();
    201     let l_to_ml = convert_unit_decimal(common::dec("2"), VolumeL, VolumeMl).unwrap();
    202     let each_to_each = convert_unit_decimal(common::dec("3"), Each, Each).unwrap();
    203     assert_eq!(kg_to_g, common::dec("1000"));
    204     assert_eq!(l_to_ml, common::dec("2000"));
    205     assert_eq!(each_to_each, common::dec("3"));
    206 }
    207 
    208 #[test]
    209 fn display_paths_for_unit_and_errors_are_exercised() {
    210     assert_eq!(RadrootsCoreUnit::MassOz.to_string(), "oz");
    211     assert_eq!(
    212         RadrootsCoreUnitParseError::UnknownUnit.to_string(),
    213         "unknown unit string"
    214     );
    215     assert_eq!(
    216         RadrootsCoreUnitParseError::NotAMassUnit.to_string(),
    217         "unit is not a mass unit"
    218     );
    219     assert_eq!(
    220         RadrootsCoreUnitParseError::NotAVolumeUnit.to_string(),
    221         "unit is not a volume unit"
    222     );
    223     assert_eq!(
    224         RadrootsCoreUnitConvertError::NotMassUnit {
    225             from: RadrootsCoreUnit::Each,
    226             to: RadrootsCoreUnit::MassG
    227         }
    228         .to_string(),
    229         "unit conversion requires mass units: each -> g"
    230     );
    231     assert_eq!(
    232         RadrootsCoreUnitConvertError::NotVolumeUnit {
    233             from: RadrootsCoreUnit::Each,
    234             to: RadrootsCoreUnit::VolumeL
    235         }
    236         .to_string(),
    237         "unit conversion requires volume units: each -> l"
    238     );
    239     assert_eq!(
    240         RadrootsCoreUnitConvertError::NotConvertibleUnits {
    241             from: RadrootsCoreUnit::Each,
    242             to: RadrootsCoreUnit::MassG
    243         }
    244         .to_string(),
    245         "unit conversion requires matching dimensions: each -> g"
    246     );
    247 }
    248 
    249 #[test]
    250 fn convert_unit_decimal_rejects_mismatched_dimensions() {
    251     let err = convert_unit_decimal(
    252         common::dec("1"),
    253         RadrootsCoreUnit::Each,
    254         RadrootsCoreUnit::MassG,
    255     )
    256     .unwrap_err();
    257     assert_eq!(
    258         err,
    259         RadrootsCoreUnitConvertError::NotConvertibleUnits {
    260             from: RadrootsCoreUnit::Each,
    261             to: RadrootsCoreUnit::MassG
    262         }
    263     );
    264 }
    265 
    266 #[cfg(feature = "serde")]
    267 #[test]
    268 fn serde_roundtrip_for_unit_paths() {
    269     use RadrootsCoreUnit::*;
    270     let all = [Each, MassKg, MassG, MassOz, MassLb, VolumeL, VolumeMl];
    271     for unit in all {
    272         let json = serde_json::to_string(&unit).unwrap();
    273         let back: RadrootsCoreUnit = serde_json::from_str(&json).unwrap();
    274         assert_eq!(back, unit);
    275     }
    276     let back: RadrootsCoreUnit = serde_json::from_str("\"kg\"").unwrap();
    277     assert_eq!(back, RadrootsCoreUnit::MassKg);
    278     let unknown_err = serde_json::from_str::<RadrootsCoreUnit>("\"bogus\"").unwrap_err();
    279     assert!(unknown_err.to_string().contains("unknown unit"));
    280     let err = serde_json::from_str::<RadrootsCoreUnit>("123").unwrap_err();
    281     assert!(err.to_string().contains("invalid type"));
    282     let missing_err = serde_json::from_str::<RadrootsCoreUnit>("{}").unwrap_err();
    283     assert!(!missing_err.to_string().is_empty());
    284 }