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}