routee-compass-core 0.11.3

The core routing algorithms and data structures of the RouteE-Compass energy-aware routing engine
Documentation
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, str::FromStr};

use super::{baseunit, AsF64, Convert, UnitError, Volume};

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case", try_from = "String")]
pub enum VolumeUnit {
    GallonsUs,
    GallonsUk,
    Liters,
}

impl Convert<Volume> for VolumeUnit {
    fn convert(&self, value: &mut Cow<Volume>, to: &Self) -> Result<(), UnitError> {
        use VolumeUnit as V;
        let conversion_factor: Option<f64> = match (self, to) {
            (V::Liters, V::Liters) => None,
            (V::Liters, V::GallonsUs) => Some(0.264172),
            (V::Liters, V::GallonsUk) => Some(0.219969),
            (V::GallonsUs, V::Liters) => Some(3.78541),
            (V::GallonsUs, V::GallonsUs) => None,
            (V::GallonsUs, V::GallonsUk) => Some(0.832674),
            (V::GallonsUk, V::Liters) => Some(4.54609),
            (V::GallonsUk, V::GallonsUs) => Some(1.20095),
            (V::GallonsUk, V::GallonsUk) => None,
        };
        if let Some(factor) = conversion_factor {
            let updated = Volume::from(value.as_ref().as_f64() * factor);
            *value.to_mut() = updated;
        }
        Ok(())
    }

    fn convert_to_base(&self, value: &mut Cow<Volume>) -> Result<(), UnitError> {
        self.convert(value, &baseunit::VOLUME_UNIT)
    }
}

impl std::fmt::Display for VolumeUnit {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = serde_json::to_string(self)
            .map_err(|_| std::fmt::Error)?
            .replace('\"', "");
        write!(f, "{}", s)
    }
}

impl FromStr for VolumeUnit {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use VolumeUnit as V;
        match s.trim().to_lowercase().as_str() {
            "gal" | "usgal" | "usgals" => Ok(V::GallonsUs),
            "ukgal" | "ukgals" => Ok(V::GallonsUk),
            "liter" | "liters" | "l" => Ok(V::Liters),
            _ => Err(format!("unknown volume unit '{}'", s)),
        }
    }
}

impl TryFrom<String> for VolumeUnit {
    type Error = String;
    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::from_str(&value)
    }
}

#[cfg(test)]
mod test {

    use crate::model::unit::{VolumeUnit as V, *};
    use std::borrow::Cow;

    fn assert_approx_eq(a: Volume, b: Volume, error: f64) {
        let result = match (a, b) {
            (c, d) if c < d => (d - c).as_f64() < error,
            (c, d) if c > d => (c - d).as_f64() < error,
            (_, _) => true,
        };
        assert!(
            result,
            "{} ~= {} is not true within an error of {}",
            a, b, error
        )
    }

    #[test]
    fn test_l_l() {
        let mut value = Cow::Owned(Volume::ONE);
        V::Liters.convert(&mut value, &V::Liters).unwrap();
        assert_approx_eq(value.into_owned(), Volume::ONE, 0.001);
    }

    #[test]
    fn test_l_gal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::Liters.convert(&mut value, &V::GallonsUs).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(0.264172), 0.001);
    }

    #[test]
    fn test_l_ukgal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::Liters.convert(&mut value, &V::GallonsUk).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(0.219969), 0.001);
    }

    #[test]
    fn test_gal_l() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUs.convert(&mut value, &V::Liters).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(3.78541), 0.001);
    }

    #[test]
    fn test_gal_gal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUs.convert(&mut value, &V::GallonsUs).unwrap();
        assert_approx_eq(value.into_owned(), Volume::ONE, 0.001);
    }

    #[test]
    fn test_gal_ukgal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUs.convert(&mut value, &V::GallonsUk).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(0.832674), 0.001);
    }

    #[test]
    fn test_ukgal_l() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUk.convert(&mut value, &V::Liters).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(4.54609), 0.001);
    }

    #[test]
    fn test_ukgal_gal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUk.convert(&mut value, &V::GallonsUs).unwrap();
        assert_approx_eq(value.into_owned(), Volume::from(1.20095), 0.001);
    }

    #[test]
    fn test_ukgal_ukgal() {
        let mut value = Cow::Owned(Volume::ONE);
        V::GallonsUk.convert(&mut value, &V::GallonsUk).unwrap();
        assert_approx_eq(value.into_owned(), Volume::ONE, 0.001);
    }
}