iguazu 0.0.1

Tools for viewing, storing, and sharing mixed-signal time series data
Documentation
use ecow::EcoString;
use jiff::Timestamp;
use strum::{EnumString, IntoStaticStr};

use crate::{schema::{Entity, Field}, time::Time};
use super::{ Attribute, AttributeMap, string_attribute, AttributeValue};

pub const SAMPLE_RATE: Attribute<f64> = Attribute::named("sample_rate");
pub const TIME: Attribute<EcoString> = Attribute::named("time");
pub const TIME_RATE: Attribute<f64> = Attribute::named("time:rate");
pub const TIME_EPOCH: Attribute<Timestamp> = Attribute::named("time:epoch");
pub const TIME_DISPLAY: Attribute<TimeDisplay> = Attribute::named("time:display");
pub const TEXT: Attribute<EcoString> = Attribute::named("text");

pub const NUMBER_RANGE: Attribute<NumberRange> = Attribute::named("number:range");
pub const NUMBER_SCALE: Attribute<f64> = Attribute::named("number:scale");
pub const NUMBER_OFFSET: Attribute<f64> = Attribute::named("number:offset");

#[derive(Clone, Copy, Debug)]
pub struct NumberRange {
    pub min: f64,
    pub max: f64,
}

impl TryFrom<&AttributeValue> for NumberRange {
    type Error = ();

    fn try_from(value: &AttributeValue) -> Result<Self, Self::Error> {
        let o: AttributeMap = value.try_into().map_err(|_| ())?;
        Ok(NumberRange {
            min: o.get("min").ok_or(())?,
            max: o.get("max").ok_or(())?,
        })
    }
}

impl From<NumberRange> for AttributeValue {
    fn from(range: NumberRange) -> AttributeValue {
        AttributeMap::from_iter([
            ("min".into(), range.min.into()),
            ("max".into(), range.max.into()),
        ]).into()
    }
}

#[derive(Clone, Copy, Debug, IntoStaticStr, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum TimeDisplay {
    /// ISO 8601 / RFC 3339 absolute timestamp
    Iso,

    /// Relative to epoch in HH:MM:SS.sss format
    Relative,

    /// Raw integer ticks
    Raw,
}

string_attribute!(TimeDisplay);

impl<D, S> Entity<D, S> {
    pub fn sample_rate(&self) -> Option<f64> {
        self.attribute(SAMPLE_RATE)
    }

    pub fn sample_rate_as_period(&self) -> Option<Time> {
        self.sample_rate().map(Time::period_float)
    }

    pub fn time(&self) -> Option<EcoString> {
        self.attribute(TIME)
    }

    pub fn text(&self) -> Option<EcoString> {
        self.attribute(TEXT)
    }
}

impl Field {
    pub fn number_range(&self) -> Option<NumberRange> {
        self.attribute(NUMBER_RANGE)
    }

    pub fn time_rate(&self) -> Option<f64> {
        self.attribute(TIME_RATE)
    }

    pub fn time_rate_as_period(&self) -> Option<Time> {
        self.time_rate().map(Time::period_float)
    }

    pub fn time_epoch(&self) -> Option<Timestamp> {
        self.attribute(TIME_EPOCH)
    }

    pub fn time_display(&self) -> TimeDisplay {
        self.attribute(TIME_DISPLAY).unwrap_or({
            if self.time_rate().is_none() {
                TimeDisplay::Raw
            } else if self.time_epoch().is_none() {
                TimeDisplay::Relative
            } else {
                TimeDisplay::Iso
            }
        })
    }

    pub fn number_scale(&self) -> f64 {
        self.attribute(NUMBER_SCALE).unwrap_or(1.0)
    }

    pub fn number_offset(&self) -> f64 {
        self.attribute(NUMBER_OFFSET).unwrap_or(0.0)
    }

    pub fn text(&self) -> Option<EcoString> {
        self.attribute(TEXT)
    }
}