Skip to main content

serializer/
types.rs

1//! Core data types for DX format
2//!
3//! This module defines the core value types for the DX **machine format** (binary).
4//! The machine format is optimized for zero-copy deserialization and runtime performance.
5//!
6//! # Format Comparison
7//!
8//! DX provides two serialization formats:
9//!
10//! | Format | Type | Use Case | Performance |
11//! |--------|------|----------|-------------|
12//! | **Machine** | [`DxValue`] | Binary, zero-copy, runtime | Fastest parsing |
13//! | **LLM** | [`DxLlmValue`](crate::llm::DxLlmValue) | Text, token-efficient, LLM context | 73%+ token savings |
14//!
15//! # When to Use This Module
16//!
17//! Use [`DxValue`] and the machine format when:
18//! - You need maximum parsing and serialization performance
19//! - You're working with binary data or network protocols
20//! - You want zero-copy deserialization for large datasets
21//! - You're building runtime data structures
22//!
23//! Use [`DxLlmValue`](crate::llm::DxLlmValue) when:
24//! - You're preparing data for LLM context windows
25//! - You need human-readable output
26//! - Token efficiency is more important than raw speed
27//!
28//! # Thread Safety
29//!
30//! All types in this module implement `Send + Sync` and can be safely shared
31//! between threads. See the compile-time assertions at the bottom of this module.
32
33use rustc_hash::FxHashMap;
34use std::fmt;
35
36/// The core value type for the DX **machine format** (binary, zero-copy).
37///
38/// `DxValue` represents all possible values in the DX binary format, optimized
39/// for maximum parsing performance and zero-copy deserialization.
40///
41/// # Relationship to DxLlmValue
42///
43/// DX provides two value types for different use cases:
44///
45/// - **`DxValue`** (this type): For the binary machine format. Use when you need
46///   maximum performance, zero-copy parsing, or runtime data structures.
47///
48/// - **[`DxLlmValue`](crate::llm::DxLlmValue)**: For the text LLM format. Use when
49///   preparing data for LLM context windows or when token efficiency matters.
50///
51/// The two types have different variant sets because they serve different purposes:
52/// - `DxValue` has separate `Int` and `Float` variants for type precision
53/// - `DxLlmValue` has a single `Num` variant since LLMs don't distinguish
54/// - `DxValue` has `Object` and `Table` for structured data
55/// - `DxLlmValue` has `Ref` for reference pointers in the Dx Serializer format
56///
57/// # When to Use DxValue
58///
59/// Choose `DxValue` when:
60/// - **Performance is critical**: Binary format parses faster than text
61/// - **Zero-copy is needed**: Large datasets can be memory-mapped
62/// - **Type precision matters**: Separate `Int`/`Float` types preserve precision
63/// - **Building runtime structures**: Objects and Tables provide efficient access
64///
65/// # Examples
66///
67/// ## Creating Values
68///
69/// ```rust
70/// use serializer::DxValue;
71///
72/// // Primitive values
73/// let null = DxValue::Null;
74/// let boolean = DxValue::Bool(true);
75/// let integer = DxValue::Int(42);
76/// let float = DxValue::Float(3.14);
77/// let string = DxValue::String("hello".to_string());
78/// ```
79///
80/// ## Working with Arrays
81///
82/// ```rust
83/// use serializer::{DxValue, DxArray};
84///
85/// let mut arr = DxArray::new();
86/// arr.values.push(DxValue::Int(1));
87/// arr.values.push(DxValue::Int(2));
88/// arr.values.push(DxValue::Int(3));
89///
90/// let array_value = DxValue::Array(arr);
91/// ```
92///
93/// ## Working with Objects
94///
95/// ```rust
96/// use serializer::{DxValue, DxObject};
97///
98/// let mut obj = DxObject::new();
99/// obj.insert("name".to_string(), DxValue::String("Alice".to_string()));
100/// obj.insert("age".to_string(), DxValue::Int(30));
101///
102/// let object_value = DxValue::Object(obj);
103/// ```
104///
105/// ## Type Inspection
106///
107/// ```rust
108/// use serializer::DxValue;
109///
110/// let value = DxValue::Int(42);
111/// assert_eq!(value.type_name(), "int");
112/// assert_eq!(value.as_int(), Some(42));
113/// ```
114///
115/// # Thread Safety
116///
117/// `DxValue` implements `Send + Sync` and can be safely shared between threads.
118/// This is verified at compile time via static assertions.
119///
120/// # See Also
121///
122/// - [`DxLlmValue`](crate::llm::DxLlmValue) - Value type for the LLM text format
123/// - [`DxArray`] - Array container type
124/// - [`DxObject`] - Object/map container type
125/// - [`DxTable`] - Schema-defined table type
126#[derive(Debug, Clone, PartialEq)]
127pub enum DxValue {
128    /// Null value, represented as `~` in DX format.
129    Null,
130    /// Boolean value, represented as `+` (true) or `-` (false) in DX format.
131    Bool(bool),
132    /// 64-bit signed integer.
133    ///
134    /// Note: [`DxLlmValue`](crate::llm::DxLlmValue) uses a single `Num` variant
135    /// for all numbers since LLMs don't distinguish integer vs float.
136    Int(i64),
137    /// 64-bit floating-point number.
138    ///
139    /// Note: [`DxLlmValue`](crate::llm::DxLlmValue) uses a single `Num` variant
140    /// for all numbers since LLMs don't distinguish integer vs float.
141    Float(f64),
142    /// String value. In machine format, strings don't require quotes.
143    String(String),
144    /// Array/List of values. See [`DxArray`] for details.
145    Array(DxArray),
146    /// Object/Map with key-value pairs. See [`DxObject`] for details.
147    Object(DxObject),
148    /// Table with schema-defined columns. See [`DxTable`] for details.
149    ///
150    /// Tables are a DX-specific feature for efficient tabular data representation.
151    Table(DxTable),
152    /// Reference to an anchor by index (`@N` in DX format).
153    ///
154    /// Anchors allow deduplication of repeated values in the document.
155    Ref(usize),
156}
157
158/// A DX array (inline or vertical)
159#[derive(Debug, Clone, PartialEq)]
160pub struct DxArray {
161    /// Values stored in insertion order.
162    pub values: Vec<DxValue>,
163    /// Whether this was a stream (>)
164    pub is_stream: bool,
165}
166
167impl DxArray {
168    /// Create an empty array value.
169    pub fn new() -> Self {
170        Self {
171            values: Vec::new(),
172            is_stream: false,
173        }
174    }
175
176    /// Create an empty array value with preallocated capacity.
177    pub fn with_capacity(cap: usize) -> Self {
178        Self {
179            values: Vec::with_capacity(cap),
180            is_stream: false,
181        }
182    }
183
184    /// Create a stream-style array from existing values.
185    pub fn stream(values: Vec<DxValue>) -> Self {
186        Self {
187            values,
188            is_stream: true,
189        }
190    }
191}
192
193impl Default for DxArray {
194    fn default() -> Self {
195        Self::new()
196    }
197}
198
199/// A DX object (key-value pairs)
200#[derive(Debug, Clone, PartialEq)]
201pub struct DxObject {
202    /// Ordered key-value pairs
203    pub fields: Vec<(String, DxValue)>,
204    /// Fast lookup map (key index)
205    lookup: FxHashMap<String, usize>,
206}
207
208impl DxObject {
209    /// Create an empty ordered object.
210    pub fn new() -> Self {
211        Self {
212            fields: Vec::new(),
213            lookup: FxHashMap::default(),
214        }
215    }
216
217    /// Create an empty ordered object with preallocated field capacity.
218    pub fn with_capacity(cap: usize) -> Self {
219        Self {
220            fields: Vec::with_capacity(cap),
221            lookup: FxHashMap::with_capacity_and_hasher(cap, Default::default()),
222        }
223    }
224
225    /// Insert or replace a field while preserving insertion order.
226    pub fn insert(&mut self, key: String, value: DxValue) {
227        if let Some(&idx) = self.lookup.get(&key) {
228            self.fields[idx].1 = value;
229        } else {
230            let idx = self.fields.len();
231            self.fields.push((key.clone(), value));
232            self.lookup.insert(key, idx);
233        }
234    }
235
236    /// Get a field value by key.
237    pub fn get(&self, key: &str) -> Option<&DxValue> {
238        self.lookup.get(key).map(|&idx| &self.fields[idx].1)
239    }
240
241    /// Iterate fields in insertion order.
242    pub fn iter(&self) -> impl Iterator<Item = &(String, DxValue)> {
243        self.fields.iter()
244    }
245}
246
247impl Default for DxObject {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253impl fmt::Display for DxObject {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        write!(f, "{{")?;
256        for (i, (k, v)) in self.fields.iter().enumerate() {
257            if i > 0 {
258                write!(f, ", ")?;
259            }
260            write!(f, "{}: {:?}", k, v)?;
261        }
262        write!(f, "}}")
263    }
264}
265
266/// A table with schema-defined columns
267#[derive(Debug, Clone, PartialEq)]
268pub struct DxTable {
269    /// Schema that defines column names and type hints.
270    pub schema: crate::schema::Schema,
271    /// Table rows, each matching the schema column count.
272    pub rows: Vec<Vec<DxValue>>,
273}
274
275impl DxTable {
276    /// Create an empty table for the provided schema.
277    pub fn new(schema: crate::schema::Schema) -> Self {
278        Self {
279            schema,
280            rows: Vec::new(),
281        }
282    }
283
284    /// Add a row after validating its width against the schema.
285    pub fn add_row(&mut self, row: Vec<DxValue>) -> Result<(), String> {
286        if row.len() != self.schema.columns.len() {
287            return Err(format!(
288                "Row length {} doesn't match schema length {}",
289                row.len(),
290                self.schema.columns.len()
291            ));
292        }
293        self.rows.push(row);
294        Ok(())
295    }
296
297    /// Return the number of rows currently stored in the table.
298    pub fn row_count(&self) -> usize {
299        self.rows.len()
300    }
301
302    /// Return the number of columns declared by the table schema.
303    pub fn column_count(&self) -> usize {
304        self.schema.columns.len()
305    }
306}
307
308impl DxValue {
309    /// Check if this value is "empty" for ditto logic
310    pub fn is_empty(&self) -> bool {
311        matches!(self, DxValue::Null)
312    }
313
314    /// Get type name for error messages
315    pub fn type_name(&self) -> &'static str {
316        match self {
317            DxValue::Null => "null",
318            DxValue::Bool(_) => "bool",
319            DxValue::Int(_) => "int",
320            DxValue::Float(_) => "float",
321            DxValue::String(_) => "string",
322            DxValue::Array(_) => "array",
323            DxValue::Object(_) => "object",
324            DxValue::Table(_) => "table",
325            DxValue::Ref(_) => "ref",
326        }
327    }
328
329    /// Convert to boolean if possible
330    pub fn as_bool(&self) -> Option<bool> {
331        match self {
332            DxValue::Bool(b) => Some(*b),
333            _ => None,
334        }
335    }
336
337    /// Convert to integer if possible
338    pub fn as_int(&self) -> Option<i64> {
339        match self {
340            DxValue::Int(i) => Some(*i),
341            DxValue::Float(f) => Some(*f as i64),
342            _ => None,
343        }
344    }
345
346    /// Convert to float if possible
347    pub fn as_float(&self) -> Option<f64> {
348        match self {
349            DxValue::Float(f) => Some(*f),
350            DxValue::Int(i) => Some(*i as f64),
351            _ => None,
352        }
353    }
354
355    /// Convert to string if possible
356    pub fn as_str(&self) -> Option<&str> {
357        match self {
358            DxValue::String(s) => Some(s),
359            _ => None,
360        }
361    }
362}
363
364// =============================================================================
365// Thread Safety Compile-Time Assertions
366// =============================================================================
367
368// These static assertions verify at compile time that our types are thread-safe.
369// If any of these types stop implementing Send or Sync, compilation will fail.
370
371/// Compile-time assertion that a type implements Send
372const fn _assert_send<T: Send>() {}
373
374/// Compile-time assertion that a type implements Sync
375const fn _assert_sync<T: Sync>() {}
376
377// Verify DxValue is Send + Sync
378const _: () = _assert_send::<DxValue>();
379const _: () = _assert_sync::<DxValue>();
380
381// Verify DxArray is Send + Sync
382const _: () = _assert_send::<DxArray>();
383const _: () = _assert_sync::<DxArray>();
384
385// Verify DxObject is Send + Sync
386const _: () = _assert_send::<DxObject>();
387const _: () = _assert_sync::<DxObject>();
388
389// Verify DxTable is Send + Sync
390const _: () = _assert_send::<DxTable>();
391const _: () = _assert_sync::<DxTable>();