Skip to main content

somatize_core/
value.rs

1//! Typed values flowing between filters in a pipeline.
2//!
3//! [`Value`] variants: Tensor (f64 array with shape), JSON, Bytes, Empty.
4//! Values are serializable and content-addressable via [`CacheKey`].
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::sync::Arc;
9
10/// Typed values flowing between filters in a pipeline.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(tag = "type", content = "data")]
13#[non_exhaustive]
14pub enum Value {
15    /// Numeric tensor data (shape + flat data).
16    /// `values` is wrapped in [`Arc`] so that cloning a `Value` is O(1).
17    Tensor {
18        values: Arc<Vec<f64>>,
19        shape: Vec<usize>,
20    },
21
22    /// Structured JSON data (Arc-wrapped for cheap cloning).
23    Json(Arc<serde_json::Value>),
24
25    /// Raw bytes (Arc-wrapped for cheap cloning).
26    Bytes(Arc<Vec<u8>>),
27
28    /// Opaque serialized object (e.g. Python pickle).
29    /// Soma passes it through without interpreting the contents.
30    /// Used for efficient inter-filter data transfer when the producing
31    /// and consuming runtimes share a serialization format.
32    Object(Arc<Vec<u8>>),
33
34    /// Empty / void value
35    Empty,
36}
37
38impl Value {
39    pub fn tensor(values: Vec<f64>, shape: Vec<usize>) -> Self {
40        Self::Tensor {
41            values: Arc::new(values),
42            shape,
43        }
44    }
45
46    pub fn json(val: serde_json::Value) -> Self {
47        Self::Json(Arc::new(val))
48    }
49
50    pub fn bytes(data: Vec<u8>) -> Self {
51        Self::Bytes(Arc::new(data))
52    }
53
54    pub fn object(data: Vec<u8>) -> Self {
55        Self::Object(Arc::new(data))
56    }
57
58    pub fn is_empty(&self) -> bool {
59        matches!(self, Self::Empty)
60    }
61
62    /// Try to extract tensor data.
63    pub fn as_tensor(&self) -> Option<(&[f64], &[usize])> {
64        match self {
65            Self::Tensor { values, shape } => Some((values, shape)),
66            _ => None,
67        }
68    }
69
70    /// Try to extract JSON value.
71    pub fn as_json(&self) -> Option<&serde_json::Value> {
72        match self {
73            Self::Json(v) => Some(v),
74            _ => None,
75        }
76    }
77
78    /// Number of elements (for tensors) or bytes.
79    pub fn size(&self) -> usize {
80        match self {
81            Self::Tensor { values, .. } => values.len(),
82            Self::Json(v) => v.to_string().len(),
83            Self::Bytes(b) | Self::Object(b) => b.len(),
84            Self::Empty => 0,
85        }
86    }
87}
88
89impl fmt::Display for Value {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Self::Tensor { shape, values } => {
93                write!(f, "Tensor(shape={shape:?}, len={})", values.len())
94            }
95            Self::Json(v) => write!(f, "Json({v})"),
96            Self::Bytes(b) => write!(f, "Bytes(len={})", b.len()),
97            Self::Object(b) => write!(f, "Object(len={})", b.len()),
98            Self::Empty => write!(f, "Empty"),
99        }
100    }
101}
102
103impl From<Vec<f64>> for Value {
104    fn from(values: Vec<f64>) -> Self {
105        let len = values.len();
106        Self::Tensor {
107            values: Arc::new(values),
108            shape: vec![len],
109        }
110    }
111}
112
113impl From<serde_json::Value> for Value {
114    fn from(v: serde_json::Value) -> Self {
115        Self::Json(Arc::new(v))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use serde_json::json;
123
124    #[test]
125    fn tensor_creation_and_access() {
126        let v = Value::tensor(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]);
127        let (data, shape) = v.as_tensor().unwrap();
128        assert_eq!(data, &[1.0, 2.0, 3.0, 4.0]);
129        assert_eq!(shape, &[2, 2]);
130    }
131
132    #[test]
133    fn json_value() {
134        let v = Value::json(json!({"key": "value"}));
135        let j = v.as_json().unwrap();
136        assert_eq!(j["key"], "value");
137    }
138
139    #[test]
140    fn empty_value() {
141        let v = Value::Empty;
142        assert!(v.is_empty());
143        assert_eq!(v.size(), 0);
144    }
145
146    #[test]
147    fn from_vec_f64() {
148        let v: Value = vec![1.0, 2.0, 3.0].into();
149        let (data, shape) = v.as_tensor().unwrap();
150        assert_eq!(data, &[1.0, 2.0, 3.0]);
151        assert_eq!(shape, &[3]);
152    }
153
154    #[test]
155    fn display_formatting() {
156        let t = Value::tensor(vec![1.0, 2.0], vec![2]);
157        assert_eq!(t.to_string(), "Tensor(shape=[2], len=2)");
158
159        let e = Value::Empty;
160        assert_eq!(e.to_string(), "Empty");
161    }
162
163    #[test]
164    fn serde_roundtrip() {
165        let values = vec![
166            Value::tensor(vec![1.0, 2.0, 3.0], vec![3]),
167            Value::json(json!({"a": 1})),
168            Value::bytes(vec![0xDE, 0xAD]),
169            Value::Empty,
170        ];
171
172        for v in values {
173            let serialized = serde_json::to_string(&v).unwrap();
174            let deserialized: Value = serde_json::from_str(&serialized).unwrap();
175            assert_eq!(v, deserialized);
176        }
177    }
178
179    #[test]
180    fn size_returns_correct_values() {
181        assert_eq!(Value::tensor(vec![1.0; 100], vec![10, 10]).size(), 100);
182        assert_eq!(Value::bytes(vec![0; 50]).size(), 50);
183        assert!(Value::json(json!({"key": "val"})).size() > 0);
184    }
185}