oca-bundle 0.4.2

Rust implementation of Overlays Capture Architecture bundle
Documentation
use crate::state::{attribute::Attribute, oca::Overlay};
use oca_ast::ast::OverlayType;
use said::{sad::SerializationFormats, sad::SAD};
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
use std::any::Any;
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Debug, Clone, Eq, Hash, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MeasurementSystem {
    Metric,
    Imperial,
}

pub struct AttributeUnit {
    pub measurement_system: MeasurementSystem,
    pub unit: MeasurementUnit,
}
#[derive(Eq, Hash, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MeasurementUnit {
    Metric(MetricUnit),
    Imperial(ImperialUnit),
}

#[derive(Eq, Hash, Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MetricUnit {
    Kilogram,
    Gram,
    Milligram,
    Liter,
    Milliliter,
    Meter,
    Centimeter,
    Millimeter,
    Inch,
    Foot,
    Yard,
    Mile,
    Celsius,
    Fahrenheit,
    Kelvin,
    Percent,
    Count,
    Other,
}

#[derive(Eq, Hash, Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ImperialUnit {
    Pound,
    Ounce,
    Gallon,
    Quart,
    Pint,
    FluidOunce,
    Inch,
    Foot,
    Yard,
    Mile,
    Celsius,
    Fahrenheit,
    Kelvin,
    Percent,
    Count,
    Other,
}

impl std::str::FromStr for MetricUnit {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "kilogram" => Ok(MetricUnit::Kilogram),
            "kg" => Ok(MetricUnit::Kilogram),
            "gram" => Ok(MetricUnit::Gram),
            "g" => Ok(MetricUnit::Gram),
            "milligram" => Ok(MetricUnit::Milligram),
            "mg" => Ok(MetricUnit::Milligram),
            "liter" => Ok(MetricUnit::Liter),
            "l" => Ok(MetricUnit::Liter),
            "meter" => Ok(MetricUnit::Meter),
            "m" => Ok(MetricUnit::Meter),
            "milliliter" => Ok(MetricUnit::Milliliter),
            "ml" => Ok(MetricUnit::Milliliter),
            "centimeter" => Ok(MetricUnit::Centimeter),
            "cm" => Ok(MetricUnit::Centimeter),
            "millimeter" => Ok(MetricUnit::Millimeter),
            "mm" => Ok(MetricUnit::Millimeter),
            "inch" => Ok(MetricUnit::Inch),
            "in" => Ok(MetricUnit::Inch),
            "foot" => Ok(MetricUnit::Foot),
            "ft" => Ok(MetricUnit::Foot),
            "yard" => Ok(MetricUnit::Yard),
            "yd" => Ok(MetricUnit::Yard),
            "mile" => Ok(MetricUnit::Mile),
            "mi" => Ok(MetricUnit::Mile),
            "celsius" => Ok(MetricUnit::Celsius),
            "c" => Ok(MetricUnit::Celsius),
            "fahrenheit" => Ok(MetricUnit::Fahrenheit),
            "f" => Ok(MetricUnit::Fahrenheit),
            "kelvin" => Ok(MetricUnit::Kelvin),
            "k" => Ok(MetricUnit::Kelvin),
            "percent" => Ok(MetricUnit::Percent),
            "%" => Ok(MetricUnit::Percent),
            "count" => Ok(MetricUnit::Count),
            "other" => Ok(MetricUnit::Other),
            _ => Err(()),
        }
    }
}

impl std::str::FromStr for ImperialUnit {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "pound" => Ok(ImperialUnit::Pound),
            "lb" => Ok(ImperialUnit::Pound),
            "ounce" => Ok(ImperialUnit::Ounce),
            "oz" => Ok(ImperialUnit::Ounce),
            "gallon" => Ok(ImperialUnit::Gallon),
            "gal" => Ok(ImperialUnit::Gallon),
            "quart" => Ok(ImperialUnit::Quart),
            "qt" => Ok(ImperialUnit::Quart),
            "pint" => Ok(ImperialUnit::Pint),
            "pt" => Ok(ImperialUnit::Pint),
            "fluid ounce" => Ok(ImperialUnit::FluidOunce),
            "fl oz" => Ok(ImperialUnit::FluidOunce),
            "inch" => Ok(ImperialUnit::Inch),
            "in" => Ok(ImperialUnit::Inch),
            "foot" => Ok(ImperialUnit::Foot),
            "ft" => Ok(ImperialUnit::Foot),
            "yard" => Ok(ImperialUnit::Yard),
            "yd" => Ok(ImperialUnit::Yard),
            "mile" => Ok(ImperialUnit::Mile),
            "mi" => Ok(ImperialUnit::Mile),
            "celsius" => Ok(ImperialUnit::Celsius),
            "c" => Ok(ImperialUnit::Celsius),
            "fahrenheit" => Ok(ImperialUnit::Fahrenheit),
            "f" => Ok(ImperialUnit::Fahrenheit),
            "kelvin" => Ok(ImperialUnit::Kelvin),
            "k" => Ok(ImperialUnit::Kelvin),
            "percent" => Ok(ImperialUnit::Percent),
            "%" => Ok(ImperialUnit::Percent),
            "count" => Ok(ImperialUnit::Count),
            "other" => Ok(ImperialUnit::Other),
            _ => Err(()),
        }
    }
}

impl std::str::FromStr for MeasurementSystem {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "metric" => Ok(MeasurementSystem::Metric),
            "si" => Ok(MeasurementSystem::Metric),
            "imperial" => Ok(MeasurementSystem::Imperial),
            "iu" => Ok(MeasurementSystem::Imperial),
            _ => Err(()),
        }
    }
}

pub trait Unit {
    fn set_unit(&mut self, attr_unit: AttributeUnit);
}

impl Unit for Attribute {
    fn set_unit(&mut self, attr_unit: AttributeUnit) {
        match self.units {
            Some(ref mut units) => {
                units.insert(attr_unit.measurement_system, attr_unit.unit);
            }
            None => {
                let mut units = HashMap::new();
                units.insert(attr_unit.measurement_system, attr_unit.unit);
                self.units = Some(units);
            }
        }
    }
}

pub fn serialize_attributes<S>(
    attributes: &HashMap<String, MeasurementUnit>,
    s: S,
) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    use std::collections::BTreeMap;

    let mut ser = s.serialize_map(Some(attributes.len()))?;
    let sorted_attributes: BTreeMap<_, _> = attributes.iter().collect();
    for (k, v) in sorted_attributes {
        ser.serialize_entry(k, v)?;
    }
    ser.end()
}

#[derive(SAD, Serialize, Deserialize, Debug, Clone)]
pub struct UnitOverlay {
    #[said]
    #[serde(rename = "d")]
    said: Option<said::SelfAddressingIdentifier>,
    #[serde(rename = "type")]
    overlay_type: OverlayType,
    capture_base: Option<said::SelfAddressingIdentifier>,
    pub measurement_system: MeasurementSystem,
    #[serde(serialize_with = "serialize_attributes")]
    pub attribute_units: HashMap<String, MeasurementUnit>,
}

impl Overlay for UnitOverlay {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn capture_base(&self) -> &Option<said::SelfAddressingIdentifier> {
        &self.capture_base
    }
    fn set_capture_base(&mut self, said: &said::SelfAddressingIdentifier) {
        self.capture_base = Some(said.clone());
    }
    fn said(&self) -> &Option<said::SelfAddressingIdentifier> {
        &self.said
    }
    fn overlay_type(&self) -> &OverlayType {
        &self.overlay_type
    }

    fn attributes(&self) -> Vec<&String> {
        self.attribute_units.keys().collect::<Vec<&String>>()
    }

    fn add(&mut self, attribute: &Attribute) {
        if let Some(units) = &attribute.units {
            if let Some(unit) = units.get(&self.measurement_system) {
                self.attribute_units
                    .insert(attribute.name.clone(), unit.clone());
            }
        }
    }
}
impl UnitOverlay {
    pub fn new(measurement_system: MeasurementSystem) -> UnitOverlay {
        UnitOverlay {
            capture_base: None,
            said: None,
            overlay_type: OverlayType::Unit,
            measurement_system,
            attribute_units: HashMap::new(),
        }
    }

    pub fn measurement_system(&self) -> Option<&MeasurementSystem> {
        Some(&self.measurement_system)
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use crate::state::attribute::Attribute;
    use crate::state::oca::overlay::unit::MeasurementSystem;

    #[test]
    fn test_set_unit() {
        let attribute = cascade! {
            Attribute::new("test".to_string());
            ..set_unit(AttributeUnit { measurement_system: MeasurementSystem::Metric, unit: MeasurementUnit::Metric(MetricUnit::Kilogram)});
            ..set_unit(AttributeUnit { measurement_system: MeasurementSystem::Imperial, unit: MeasurementUnit::Imperial(ImperialUnit::Pound) });
        };

        // assert eq units
        assert_eq!(
            attribute.units,
            Some({
                let mut units = HashMap::new();
                units.insert(
                    MeasurementSystem::Metric,
                    MeasurementUnit::Metric(MetricUnit::Kilogram),
                );
                units.insert(
                    MeasurementSystem::Imperial,
                    MeasurementUnit::Imperial(ImperialUnit::Pound),
                );
                units
            })
        );
    }
}