daedalus_data/
model.rs

1use serde::{Deserialize, Serialize};
2use std::borrow::Cow;
3
4/// Concrete runtime value.
5///
6/// ```
7/// use daedalus_data::model::Value;
8/// let v = Value::Int(42);
9/// assert_eq!(v, Value::Int(42));
10/// ```
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
12#[serde(tag = "type", content = "value")]
13pub enum Value {
14    Unit,
15    Bool(bool),
16    Int(i64),
17    Float(f64),
18    String(Cow<'static, str>),
19    Bytes(Cow<'static, [u8]>),
20    List(Vec<Value>),
21    Map(Vec<(Value, Value)>),
22    Tuple(Vec<Value>),
23    Struct(Vec<StructFieldValue>),
24    Enum(EnumValue),
25}
26
27/// Borrowed view of a value to avoid cloning large payloads.
28///
29/// ```
30/// use daedalus_data::model::{Value, ValueRef};
31/// let value = Value::String("hi".into());
32/// let view = ValueRef::from(&value);
33/// assert!(matches!(view, ValueRef::String("hi")));
34/// ```
35#[derive(Clone, Debug, PartialEq)]
36pub enum ValueRef<'a> {
37    Unit,
38    Bool(bool),
39    Int(i64),
40    Float(f64),
41    String(&'a str),
42    Bytes(&'a [u8]),
43    List(&'a [Value]),
44    Map(&'a [(Value, Value)]),
45    Tuple(&'a [Value]),
46    Struct(&'a [StructFieldValue]),
47    Enum {
48        name: &'a str,
49        value: Option<&'a Value>,
50    },
51}
52
53impl<'a> From<&'a Value> for ValueRef<'a> {
54    fn from(v: &'a Value) -> Self {
55        match v {
56            Value::Unit => ValueRef::Unit,
57            Value::Bool(b) => ValueRef::Bool(*b),
58            Value::Int(i) => ValueRef::Int(*i),
59            Value::Float(f) => ValueRef::Float(*f),
60            Value::String(s) => ValueRef::String(s),
61            Value::Bytes(b) => ValueRef::Bytes(b),
62            Value::List(items) => ValueRef::List(items),
63            Value::Map(entries) => ValueRef::Map(entries),
64            Value::Tuple(items) => ValueRef::Tuple(items),
65            Value::Struct(fields) => ValueRef::Struct(fields),
66            Value::Enum(ev) => ValueRef::Enum {
67                name: &ev.name,
68                value: ev.value.as_deref(),
69            },
70        }
71    }
72}
73
74impl<'a> ValueRef<'a> {
75    /// Convert a borrowed view into an owned value.
76    ///
77    /// ```
78    /// use daedalus_data::model::{Value, ValueRef};
79    /// let value = Value::Bool(true);
80    /// let owned = ValueRef::from(&value).into_owned();
81    /// assert_eq!(owned, Value::Bool(true));
82    /// ```
83    pub fn into_owned(self) -> Value {
84        match self {
85            ValueRef::Unit => Value::Unit,
86            ValueRef::Bool(b) => Value::Bool(b),
87            ValueRef::Int(i) => Value::Int(i),
88            ValueRef::Float(f) => Value::Float(f),
89            ValueRef::String(s) => Value::String(Cow::Owned(s.to_string())),
90            ValueRef::Bytes(b) => Value::Bytes(Cow::Owned(b.to_vec())),
91            ValueRef::List(items) => Value::List(items.to_vec()),
92            ValueRef::Map(entries) => Value::Map(entries.to_vec()),
93            ValueRef::Tuple(items) => Value::Tuple(items.to_vec()),
94            ValueRef::Struct(fields) => Value::Struct(fields.to_vec()),
95            ValueRef::Enum { name, value } => Value::Enum(EnumValue {
96                name: name.to_string(),
97                value: value.map(|v| Box::new(v.clone())),
98            }),
99        }
100    }
101}
102
103/// Static value type.
104///
105/// ```
106/// use daedalus_data::model::ValueType;
107/// let ty = ValueType::String;
108/// assert_eq!(ty, ValueType::String);
109/// ```
110#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
111pub enum ValueType {
112    Unit,
113    Bool,
114    /// 32-bit signed integer (stored in `Value::Int`).
115    I32,
116    /// 32-bit unsigned integer (stored in `Value::Int`).
117    U32,
118    Int,
119    /// 32-bit float (stored in `Value::Float`).
120    F32,
121    Float,
122    String,
123    Bytes,
124    // Future: more primitives can be added.
125}
126
127/// Type expression to describe structured types.
128///
129/// ```
130/// use daedalus_data::model::{TypeExpr, ValueType};
131/// let ty = TypeExpr::List(Box::new(TypeExpr::Scalar(ValueType::Int)));
132/// assert!(matches!(ty, TypeExpr::List(_)));
133/// ```
134#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
135pub enum TypeExpr {
136    Scalar(ValueType),
137    /// Opaque, named type identifier (e.g. plugin-defined types).
138    ///
139    /// This is useful when a type's internal structure isn't expressed in the graph
140    /// type system, but you still want strong matching and a meaningful label in UIs.
141    Opaque(String),
142    Optional(Box<TypeExpr>),
143    List(Box<TypeExpr>),
144    Map(Box<TypeExpr>, Box<TypeExpr>),
145    Tuple(Vec<TypeExpr>),
146    Struct(Vec<StructField>),
147    Enum(Vec<EnumVariant>),
148}
149
150impl TypeExpr {
151    /// Construct a scalar type expression.
152    pub fn scalar(t: ValueType) -> Self {
153        TypeExpr::Scalar(t)
154    }
155
156    /// Construct an opaque type expression.
157    pub fn opaque(name: impl Into<String>) -> Self {
158        TypeExpr::Opaque(name.into())
159    }
160
161    /// Wrap an optional type.
162    pub fn optional(inner: TypeExpr) -> Self {
163        TypeExpr::Optional(Box::new(inner))
164    }
165
166    /// Wrap a list type.
167    pub fn list(inner: TypeExpr) -> Self {
168        TypeExpr::List(Box::new(inner))
169    }
170
171    /// Wrap a map type.
172    pub fn map(key: TypeExpr, value: TypeExpr) -> Self {
173        TypeExpr::Map(Box::new(key), Box::new(value))
174    }
175
176    /// Construct a struct type.
177    pub fn r#struct(fields: Vec<StructField>) -> Self {
178        TypeExpr::Struct(fields)
179    }
180
181    /// Construct an enum type.
182    pub fn r#enum(variants: Vec<EnumVariant>) -> Self {
183        TypeExpr::Enum(variants)
184    }
185
186    /// Produce a canonically ordered representation for deterministic equality/ordering.
187    pub fn normalize(self) -> Self {
188        match self {
189            TypeExpr::Scalar(v) => TypeExpr::Scalar(v),
190            TypeExpr::Opaque(name) => TypeExpr::Opaque(name),
191            TypeExpr::Optional(inner) => TypeExpr::Optional(Box::new(inner.normalize())),
192            TypeExpr::List(inner) => TypeExpr::List(Box::new(inner.normalize())),
193            TypeExpr::Map(k, v) => TypeExpr::Map(Box::new(k.normalize()), Box::new(v.normalize())),
194            TypeExpr::Tuple(items) => {
195                TypeExpr::Tuple(items.into_iter().map(|t| t.normalize()).collect())
196            }
197            TypeExpr::Struct(mut fields) => {
198                for f in &mut fields {
199                    f.ty = f.ty.clone().normalize();
200                }
201                fields.sort_by(|a, b| a.name.cmp(&b.name));
202                TypeExpr::Struct(fields)
203            }
204            TypeExpr::Enum(mut variants) => {
205                for v in &mut variants {
206                    if let Some(t) = &v.ty {
207                        v.ty = Some(t.clone().normalize());
208                    }
209                }
210                variants.sort_by(|a, b| a.name.cmp(&b.name));
211                TypeExpr::Enum(variants)
212            }
213        }
214    }
215}
216
217/// Named field for struct types.
218///
219/// ```
220/// use daedalus_data::model::{StructField, TypeExpr, ValueType};
221/// let field = StructField { name: "count".into(), ty: TypeExpr::Scalar(ValueType::Int) };
222/// assert_eq!(field.name, "count");
223/// ```
224#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
225pub struct StructField {
226    pub name: String,
227    pub ty: TypeExpr,
228}
229
230/// Struct field value pairing.
231///
232/// ```
233/// use daedalus_data::model::{StructFieldValue, Value};
234/// let field = StructFieldValue { name: "ok".into(), value: Value::Bool(true) };
235/// assert_eq!(field.name, "ok");
236/// ```
237#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
238pub struct StructFieldValue {
239    pub name: String,
240    pub value: Value,
241}
242
243/// Enum variant with optional payload type.
244///
245/// ```
246/// use daedalus_data::model::{EnumVariant, TypeExpr, ValueType};
247/// let variant = EnumVariant { name: "Ready".into(), ty: Some(TypeExpr::Scalar(ValueType::Unit)) };
248/// assert_eq!(variant.name, "Ready");
249/// ```
250#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
251pub struct EnumVariant {
252    pub name: String,
253    pub ty: Option<TypeExpr>,
254}
255
256/// Enum value with optional payload.
257///
258/// ```
259/// use daedalus_data::model::{EnumValue, Value};
260/// let value = EnumValue { name: "Done".into(), value: Some(Box::new(Value::Unit)) };
261/// assert_eq!(value.name, "Done");
262/// ```
263#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
264pub struct EnumValue {
265    pub name: String,
266    pub value: Option<Box<Value>>,
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn struct_and_enum_ordering_are_deterministic() {
275        let a = TypeExpr::Struct(vec![
276            StructField {
277                name: "b".into(),
278                ty: TypeExpr::Scalar(ValueType::Bool),
279            },
280            StructField {
281                name: "a".into(),
282                ty: TypeExpr::Scalar(ValueType::Int),
283            },
284        ])
285        .normalize();
286        let fields = match a {
287            TypeExpr::Struct(f) => f,
288            _ => unreachable!(),
289        };
290        assert_eq!(fields[0].name, "a");
291
292        let variants = TypeExpr::Enum(vec![
293            EnumVariant {
294                name: "z".into(),
295                ty: None,
296            },
297            EnumVariant {
298                name: "a".into(),
299                ty: Some(TypeExpr::Scalar(ValueType::String)),
300            },
301        ])
302        .normalize();
303        let variants = match variants {
304            TypeExpr::Enum(v) => v,
305            _ => unreachable!(),
306        };
307        assert_eq!(variants[0].name, "a");
308    }
309
310    #[test]
311    fn value_ref_round_trip_owned() {
312        let v = Value::Struct(vec![
313            StructFieldValue {
314                name: "a".into(),
315                value: Value::Int(1),
316            },
317            StructFieldValue {
318                name: "b".into(),
319                value: Value::String("hi".into()),
320            },
321        ]);
322        let view = ValueRef::from(&v);
323        let owned = view.into_owned();
324        assert_eq!(v, owned);
325    }
326}