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    /// Structured content node for rich rendering
102    Content(shape_value::content::ContentNode),
103}
104
105/// Wire format for table data
106///
107/// Stores Arrow IPC bytes for exact schema + data roundtripping.
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct WireTable {
110    /// Arrow IPC bytes for a single RecordBatch
111    pub ipc_bytes: Vec<u8>,
112    /// Optional type name (e.g., "Candle", "SensorReading")
113    pub type_name: Option<String>,
114    /// Optional schema id for typed tables
115    pub schema_id: Option<u32>,
116    /// Number of rows
117    pub row_count: usize,
118    /// Number of columns
119    pub column_count: usize,
120}
121
122/// Column data in a wire series
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub enum WireColumn {
125    /// Array of f64 values (may contain NaN for missing)
126    Numbers(Vec<f64>),
127
128    /// Array of i64 values
129    Integers(Vec<i64>),
130
131    /// Array of boolean values
132    Booleans(Vec<bool>),
133
134    /// Array of strings
135    Strings(Vec<String>),
136
137    /// Array of nested objects
138    Objects(Vec<WireValue>),
139}
140
141impl WireValue {
142    /// Create a null value
143    pub fn null() -> Self {
144        WireValue::Null
145    }
146
147    /// Check if this value is null
148    pub fn is_null(&self) -> bool {
149        matches!(self, WireValue::Null)
150    }
151
152    /// Try to get this value as a number
153    pub fn as_number(&self) -> Option<f64> {
154        match self {
155            WireValue::Number(n) => Some(*n),
156            WireValue::Integer(i) => Some(*i as f64),
157            WireValue::I8(v) => Some(*v as f64),
158            WireValue::U8(v) => Some(*v as f64),
159            WireValue::I16(v) => Some(*v as f64),
160            WireValue::U16(v) => Some(*v as f64),
161            WireValue::I32(v) => Some(*v as f64),
162            WireValue::U32(v) => Some(*v as f64),
163            // Keep 64-bit/native-width integers exact; callers should use
164            // type-specific accessors instead of lossy number coercion.
165            WireValue::I64(_)
166            | WireValue::U64(_)
167            | WireValue::Isize(_)
168            | WireValue::Usize(_)
169            | WireValue::Ptr(_) => None,
170            WireValue::F32(v) => Some(*v as f64),
171            _ => None,
172        }
173    }
174
175    /// Try to get this value as a string
176    pub fn as_str(&self) -> Option<&str> {
177        match self {
178            WireValue::String(s) => Some(s),
179            _ => None,
180        }
181    }
182
183    /// Try to get this value as a boolean
184    pub fn as_bool(&self) -> Option<bool> {
185        match self {
186            WireValue::Bool(b) => Some(*b),
187            _ => None,
188        }
189    }
190
191    /// Get the type name for display purposes
192    pub fn type_name(&self) -> &'static str {
193        match self {
194            WireValue::Null => "Null",
195            WireValue::Bool(_) => "Bool",
196            WireValue::Number(_) => "Number",
197            WireValue::Integer(_) => "Integer",
198            WireValue::I8(_) => "i8",
199            WireValue::U8(_) => "u8",
200            WireValue::I16(_) => "i16",
201            WireValue::U16(_) => "u16",
202            WireValue::I32(_) => "i32",
203            WireValue::U32(_) => "u32",
204            WireValue::I64(_) => "i64",
205            WireValue::U64(_) => "u64",
206            WireValue::Isize(_) => "isize",
207            WireValue::Usize(_) => "usize",
208            WireValue::Ptr(_) => "ptr",
209            WireValue::F32(_) => "f32",
210            WireValue::String(_) => "String",
211            WireValue::Timestamp(_) => "Timestamp",
212            WireValue::Duration { .. } => "Duration",
213            WireValue::Array(_) => "Array",
214            WireValue::Object(_) => "Object",
215            WireValue::Table(_) => "Table",
216            WireValue::Result { .. } => "Result",
217            WireValue::Range { .. } => "Range",
218            WireValue::FunctionRef { .. } => "Function",
219            WireValue::PrintResult(_) => "PrintResult",
220            WireValue::Content(_) => "Content",
221        }
222    }
223}
224
225impl WireTable {
226    /// Create an empty table
227    pub fn empty() -> Self {
228        WireTable {
229            ipc_bytes: Vec::new(),
230            type_name: None,
231            schema_id: None,
232            row_count: 0,
233            column_count: 0,
234        }
235    }
236}
237
238impl WireColumn {
239    /// Get the number of elements in this column
240    pub fn len(&self) -> usize {
241        match self {
242            WireColumn::Numbers(v) => v.len(),
243            WireColumn::Integers(v) => v.len(),
244            WireColumn::Booleans(v) => v.len(),
245            WireColumn::Strings(v) => v.len(),
246            WireColumn::Objects(v) => v.len(),
247        }
248    }
249
250    /// Check if the column is empty
251    pub fn is_empty(&self) -> bool {
252        self.len() == 0
253    }
254
255    /// Get the element type name
256    pub fn element_type(&self) -> &'static str {
257        match self {
258            WireColumn::Numbers(_) => "Number",
259            WireColumn::Integers(_) => "Integer",
260            WireColumn::Booleans(_) => "Bool",
261            WireColumn::Strings(_) => "String",
262            WireColumn::Objects(_) => "Object",
263        }
264    }
265}
266
267// Conversion from common types
268impl From<bool> for WireValue {
269    fn from(b: bool) -> Self {
270        WireValue::Bool(b)
271    }
272}
273
274impl From<f64> for WireValue {
275    fn from(n: f64) -> Self {
276        WireValue::Number(n)
277    }
278}
279
280impl From<i64> for WireValue {
281    fn from(n: i64) -> Self {
282        WireValue::Integer(n)
283    }
284}
285
286impl From<u64> for WireValue {
287    fn from(n: u64) -> Self {
288        WireValue::U64(n)
289    }
290}
291
292impl From<i32> for WireValue {
293    fn from(n: i32) -> Self {
294        WireValue::I32(n)
295    }
296}
297
298impl From<u32> for WireValue {
299    fn from(n: u32) -> Self {
300        WireValue::U32(n)
301    }
302}
303
304impl From<i16> for WireValue {
305    fn from(n: i16) -> Self {
306        WireValue::I16(n)
307    }
308}
309
310impl From<u16> for WireValue {
311    fn from(n: u16) -> Self {
312        WireValue::U16(n)
313    }
314}
315
316impl From<i8> for WireValue {
317    fn from(n: i8) -> Self {
318        WireValue::I8(n)
319    }
320}
321
322impl From<u8> for WireValue {
323    fn from(n: u8) -> Self {
324        WireValue::U8(n)
325    }
326}
327
328impl From<f32> for WireValue {
329    fn from(n: f32) -> Self {
330        WireValue::F32(n)
331    }
332}
333
334impl From<String> for WireValue {
335    fn from(s: String) -> Self {
336        WireValue::String(s)
337    }
338}
339
340impl From<&str> for WireValue {
341    fn from(s: &str) -> Self {
342        WireValue::String(s.to_string())
343    }
344}
345
346impl<T: Into<WireValue>> From<Vec<T>> for WireValue {
347    fn from(v: Vec<T>) -> Self {
348        WireValue::Array(v.into_iter().map(Into::into).collect())
349    }
350}
351
352impl<T: Into<WireValue>> From<Option<T>> for WireValue {
353    fn from(opt: Option<T>) -> Self {
354        match opt {
355            Some(v) => v.into(),
356            None => WireValue::Null,
357        }
358    }
359}
360
361/// Convert from serde_json::Value to WireValue
362///
363/// This allows creating envelopes from JSON values for display purposes.
364/// Note: This conversion is lossy - JSON doesn't have type information
365/// for things like Timestamp vs Integer, so we use heuristics.
366impl From<serde_json::Value> for WireValue {
367    fn from(json: serde_json::Value) -> Self {
368        match json {
369            serde_json::Value::Null => WireValue::Null,
370            serde_json::Value::Bool(b) => WireValue::Bool(b),
371            serde_json::Value::Number(n) => {
372                if let Some(i) = n.as_i64() {
373                    WireValue::Integer(i)
374                } else if let Some(u) = n.as_u64() {
375                    WireValue::U64(u)
376                } else if let Some(f) = n.as_f64() {
377                    WireValue::Number(f)
378                } else {
379                    WireValue::Null
380                }
381            }
382            serde_json::Value::String(s) => WireValue::String(s),
383            serde_json::Value::Array(arr) => {
384                WireValue::Array(arr.into_iter().map(WireValue::from).collect())
385            }
386            serde_json::Value::Object(obj) => {
387                // Regular object
388                let map: BTreeMap<String, WireValue> = obj
389                    .into_iter()
390                    .map(|(k, v)| (k, WireValue::from(v)))
391                    .collect();
392                WireValue::Object(map)
393            }
394        }
395    }
396}
397
398impl From<&serde_json::Value> for WireValue {
399    fn from(json: &serde_json::Value) -> Self {
400        WireValue::from(json.clone())
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    #[test]
409    fn test_wire_value_type_names() {
410        assert_eq!(WireValue::Null.type_name(), "Null");
411        assert_eq!(WireValue::Bool(true).type_name(), "Bool");
412        assert_eq!(WireValue::Number(1.0).type_name(), "Number");
413        assert_eq!(WireValue::String("test".into()).type_name(), "String");
414    }
415
416    #[test]
417    fn test_wire_value_conversions() {
418        let v: WireValue = 42.0.into();
419        assert_eq!(v.as_number(), Some(42.0));
420
421        let v = WireValue::I64(42);
422        assert_eq!(v.as_number(), None, "i64 should not coerce to number");
423
424        let v: WireValue = "hello".into();
425        assert_eq!(v.as_str(), Some("hello"));
426
427        let v: WireValue = true.into();
428        assert_eq!(v.as_bool(), Some(true));
429    }
430
431    #[test]
432    fn test_wire_series_empty() {
433        let series = WireTable::empty();
434        assert_eq!(series.row_count, 0);
435        assert_eq!(series.column_count, 0);
436        assert!(series.ipc_bytes.is_empty());
437    }
438
439    #[test]
440    fn test_json_to_wire_conversion() {
441        // Null
442        let json = serde_json::json!(null);
443        let wire = WireValue::from(json);
444        assert!(wire.is_null());
445
446        // Bool
447        let json = serde_json::json!(true);
448        let wire = WireValue::from(json);
449        assert_eq!(wire.as_bool(), Some(true));
450
451        // Integer
452        let json = serde_json::json!(42);
453        let wire = WireValue::from(json);
454        assert!(matches!(wire, WireValue::Integer(42)));
455
456        // Float
457        let json = serde_json::json!(3.14);
458        let wire = WireValue::from(json);
459        assert!(matches!(wire, WireValue::Number(n) if (n - 3.14).abs() < 0.001));
460
461        // String
462        let json = serde_json::json!("hello");
463        let wire = WireValue::from(json);
464        assert_eq!(wire.as_str(), Some("hello"));
465
466        // Array
467        let json = serde_json::json!([1, 2, 3]);
468        let wire = WireValue::from(json);
469        assert!(matches!(wire, WireValue::Array(arr) if arr.len() == 3));
470
471        // Object
472        let json = serde_json::json!({"x": 10, "y": 20});
473        let wire = WireValue::from(json);
474        if let WireValue::Object(map) = wire {
475            assert_eq!(map.len(), 2);
476        } else {
477            panic!("Expected Object");
478        }
479    }
480}