Skip to main content

shape_wire/
value.rs

1//! Wire format value types
2//!
3//! These types represent the serializable subset of Shape values.
4//! Non-serializable runtime constructs (closures, references) are
5//! converted to their serializable representations.
6
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9
10/// Duration unit for time spans
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum DurationUnit {
14    Nanoseconds,
15    Microseconds,
16    Milliseconds,
17    Seconds,
18    Minutes,
19    Hours,
20    Days,
21    Weeks,
22}
23
24/// The universal wire format for Shape values
25///
26/// This enum represents all Shape values in a serializable form.
27/// It is the core data structure exchanged between components.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub enum WireValue {
30    /// Null/None value
31    Null,
32
33    /// Boolean value
34    Bool(bool),
35
36    /// 64-bit floating point number
37    Number(f64),
38
39    /// 64-bit signed integer
40    Integer(i64),
41
42    /// 8-bit signed integer (ABI-preserving native scalar)
43    I8(i8),
44    /// 8-bit unsigned integer (ABI-preserving native scalar)
45    U8(u8),
46    /// 16-bit signed integer (ABI-preserving native scalar)
47    I16(i16),
48    /// 16-bit unsigned integer (ABI-preserving native scalar)
49    U16(u16),
50    /// 32-bit signed integer (ABI-preserving native scalar)
51    I32(i32),
52    /// 32-bit unsigned integer (ABI-preserving native scalar)
53    U32(u32),
54    /// 64-bit signed integer (ABI-preserving native scalar)
55    I64(i64),
56    /// 64-bit unsigned integer (ABI-preserving native scalar)
57    U64(u64),
58    /// Pointer-width signed integer normalized to i64 for portability
59    Isize(i64),
60    /// Pointer-width unsigned integer normalized to u64 for portability
61    Usize(u64),
62    /// C pointer value normalized to u64 for portability
63    Ptr(u64),
64    /// 32-bit float (ABI-preserving native scalar)
65    F32(f32),
66
67    /// UTF-8 string
68    String(String),
69
70    /// Timestamp as Unix milliseconds (UTC)
71    Timestamp(i64),
72
73    /// Duration with unit
74    Duration { value: f64, unit: DurationUnit },
75
76    /// Homogeneous array of values
77    Array(Vec<WireValue>),
78
79    /// Object with string keys (ordered for deterministic serialization)
80    Object(BTreeMap<String, WireValue>),
81
82    /// Table data with Arrow IPC serialization
83    Table(WireTable),
84
85    /// Result type (Ok or Err)
86    Result { ok: bool, value: Box<WireValue> },
87
88    /// Range value
89    Range {
90        start: Option<Box<WireValue>>,
91        end: Option<Box<WireValue>>,
92        inclusive: bool,
93    },
94
95    /// Function reference (name only, not callable)
96    FunctionRef { name: String },
97
98    /// Print result with structured spans
99    PrintResult(crate::print_result::WirePrintResult),
100}
101
102/// Wire format for table data
103///
104/// Stores Arrow IPC bytes for exact schema + data roundtripping.
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct WireTable {
107    /// Arrow IPC bytes for a single RecordBatch
108    pub ipc_bytes: Vec<u8>,
109    /// Optional type name (e.g., "Candle", "SensorReading")
110    pub type_name: Option<String>,
111    /// Optional schema id for typed tables
112    pub schema_id: Option<u32>,
113    /// Number of rows
114    pub row_count: usize,
115    /// Number of columns
116    pub column_count: usize,
117}
118
119/// Column data in a wire series
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub enum WireColumn {
122    /// Array of f64 values (may contain NaN for missing)
123    Numbers(Vec<f64>),
124
125    /// Array of i64 values
126    Integers(Vec<i64>),
127
128    /// Array of boolean values
129    Booleans(Vec<bool>),
130
131    /// Array of strings
132    Strings(Vec<String>),
133
134    /// Array of nested objects
135    Objects(Vec<WireValue>),
136}
137
138impl WireValue {
139    /// Create a null value
140    pub fn null() -> Self {
141        WireValue::Null
142    }
143
144    /// Check if this value is null
145    pub fn is_null(&self) -> bool {
146        matches!(self, WireValue::Null)
147    }
148
149    /// Try to get this value as a number
150    pub fn as_number(&self) -> Option<f64> {
151        match self {
152            WireValue::Number(n) => Some(*n),
153            WireValue::Integer(i) => Some(*i as f64),
154            WireValue::I8(v) => Some(*v as f64),
155            WireValue::U8(v) => Some(*v as f64),
156            WireValue::I16(v) => Some(*v as f64),
157            WireValue::U16(v) => Some(*v as f64),
158            WireValue::I32(v) => Some(*v as f64),
159            WireValue::U32(v) => Some(*v as f64),
160            // Keep 64-bit/native-width integers exact; callers should use
161            // type-specific accessors instead of lossy number coercion.
162            WireValue::I64(_)
163            | WireValue::U64(_)
164            | WireValue::Isize(_)
165            | WireValue::Usize(_)
166            | WireValue::Ptr(_) => None,
167            WireValue::F32(v) => Some(*v as f64),
168            _ => None,
169        }
170    }
171
172    /// Try to get this value as a string
173    pub fn as_str(&self) -> Option<&str> {
174        match self {
175            WireValue::String(s) => Some(s),
176            _ => None,
177        }
178    }
179
180    /// Try to get this value as a boolean
181    pub fn as_bool(&self) -> Option<bool> {
182        match self {
183            WireValue::Bool(b) => Some(*b),
184            _ => None,
185        }
186    }
187
188    /// Get the type name for display purposes
189    pub fn type_name(&self) -> &'static str {
190        match self {
191            WireValue::Null => "Null",
192            WireValue::Bool(_) => "Bool",
193            WireValue::Number(_) => "Number",
194            WireValue::Integer(_) => "Integer",
195            WireValue::I8(_) => "i8",
196            WireValue::U8(_) => "u8",
197            WireValue::I16(_) => "i16",
198            WireValue::U16(_) => "u16",
199            WireValue::I32(_) => "i32",
200            WireValue::U32(_) => "u32",
201            WireValue::I64(_) => "i64",
202            WireValue::U64(_) => "u64",
203            WireValue::Isize(_) => "isize",
204            WireValue::Usize(_) => "usize",
205            WireValue::Ptr(_) => "ptr",
206            WireValue::F32(_) => "f32",
207            WireValue::String(_) => "String",
208            WireValue::Timestamp(_) => "Timestamp",
209            WireValue::Duration { .. } => "Duration",
210            WireValue::Array(_) => "Array",
211            WireValue::Object(_) => "Object",
212            WireValue::Table(_) => "Table",
213            WireValue::Result { .. } => "Result",
214            WireValue::Range { .. } => "Range",
215            WireValue::FunctionRef { .. } => "Function",
216            WireValue::PrintResult(_) => "PrintResult",
217        }
218    }
219}
220
221impl WireTable {
222    /// Create an empty table
223    pub fn empty() -> Self {
224        WireTable {
225            ipc_bytes: Vec::new(),
226            type_name: None,
227            schema_id: None,
228            row_count: 0,
229            column_count: 0,
230        }
231    }
232}
233
234impl WireColumn {
235    /// Get the number of elements in this column
236    pub fn len(&self) -> usize {
237        match self {
238            WireColumn::Numbers(v) => v.len(),
239            WireColumn::Integers(v) => v.len(),
240            WireColumn::Booleans(v) => v.len(),
241            WireColumn::Strings(v) => v.len(),
242            WireColumn::Objects(v) => v.len(),
243        }
244    }
245
246    /// Check if the column is empty
247    pub fn is_empty(&self) -> bool {
248        self.len() == 0
249    }
250
251    /// Get the element type name
252    pub fn element_type(&self) -> &'static str {
253        match self {
254            WireColumn::Numbers(_) => "Number",
255            WireColumn::Integers(_) => "Integer",
256            WireColumn::Booleans(_) => "Bool",
257            WireColumn::Strings(_) => "String",
258            WireColumn::Objects(_) => "Object",
259        }
260    }
261}
262
263// Conversion from common types
264impl From<bool> for WireValue {
265    fn from(b: bool) -> Self {
266        WireValue::Bool(b)
267    }
268}
269
270impl From<f64> for WireValue {
271    fn from(n: f64) -> Self {
272        WireValue::Number(n)
273    }
274}
275
276impl From<i64> for WireValue {
277    fn from(n: i64) -> Self {
278        WireValue::Integer(n)
279    }
280}
281
282impl From<u64> for WireValue {
283    fn from(n: u64) -> Self {
284        WireValue::U64(n)
285    }
286}
287
288impl From<i32> for WireValue {
289    fn from(n: i32) -> Self {
290        WireValue::I32(n)
291    }
292}
293
294impl From<u32> for WireValue {
295    fn from(n: u32) -> Self {
296        WireValue::U32(n)
297    }
298}
299
300impl From<i16> for WireValue {
301    fn from(n: i16) -> Self {
302        WireValue::I16(n)
303    }
304}
305
306impl From<u16> for WireValue {
307    fn from(n: u16) -> Self {
308        WireValue::U16(n)
309    }
310}
311
312impl From<i8> for WireValue {
313    fn from(n: i8) -> Self {
314        WireValue::I8(n)
315    }
316}
317
318impl From<u8> for WireValue {
319    fn from(n: u8) -> Self {
320        WireValue::U8(n)
321    }
322}
323
324impl From<f32> for WireValue {
325    fn from(n: f32) -> Self {
326        WireValue::F32(n)
327    }
328}
329
330impl From<String> for WireValue {
331    fn from(s: String) -> Self {
332        WireValue::String(s)
333    }
334}
335
336impl From<&str> for WireValue {
337    fn from(s: &str) -> Self {
338        WireValue::String(s.to_string())
339    }
340}
341
342impl<T: Into<WireValue>> From<Vec<T>> for WireValue {
343    fn from(v: Vec<T>) -> Self {
344        WireValue::Array(v.into_iter().map(Into::into).collect())
345    }
346}
347
348impl<T: Into<WireValue>> From<Option<T>> for WireValue {
349    fn from(opt: Option<T>) -> Self {
350        match opt {
351            Some(v) => v.into(),
352            None => WireValue::Null,
353        }
354    }
355}
356
357/// Convert from serde_json::Value to WireValue
358///
359/// This allows creating envelopes from JSON values for display purposes.
360/// Note: This conversion is lossy - JSON doesn't have type information
361/// for things like Timestamp vs Integer, so we use heuristics.
362impl From<serde_json::Value> for WireValue {
363    fn from(json: serde_json::Value) -> Self {
364        match json {
365            serde_json::Value::Null => WireValue::Null,
366            serde_json::Value::Bool(b) => WireValue::Bool(b),
367            serde_json::Value::Number(n) => {
368                if let Some(i) = n.as_i64() {
369                    WireValue::Integer(i)
370                } else if let Some(u) = n.as_u64() {
371                    WireValue::U64(u)
372                } else if let Some(f) = n.as_f64() {
373                    WireValue::Number(f)
374                } else {
375                    WireValue::Null
376                }
377            }
378            serde_json::Value::String(s) => WireValue::String(s),
379            serde_json::Value::Array(arr) => {
380                WireValue::Array(arr.into_iter().map(WireValue::from).collect())
381            }
382            serde_json::Value::Object(obj) => {
383                // Regular object
384                let map: BTreeMap<String, WireValue> = obj
385                    .into_iter()
386                    .map(|(k, v)| (k, WireValue::from(v)))
387                    .collect();
388                WireValue::Object(map)
389            }
390        }
391    }
392}
393
394impl From<&serde_json::Value> for WireValue {
395    fn from(json: &serde_json::Value) -> Self {
396        WireValue::from(json.clone())
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn test_wire_value_type_names() {
406        assert_eq!(WireValue::Null.type_name(), "Null");
407        assert_eq!(WireValue::Bool(true).type_name(), "Bool");
408        assert_eq!(WireValue::Number(1.0).type_name(), "Number");
409        assert_eq!(WireValue::String("test".into()).type_name(), "String");
410    }
411
412    #[test]
413    fn test_wire_value_conversions() {
414        let v: WireValue = 42.0.into();
415        assert_eq!(v.as_number(), Some(42.0));
416
417        let v = WireValue::I64(42);
418        assert_eq!(v.as_number(), None, "i64 should not coerce to number");
419
420        let v: WireValue = "hello".into();
421        assert_eq!(v.as_str(), Some("hello"));
422
423        let v: WireValue = true.into();
424        assert_eq!(v.as_bool(), Some(true));
425    }
426
427    #[test]
428    fn test_wire_series_empty() {
429        let series = WireTable::empty();
430        assert_eq!(series.row_count, 0);
431        assert_eq!(series.column_count, 0);
432        assert!(series.ipc_bytes.is_empty());
433    }
434
435    #[test]
436    fn test_json_to_wire_conversion() {
437        // Null
438        let json = serde_json::json!(null);
439        let wire = WireValue::from(json);
440        assert!(wire.is_null());
441
442        // Bool
443        let json = serde_json::json!(true);
444        let wire = WireValue::from(json);
445        assert_eq!(wire.as_bool(), Some(true));
446
447        // Integer
448        let json = serde_json::json!(42);
449        let wire = WireValue::from(json);
450        assert!(matches!(wire, WireValue::Integer(42)));
451
452        // Float
453        let json = serde_json::json!(3.14);
454        let wire = WireValue::from(json);
455        assert!(matches!(wire, WireValue::Number(n) if (n - 3.14).abs() < 0.001));
456
457        // String
458        let json = serde_json::json!("hello");
459        let wire = WireValue::from(json);
460        assert_eq!(wire.as_str(), Some("hello"));
461
462        // Array
463        let json = serde_json::json!([1, 2, 3]);
464        let wire = WireValue::from(json);
465        assert!(matches!(wire, WireValue::Array(arr) if arr.len() == 3));
466
467        // Object
468        let json = serde_json::json!({"x": 10, "y": 20});
469        let wire = WireValue::from(json);
470        if let WireValue::Object(map) = wire {
471            assert_eq!(map.len(), 2);
472        } else {
473            panic!("Expected Object");
474        }
475    }
476}