Skip to main content

panopticon_core/data/
value.rs

1use crate::imports::*;
2
3/// A scalar value held inside a [`StoreEntry::Var`].
4///
5/// `Value` is the narrow, concrete set of primitive types the pipeline
6/// understands: nothing, booleans, 64-bit integers, 64-bit floats, and
7/// owned text. Collections are represented by [`StoreEntry::Array`] and
8/// [`StoreEntry::Map`], not by additional `Value` variants. The enum is
9/// marked `#[non_exhaustive]` so future scalar additions do not break
10/// external match statements.
11///
12/// The `as_*` accessors narrow to a specific variant for callers that
13/// know what they are holding; each returns an [`AccessError::TypeMismatch`]
14/// on the wrong variant. Use [`Value::get_type`] to inspect the type tag
15/// without narrowing.
16#[derive(Debug, Clone, PartialEq)]
17#[non_exhaustive]
18pub enum Value {
19    /// The absent value.
20    Null,
21    /// A boolean.
22    Boolean(bool),
23    /// A 64-bit signed integer.
24    Integer(i64),
25    /// A 64-bit floating-point number.
26    Float(f64),
27    /// An owned UTF-8 string.
28    Text(String),
29}
30
31impl Value {
32    /// Narrows to a [`Text`](Self::Text), returning a borrowed string
33    /// slice. Fails with [`AccessError::TypeMismatch`] otherwise.
34    pub fn as_text(&self) -> Result<&str, AccessError> {
35        match self {
36            Value::Text(s) => Ok(s),
37            _ => Err(AccessError::TypeMismatch {
38                expected: "Text",
39                found: self.get_type().name(),
40            }),
41        }
42    }
43    /// Narrows to an [`Integer`](Self::Integer). Fails with
44    /// [`AccessError::TypeMismatch`] otherwise.
45    pub fn as_integer(&self) -> Result<i64, AccessError> {
46        match self {
47            Value::Integer(i) => Ok(*i),
48            _ => Err(AccessError::TypeMismatch {
49                expected: "Integer",
50                found: self.get_type().name(),
51            }),
52        }
53    }
54    /// Narrows to a [`Float`](Self::Float). Fails with
55    /// [`AccessError::TypeMismatch`] otherwise.
56    pub fn as_float(&self) -> Result<f64, AccessError> {
57        match self {
58            Value::Float(f) => Ok(*f),
59            _ => Err(AccessError::TypeMismatch {
60                expected: "Float",
61                found: self.get_type().name(),
62            }),
63        }
64    }
65    /// Narrows to a [`Boolean`](Self::Boolean). Fails with
66    /// [`AccessError::TypeMismatch`] otherwise.
67    pub fn as_boolean(&self) -> Result<bool, AccessError> {
68        match self {
69            Value::Boolean(b) => Ok(*b),
70            _ => Err(AccessError::TypeMismatch {
71                expected: "Boolean",
72                found: self.get_type().name(),
73            }),
74        }
75    }
76    /// Asserts the value is [`Null`](Self::Null). Fails with
77    /// [`AccessError::TypeMismatch`] otherwise.
78    pub fn as_null(&self) -> Result<(), AccessError> {
79        match self {
80            Value::Null => Ok(()),
81            _ => Err(AccessError::TypeMismatch {
82                expected: "Null",
83                found: self.get_type().name(),
84            }),
85        }
86    }
87}
88
89impl From<bool> for Value {
90    fn from(b: bool) -> Self {
91        Value::Boolean(b)
92    }
93}
94
95impl From<i64> for Value {
96    fn from(i: i64) -> Self {
97        Value::Integer(i)
98    }
99}
100
101impl From<f64> for Value {
102    fn from(f: f64) -> Self {
103        Value::Float(f)
104    }
105}
106
107impl From<&str> for Value {
108    fn from(s: &str) -> Self {
109        Value::Text(s.into())
110    }
111}
112
113impl From<String> for Value {
114    fn from(s: String) -> Self {
115        Value::Text(s)
116    }
117}
118
119impl Value {
120    /// Returns the [`Type`] tag that corresponds to this value.
121    pub fn get_type(&self) -> Type {
122        match self {
123            Value::Null => Type::Null,
124            Value::Boolean(_) => Type::Boolean,
125            Value::Integer(_) => Type::Integer,
126            Value::Float(_) => Type::Float,
127            Value::Text(_) => Type::Text,
128        }
129    }
130}
131
132impl std::fmt::Display for Value {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            Value::Null => write!(f, ""),
136            Value::Boolean(b) => write!(f, "{}", b),
137            Value::Integer(i) => write!(f, "{}", i),
138            Value::Float(fl) => write!(f, "{}", fl),
139            Value::Text(s) => write!(f, "{}", s),
140        }
141    }
142}
143
144impl std::cmp::Eq for Value {}
145
146impl std::hash::Hash for Value {
147    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
148        std::mem::discriminant(self).hash(state);
149        match self {
150            Value::Null => {}
151            Value::Boolean(b) => b.hash(state),
152            Value::Integer(i) => i.hash(state),
153            Value::Float(f) => f.to_bits().hash(state), // Hash float by its bit representation
154            Value::Text(s) => s.hash(state),
155        }
156    }
157}
158
159/// A type tag attached to a [`Value`] or declared on an [`InputSpec`] /
160/// [`OutputSpec`].
161///
162/// The scalar variants mirror the [`Value`] variants. The `Array`, `Map`,
163/// and `Any` variants are used by metadata specs and parameter
164/// resolution to describe compound shapes — a store entry is tagged with
165/// one of the scalar variants, but a declared input can accept a whole
166/// array or map. `Any` opts out of type checking for that input. The
167/// enum is marked `#[non_exhaustive]` so future type additions do not
168/// break external match statements.
169#[derive(Debug, Clone, PartialEq, Eq, Hash)]
170#[non_exhaustive]
171pub enum Type {
172    /// Matches [`Value::Null`].
173    Null,
174    /// Matches [`Value::Boolean`].
175    Boolean,
176    /// Matches [`Value::Integer`].
177    Integer,
178    /// Matches [`Value::Float`].
179    Float,
180    /// Matches [`Value::Text`].
181    Text,
182    /// Matches [`StoreEntry::Array`] — used in spec declarations.
183    Array,
184    /// Matches [`StoreEntry::Map`] — used in spec declarations.
185    Map,
186    /// Matches any entry — used in spec declarations to opt out of type
187    /// checking.
188    Any,
189}
190
191impl Type {
192    /// Returns the human-readable type name used in error messages and
193    /// diagnostics.
194    pub fn name(&self) -> &'static str {
195        match self {
196            Type::Null => "Null",
197            Type::Boolean => "Boolean",
198            Type::Integer => "Integer",
199            Type::Float => "Float",
200            Type::Text => "Text",
201            Type::Array => "Array",
202            Type::Map => "Map",
203            Type::Any => "Any",
204        }
205    }
206}
207
208impl std::fmt::Display for Type {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(f, "{}", self.name())
211    }
212}