Skip to main content

moddef_core/
value.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Decoded value model (spec §8, §13). The no_std core surfaces [`Value`]
4//! (no heap: strings decode into caller buffers, flags stay a raw mask with
5//! name iteration via the descriptor); the `alloc` feature adds the owned
6//! [`DecodedValue`] mirroring the Go/TS union.
7
8use crate::desc::{FieldDesc, PointDesc, ValueKind};
9
10/// A reading that may be a device-reported "no data" sentinel (§8.4).
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum Reading<T> {
13    Value(T),
14    Unavailable(&'static str),
15}
16
17impl<T> Reading<T> {
18    pub fn value(self) -> Option<T> {
19        match self {
20            Reading::Value(v) => Some(v),
21            Reading::Unavailable(_) => None,
22        }
23    }
24}
25
26/// Heap-free decoded value.
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub enum Value {
29    Bool(bool),
30    U64(u64),
31    I64(i64),
32    F64(f64),
33    /// §13.2 flag set: raw mask; iterate names with [`flag_names`].
34    Flags(u64),
35    /// §13 packed sub-fields: raw window; extract with [`field_value`].
36    Fields(u64),
37    /// §8.5 epoch value (seconds or millis per the point's encoding).
38    DateTime(i64),
39    /// §8.4 sentinel hit. The meaning str borrows the descriptor table.
40    Unavailable,
41}
42
43impl Value {
44    pub fn as_f64(self) -> Option<f64> {
45        match self {
46            Value::F64(v) => Some(v),
47            Value::U64(v) => Some(v as f64),
48            Value::I64(v) => Some(v as f64),
49            Value::Bool(b) => Some(if b { 1.0 } else { 0.0 }),
50            _ => None,
51        }
52    }
53
54    pub fn as_i64(self) -> Option<i64> {
55        match self {
56            Value::I64(v) => Some(v),
57            Value::U64(v) => i64::try_from(v).ok(),
58            Value::Bool(b) => Some(b as i64),
59            Value::F64(v) => Some(v as i64),
60            Value::Flags(m) => i64::try_from(m).ok(),
61            Value::Fields(w) => i64::try_from(w).ok(),
62            Value::DateTime(t) => Some(t),
63            Value::Unavailable => None,
64        }
65    }
66}
67
68impl From<f64> for Value {
69    fn from(v: f64) -> Self {
70        Value::F64(v)
71    }
72}
73
74impl From<i64> for Value {
75    fn from(v: i64) -> Self {
76        Value::I64(v)
77    }
78}
79
80impl From<u64> for Value {
81    fn from(v: u64) -> Self {
82        Value::U64(v)
83    }
84}
85
86impl From<bool> for Value {
87    fn from(v: bool) -> Self {
88        Value::Bool(v)
89    }
90}
91
92/// Iterate the names of set flags for a FLAGS point (ascending bit order).
93pub fn flag_names<'a>(desc: &PointDesc<'a>, mask: u64) -> impl Iterator<Item = &'a str> {
94    let table: &'a [(u8, &'a str)] = match desc.value {
95        ValueKind::Flags(t) => t,
96        _ => &[],
97    };
98    table
99        .iter()
100        .filter(move |(bit, _)| mask & (1u64 << bit) != 0)
101        .map(|(_, name)| *name)
102}
103
104/// Extract one sub-field from a packed window raw value (§13).
105pub fn field_value(f: &FieldDesc<'_>, window: u64) -> u64 {
106    let mask = if f.bit_length >= 64 {
107        u64::MAX
108    } else {
109        (1u64 << f.bit_length) - 1
110    };
111    (window >> f.bit_offset) & mask
112}
113
114/// Owned decoded value (parity with the Go/TS union), `alloc` only.
115#[cfg(feature = "alloc")]
116#[derive(Clone, Debug, PartialEq)]
117pub enum DecodedValue {
118    Bool(bool),
119    U64(u64),
120    I64(i64),
121    F64(f64),
122    Str(alloc::string::String),
123    Bytes(alloc::vec::Vec<u8>),
124    /// Names of set flags.
125    Flags(alloc::vec::Vec<alloc::string::String>),
126    /// field_id -> numeric sub-value.
127    Fields(alloc::vec::Vec<(alloc::string::String, u64)>),
128    DateTime(i64),
129    Unavailable(alloc::string::String),
130}
131
132#[cfg(feature = "alloc")]
133impl DecodedValue {
134    pub fn as_f64(&self) -> Option<f64> {
135        match self {
136            DecodedValue::F64(v) => Some(*v),
137            DecodedValue::U64(v) => Some(*v as f64),
138            DecodedValue::I64(v) => Some(*v as f64),
139            DecodedValue::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
140            _ => None,
141        }
142    }
143
144    pub fn as_i64(&self) -> Option<i64> {
145        match self {
146            DecodedValue::I64(v) => Some(*v),
147            DecodedValue::U64(v) => i64::try_from(*v).ok(),
148            DecodedValue::Bool(b) => Some(*b as i64),
149            DecodedValue::F64(v) => Some(*v as i64),
150            DecodedValue::DateTime(t) => Some(*t),
151            _ => None,
152        }
153    }
154
155    pub fn is_unavailable(&self) -> bool {
156        matches!(self, DecodedValue::Unavailable(_))
157    }
158}