modio-logger-db 0.11.1

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

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

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

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,
            clippy::cast_possible_truncation
        )]
        let timestamp = f.time as u64;

        Self {
            key: f.name,
            value: f.value,
            timestamp,
        }
    }
}
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,
            clippy::cast_precision_loss
        )]
        let time = f.timestamp as f64;

        Self {
            name: f.key,
            value: f.value,
            time,
        }
    }
}

#[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 {
            Self::ReadOnly => "ro",
            Self::ReadWrite => "rw",
            Self::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) -> Self {
        match other {
            fsipc::logger1::SensorMode::ReadOnly => Self::ReadOnly,
            fsipc::logger1::SensorMode::ReadWrite => Self::ReadWrite,
            fsipc::logger1::SensorMode::WriteOnly => Self::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 {
            Self::ReadOnly => fsipc::logger1::SensorMode::ReadOnly,
            Self::ReadWrite => fsipc::logger1::SensorMode::ReadWrite,
            Self::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>,
    pub row: Option<Vec<DataType>>,
}

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

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

/// Metadata builder, used to slim down code that creates a Metadata object
impl MetadataBuilder {
    #[must_use]
    pub const fn new(n: String) -> Self {
        Self {
            n,
            u: None,
            name: None,
            description: None,
            value_map: None,
            mode: None,
            row: 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 const 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: String) -> Self {
        let value_map = serde_json::from_str::<ValueMap>(&value);
        if value_map.is_err() {
            error!("Error decoding json: {:?}", value);
        };
        self.value_map = value_map.ok();
        self
    }

    /// Parse a row metadata 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 row_string(mut self, value: &str) -> Self {
        self.row = DataType::vec_from_str(value).ok();
        self
    }

    #[must_use]
    pub fn 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),
            "row" => self.row_string(&value),
            _ => {
                error!("Strange decoding pair? {:?} {:?}", tag, &value);
                self
            }
        }
    }

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

/// A helper for cleanup results to be printed to logs from the application.
#[derive(Debug, Default)]
pub struct CleanResult {
    pub trans_failed: i32,
    pub trans_old: i32,
    pub data_deleted: i32,
}

impl std::fmt::Display for CleanResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "transactions_failed={}, transactions_old={}, data_deleted={}",
            self.trans_failed, self.trans_old, self.data_deleted,
        )
    }
}

/// A helper for statistics to be printed to logs from the application.
#[derive(Debug, Default)]
pub struct Statistics {
    pub metrics: i32,
    pub internal: i32,
    pub removed: i32,
    pub timefail: i32,
    pub transactions: i32,
    pub buffered: i32,
}

impl std::fmt::Display for Statistics {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "metrics={}, internal={}, removed={}, timefail={}, transactions={}, buffered={}",
            self.metrics,
            self.internal,
            self.removed,
            self.timefail,
            self.transactions,
            self.buffered
        )
    }
}