modio-logger-db 0.5.2

modio-logger Dbus service
Documentation
// Author: D.S. Ljungmark <spider@skuggor.se>, Modio AB
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::collections::HashMap;

#[derive(Debug)]
pub struct Sensor {
    #[allow(dead_code)]
    pub(crate) name: String,
    #[allow(dead_code)]
    pub(crate) s_id: i64,
}

#[derive(Debug)]
pub struct Metric {
    pub(crate) name: String,
    pub(crate) value: String,
    pub(crate) time: i64,
}

impl From<Metric> for fsipc::legacy::Measure {
    fn from(f: Metric) -> Self {
        // Legacy type was u64, but we want i64 in the database due to SQLite.
        #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
        Self {
            key: f.name,
            value: f.value,
            timestamp: f.time as u64,
        }
    }
}
impl From<fsipc::legacy::Measure> for Metric {
    fn from(f: fsipc::legacy::Measure) -> Self {
        // Legacy type for timestamp was u64, but we want i64 in the database due to SQLite.
        #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
        Self {
            name: f.key,
            value: f.value,
            time: f.timestamp as i64,
        }
    }
}

#[derive(Debug)]
pub struct TXMetric {
    pub(crate) id: i64,
    pub(crate) name: String,
    pub(crate) value: String,
    pub(crate) time: i64,
}

impl From<TXMetric> for fsipc::legacy::PreparedPoint {
    fn from(f: TXMetric) -> Self {
        // Once again i64 vs u64 for a timestamp
        #[allow(clippy::cast_sign_loss)]
        Self {
            id: f.id,
            key: f.name,
            value: f.value,
            timestamp: f.time as u64,
        }
    }
}

#[derive(Debug)]
pub struct Transaction {
    pub(crate) name: String,
    pub(crate) expected: String,
    pub(crate) target: String,
    pub(crate) t_id: i64,
    #[allow(dead_code)]
    pub(crate) status: String,
}

impl From<Transaction> for fsipc::legacy::Transaction {
    fn from(f: Transaction) -> Self {
        // t_id is a number, should be i64 according to sqlite, but was u64 on our wire
        // interface.
        #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
        Self {
            key: f.name,
            expected: f.expected,
            target: f.target,
            t_id: f.t_id as u64,
        }
    }
}

pub type ValueMap = HashMap<u32, String>;

#[derive(Debug, Eq, PartialEq)]
pub enum SensorMode {
    ReadOnly,
    ReadWrite,
    WriteOnly,
}
impl SensorMode {
    // Convenience function for stringification
    #[must_use]
    pub const fn as_str(&self) -> &str {
        match self {
            SensorMode::ReadOnly => "ro",
            SensorMode::ReadWrite => "rw",
            SensorMode::WriteOnly => "wo",
        }
    }
    // Map a string to a value, wrapped in an option.
    // Makes the code that reads from the database easier, and keeps the string reps in this
    // enum impl
    #[must_use]
    pub fn maybe_from_str(value: &str) -> Option<Self> {
        match value {
            "ro" => Some(Self::ReadOnly),
            "rw" => Some(Self::ReadWrite),
            "wo" => Some(Self::WriteOnly),
            _ => None,
        }
    }
}

impl From<fsipc::logger1::SensorMode> for SensorMode {
    fn from(other: fsipc::logger1::SensorMode) -> SensorMode {
        match other {
            fsipc::logger1::SensorMode::ReadOnly => SensorMode::ReadOnly,
            fsipc::logger1::SensorMode::ReadWrite => SensorMode::ReadWrite,
            fsipc::logger1::SensorMode::WriteOnly => SensorMode::WriteOnly,
        }
    }
}
// fsipc does not import this type for obvious reasons.
// thus we can only implement "Into" but not "From"
#[allow(clippy::from_over_into)]
impl Into<fsipc::logger1::SensorMode> for SensorMode {
    fn into(self) -> fsipc::logger1::SensorMode {
        match self {
            SensorMode::ReadOnly => fsipc::logger1::SensorMode::ReadOnly,
            SensorMode::ReadWrite => fsipc::logger1::SensorMode::ReadWrite,
            SensorMode::WriteOnly => fsipc::logger1::SensorMode::WriteOnly,
        }
    }
}

#[test]
fn enum_roundtrip() {
    fn around(b: SensorMode) -> SensorMode {
        let sval = b.as_str();
        SensorMode::maybe_from_str(sval).unwrap()
    }
    assert_eq!(SensorMode::ReadOnly, around(SensorMode::ReadOnly));
    assert_eq!(SensorMode::ReadWrite, around(SensorMode::ReadWrite));
    assert_eq!(SensorMode::WriteOnly, around(SensorMode::WriteOnly));
}

#[derive(Debug)]
pub struct Metadata {
    pub n: String,
    pub u: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
    pub value_map: Option<ValueMap>,
    pub mode: Option<SensorMode>,
}

#[derive(Debug)]
pub struct MetadataBuilder {
    n: String,
    u: Option<String>,
    name: Option<String>,
    description: Option<String>,
    value_map: Option<ValueMap>,
    mode: Option<SensorMode>,
}

impl Metadata {
    #[must_use]
    pub fn builder(n: String) -> MetadataBuilder {
        MetadataBuilder::new(n)
    }
}

/// Metadata builder, used to slim down code that creates a Metadata object
impl MetadataBuilder {
    #[must_use]
    pub fn new(n: String) -> Self {
        Self {
            n,
            u: None,
            name: None,
            description: None,
            value_map: None,
            mode: None,
        }
    }

    #[must_use]
    pub fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }
    #[must_use]
    pub fn unit(mut self, u: String) -> Self {
        self.u = Some(u);
        self
    }
    #[must_use]
    pub fn description(mut self, description: String) -> Self {
        self.description = Some(description);
        self
    }

    #[must_use]
    pub fn value_map(mut self, value_map: ValueMap) -> Self {
        self.value_map = Some(value_map);
        self
    }

    #[must_use]
    pub fn mode(mut self, mode: SensorMode) -> Self {
        self.mode = Some(mode);
        self
    }

    #[must_use]
    pub fn mode_string(mut self, value: &str) -> Self {
        self.mode = SensorMode::maybe_from_str(value);
        self
    }

    /// Parse a value map from a string.
    /// Does not return errors as it is assumed that the consumer of such an error would not be
    /// happy.
    #[must_use]
    pub fn value_map_string(mut self, value: &str) -> Self {
        let value_map = serde_json::from_str::<ValueMap>(value);
        if value_map.is_err() {
            log::error!("Error decoding json: {:?}", value);
        };
        self.value_map = value_map.ok();
        self
    }

    #[must_use]
    pub fn from_pair(self, tag: &str, value: String) -> Self {
        match tag {
            "unit" => self.unit(value),
            "description" => self.description(value),
            "name" => self.name(value),
            "enum" => self.value_map_string(&value),
            "mode" => self.mode_string(&value),
            _ => {
                log::debug!("Whut is this? {:?} {:?}", tag, &value);
                self
            }
        }
    }

    #[must_use]
    pub fn build(self) -> Metadata {
        let Self {
            n,
            u,
            name,
            description,
            value_map,
            mode,
        } = self;
        Metadata {
            n,
            u,
            name,
            description,
            value_map,
            mode,
        }
    }
}