modio_logger_db/
types.rs

1// Author: D.S. Ljungmark <spider@skuggor.se>, Modio AB
2// SPDX-License-Identifier: AGPL-3.0-or-later
3mod datatype;
4pub use datatype::DataType;
5use std::collections::HashMap;
6use tracing::error;
7
8#[derive(Debug)]
9pub struct Sensor {
10    #[allow(dead_code)]
11    pub(crate) name: String,
12    #[allow(dead_code)]
13    pub(crate) s_id: i64,
14}
15
16#[derive(Debug, Clone)]
17pub struct Metric {
18    pub name: String,
19    pub value: String,
20    pub time: f64,
21}
22
23impl From<Metric> for fsipc::legacy::Measure {
24    fn from(f: Metric) -> Self {
25        // Legacy type was u64, but we want i64 in the database due to SQLite.
26        #[allow(
27            clippy::cast_sign_loss,
28            clippy::cast_possible_wrap,
29            clippy::cast_possible_truncation
30        )]
31        let timestamp = f.time as u64;
32
33        Self {
34            key: f.name,
35            value: f.value,
36            timestamp,
37        }
38    }
39}
40impl From<fsipc::legacy::Measure> for Metric {
41    fn from(f: fsipc::legacy::Measure) -> Self {
42        // Legacy type for timestamp was u64, but we want i64 in the database due to SQLite.
43        #[allow(
44            clippy::cast_sign_loss,
45            clippy::cast_possible_wrap,
46            clippy::cast_precision_loss
47        )]
48        let time = f.timestamp as f64;
49
50        Self {
51            name: f.key,
52            value: f.value,
53            time,
54        }
55    }
56}
57
58#[derive(Debug)]
59pub struct TXMetric {
60    pub(crate) id: i64,
61    pub(crate) name: String,
62    pub(crate) value: String,
63    pub(crate) time: i64,
64}
65
66impl From<TXMetric> for fsipc::legacy::PreparedPoint {
67    fn from(f: TXMetric) -> Self {
68        // Once again i64 vs u64 for a timestamp
69        #[allow(clippy::cast_sign_loss)]
70        Self {
71            id: f.id,
72            key: f.name,
73            value: f.value,
74            timestamp: f.time as u64,
75        }
76    }
77}
78
79#[derive(Debug)]
80pub struct Transaction {
81    pub(crate) name: String,
82    pub(crate) expected: String,
83    pub(crate) target: String,
84    pub(crate) t_id: i64,
85    #[allow(dead_code)]
86    pub(crate) status: String,
87}
88
89impl From<Transaction> for fsipc::legacy::Transaction {
90    fn from(f: Transaction) -> Self {
91        // t_id is a number, should be i64 according to sqlite, but was u64 on our wire
92        // interface.
93        #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
94        Self {
95            key: f.name,
96            expected: f.expected,
97            target: f.target,
98            t_id: f.t_id as u64,
99        }
100    }
101}
102
103pub type ValueMap = HashMap<u32, String>;
104
105#[derive(Debug, Eq, PartialEq)]
106pub enum SensorMode {
107    ReadOnly,
108    ReadWrite,
109    WriteOnly,
110}
111impl SensorMode {
112    // Convenience function for stringification
113    #[must_use]
114    pub const fn as_str(&self) -> &str {
115        match self {
116            Self::ReadOnly => "ro",
117            Self::ReadWrite => "rw",
118            Self::WriteOnly => "wo",
119        }
120    }
121    // Map a string to a value, wrapped in an option.
122    // Makes the code that reads from the database easier, and keeps the string reps in this
123    // enum impl
124    #[must_use]
125    pub fn maybe_from_str(value: &str) -> Option<Self> {
126        match value {
127            "ro" => Some(Self::ReadOnly),
128            "rw" => Some(Self::ReadWrite),
129            "wo" => Some(Self::WriteOnly),
130            _ => None,
131        }
132    }
133}
134
135impl From<fsipc::logger1::SensorMode> for SensorMode {
136    fn from(other: fsipc::logger1::SensorMode) -> Self {
137        match other {
138            fsipc::logger1::SensorMode::ReadOnly => Self::ReadOnly,
139            fsipc::logger1::SensorMode::ReadWrite => Self::ReadWrite,
140            fsipc::logger1::SensorMode::WriteOnly => Self::WriteOnly,
141        }
142    }
143}
144
145// fsipc does not import this type for obvious reasons.
146// thus we can only implement "Into" but not "From"
147#[allow(clippy::from_over_into)]
148impl Into<fsipc::logger1::SensorMode> for SensorMode {
149    fn into(self) -> fsipc::logger1::SensorMode {
150        match self {
151            Self::ReadOnly => fsipc::logger1::SensorMode::ReadOnly,
152            Self::ReadWrite => fsipc::logger1::SensorMode::ReadWrite,
153            Self::WriteOnly => fsipc::logger1::SensorMode::WriteOnly,
154        }
155    }
156}
157
158#[test]
159fn enum_roundtrip() {
160    fn around(b: &SensorMode) -> SensorMode {
161        let sval = b.as_str();
162        SensorMode::maybe_from_str(sval).unwrap()
163    }
164    assert_eq!(SensorMode::ReadOnly, around(&SensorMode::ReadOnly));
165    assert_eq!(SensorMode::ReadWrite, around(&SensorMode::ReadWrite));
166    assert_eq!(SensorMode::WriteOnly, around(&SensorMode::WriteOnly));
167}
168
169#[derive(Debug)]
170pub struct Metadata {
171    pub n: String,
172    pub u: Option<String>,
173    pub name: Option<String>,
174    pub description: Option<String>,
175    pub value_map: Option<ValueMap>,
176    pub mode: Option<SensorMode>,
177    pub row: Option<Vec<DataType>>,
178}
179
180#[derive(Debug)]
181pub struct MetadataBuilder {
182    n: String,
183    u: Option<String>,
184    name: Option<String>,
185    description: Option<String>,
186    value_map: Option<ValueMap>,
187    mode: Option<SensorMode>,
188    row: Option<Vec<DataType>>,
189}
190
191impl Metadata {
192    #[must_use]
193    pub fn builder(n: impl Into<String>) -> MetadataBuilder {
194        MetadataBuilder::new(n.into())
195    }
196}
197
198/// Metadata builder, used to slim down code that creates a Metadata object
199impl MetadataBuilder {
200    #[must_use]
201    pub const fn new(n: String) -> Self {
202        Self {
203            n,
204            u: None,
205            name: None,
206            description: None,
207            value_map: None,
208            mode: None,
209            row: None,
210        }
211    }
212
213    #[must_use]
214    pub fn name(mut self, name: String) -> Self {
215        self.name = Some(name);
216        self
217    }
218    #[must_use]
219    pub fn unit(mut self, u: String) -> Self {
220        self.u = Some(u);
221        self
222    }
223    #[must_use]
224    pub fn description(mut self, description: String) -> Self {
225        self.description = Some(description);
226        self
227    }
228
229    #[must_use]
230    pub fn value_map(mut self, value_map: ValueMap) -> Self {
231        self.value_map = Some(value_map);
232        self
233    }
234
235    #[must_use]
236    pub const fn mode(mut self, mode: SensorMode) -> Self {
237        self.mode = Some(mode);
238        self
239    }
240
241    #[must_use]
242    pub fn mode_string(mut self, value: &str) -> Self {
243        self.mode = SensorMode::maybe_from_str(value);
244        self
245    }
246
247    /// Parse a value map from a string.
248    /// Does not return errors as it is assumed that the consumer of such an error would not be
249    /// happy.
250    #[must_use]
251    pub fn value_map_string(mut self, value: String) -> Self {
252        let value_map = serde_json::from_str::<ValueMap>(&value);
253        if value_map.is_err() {
254            error!("Error decoding json: {:?}", value);
255        };
256        self.value_map = value_map.ok();
257        self
258    }
259
260    /// Parse a row metadata from a string.
261    /// Does not return errors as it is assumed that the consumer of such an error would not be
262    /// happy.
263    #[must_use]
264    pub fn row_string(mut self, value: &str) -> Self {
265        self.row = DataType::vec_from_str(value).ok();
266        self
267    }
268
269    #[must_use]
270    pub fn pair(self, tag: &str, value: String) -> Self {
271        match tag {
272            "unit" => self.unit(value),
273            "description" => self.description(value),
274            "name" => self.name(value),
275            "enum" => self.value_map_string(value),
276            "mode" => self.mode_string(&value),
277            "row" => self.row_string(&value),
278            _ => {
279                error!("Strange decoding pair? {:?} {:?}", tag, &value);
280                self
281            }
282        }
283    }
284
285    #[must_use]
286    pub fn build(self) -> Metadata {
287        let Self {
288            n,
289            u,
290            name,
291            description,
292            value_map,
293            mode,
294            row,
295        } = self;
296        Metadata {
297            n,
298            u,
299            name,
300            description,
301            value_map,
302            mode,
303            row,
304        }
305    }
306}
307
308/// A helper for cleanup results to be printed to logs from the application.
309#[derive(Debug, Default)]
310pub struct CleanResult {
311    pub trans_failed: i32,
312    pub trans_old: i32,
313    pub data_deleted: i32,
314}
315
316impl std::fmt::Display for CleanResult {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        write!(
319            f,
320            "transactions_failed={}, transactions_old={}, data_deleted={}",
321            self.trans_failed, self.trans_old, self.data_deleted,
322        )
323    }
324}
325
326/// A helper for statistics to be printed to logs from the application.
327#[derive(Debug, Default)]
328pub struct Statistics {
329    pub metrics: i32,
330    pub internal: i32,
331    pub removed: i32,
332    pub timefail: i32,
333    pub transactions: i32,
334    pub buffered: i32,
335}
336
337impl std::fmt::Display for Statistics {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        write!(
340            f,
341            "metrics={}, internal={}, removed={}, timefail={}, transactions={}, buffered={}",
342            self.metrics,
343            self.internal,
344            self.removed,
345            self.timefail,
346            self.transactions,
347            self.buffered
348        )
349    }
350}