1use std::{fmt::Display, str::FromStr};
2
3const GRAMS_PER_POUND: f64 = 453.592;
4const OUNCES_PER_POUND: f64 = 16.0;
5
6const GRAMS_PER_OUNCE: f64 = GRAMS_PER_POUND / OUNCES_PER_POUND;
7const OUNCES_PER_GRAM: f64 = OUNCES_PER_POUND / GRAMS_PER_POUND;
8
9#[derive(Debug)]
10pub struct BadUnit;
11
12#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
13pub enum Unit {
14 #[default]
15 Gram,
16 Ounce,
17 Pound,
18}
19
20impl Unit {
21 #[must_use]
22 pub fn dual(self) -> Unit {
23 match self {
24 Unit::Gram => Unit::Ounce,
25 Unit::Ounce | Unit::Pound => Unit::Gram,
26 }
27 }
28
29 #[must_use]
33 pub fn per(self, unit: Unit) -> f64 {
34 match (self, unit) {
35 (Unit::Gram, Unit::Gram) | (Unit::Ounce, Unit::Ounce) | (Unit::Pound, Unit::Pound) => {
36 1.0
37 }
38 (Unit::Gram, Unit::Ounce) => GRAMS_PER_OUNCE,
39 (Unit::Gram, Unit::Pound) => GRAMS_PER_POUND,
40 (Unit::Ounce, Unit::Gram) => OUNCES_PER_GRAM,
41 (Unit::Ounce, Unit::Pound) => OUNCES_PER_POUND,
42 (Unit::Pound, Unit::Gram) => 1.0 / GRAMS_PER_POUND,
43 (Unit::Pound, Unit::Ounce) => 1.0 / OUNCES_PER_POUND,
44 }
45 }
46}
47
48impl Display for Unit {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 let s = match self {
51 Unit::Gram => "g",
52 Unit::Ounce => "oz",
53 Unit::Pound => "lb",
54 };
55 write!(f, "{s}")
56 }
57}
58
59impl FromStr for Unit {
60 type Err = BadUnit;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s {
64 "" => Ok(Unit::default()),
65 "g" => Ok(Unit::Gram),
66 "lb" | "lbs" | "#" => Ok(Unit::Pound),
67 "oz" => Ok(Unit::Ounce),
68 _ => Err(BadUnit),
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_unit_parsing() {
79 assert_eq!("g".parse::<Unit>().unwrap(), Unit::Gram);
80 assert_eq!("oz".parse::<Unit>().unwrap(), Unit::Ounce);
81 assert_eq!("lb".parse::<Unit>().unwrap(), Unit::Pound);
82 assert_eq!("lbs".parse::<Unit>().unwrap(), Unit::Pound);
83 assert!("invalid".parse::<Unit>().is_err());
84 }
85
86 #[test]
87 fn test_unit_display() {
88 assert_eq!(Unit::Gram.to_string(), "g");
89 assert_eq!(Unit::Ounce.to_string(), "oz");
90 assert_eq!(Unit::Pound.to_string(), "lb");
91 }
92
93 #[test]
94 fn test_unit_conversion() {
95 let grams_per_oz = Unit::Gram.per(Unit::Ounce);
97 assert!((grams_per_oz - 28.35).abs() < 0.1);
98
99 let grams_per_lb = Unit::Gram.per(Unit::Pound);
101 assert!((grams_per_lb - 453.6).abs() < 0.1);
102
103 assert_eq!(Unit::Gram.per(Unit::Gram), 1.0);
105 }
106}