seq_runtime/
serialize.rs

1//! Serialization of Seq Values
2//!
3//! This module provides a serializable representation of Seq runtime values.
4//! It enables Value persistence and exchange with external systems.
5//!
6//! # Use Cases
7//!
8//! - **Actor persistence**: Event sourcing and state snapshots
9//! - **Data pipelines**: Arrow/Parquet integration
10//! - **IPC**: Message passing between processes
11//! - **Storage**: Database and file persistence
12//!
13//! # Why TypedValue?
14//!
15//! The runtime `Value` type contains arena-allocated strings (`SeqString`)
16//! which aren't directly serializable. `TypedValue` uses owned `String`s
17//! and can be serialized with serde/bincode.
18//!
19//! # Why BTreeMap instead of HashMap?
20//!
21//! `TypedValue::Map` uses `BTreeMap` (not `HashMap`) for deterministic serialization.
22//! This ensures that the same logical map always serializes to identical bytes,
23//! which is important for:
24//! - Content-addressable storage (hashing serialized data)
25//! - Reproducible snapshots for testing and debugging
26//! - Consistent behavior across runs
27//!
28//! The O(n log n) insertion overhead is acceptable since serialization is
29//! typically infrequent (snapshots, persistence) rather than on the hot path.
30//!
31//! # Performance
32//!
33//! Uses bincode for fast, compact binary serialization.
34//! For debugging, use `TypedValue::to_debug_string()`.
35
36use crate::seqstring::global_string;
37use crate::value::{MapKey as RuntimeMapKey, Value, VariantData};
38use serde::{Deserialize, Serialize};
39use std::collections::{BTreeMap, HashMap};
40
41/// Error during serialization/deserialization
42#[derive(Debug)]
43pub enum SerializeError {
44    /// Cannot serialize quotations (code)
45    QuotationNotSerializable,
46    /// Cannot serialize closures
47    ClosureNotSerializable,
48    /// Bincode encoding/decoding error (preserves original error for debugging)
49    BincodeError(Box<bincode::Error>),
50    /// Invalid data structure
51    InvalidData(String),
52    /// Non-finite float (NaN or Infinity)
53    NonFiniteFloat(f64),
54}
55
56impl std::fmt::Display for SerializeError {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            SerializeError::QuotationNotSerializable => {
60                write!(f, "Quotations cannot be serialized - code is not data")
61            }
62            SerializeError::ClosureNotSerializable => {
63                write!(f, "Closures cannot be serialized - code is not data")
64            }
65            SerializeError::BincodeError(e) => write!(f, "Bincode error: {}", e),
66            SerializeError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
67            SerializeError::NonFiniteFloat(v) => {
68                write!(f, "Cannot serialize non-finite float: {}", v)
69            }
70        }
71    }
72}
73
74impl std::error::Error for SerializeError {
75    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
76        match self {
77            SerializeError::BincodeError(e) => Some(e.as_ref()),
78            _ => None,
79        }
80    }
81}
82
83impl From<bincode::Error> for SerializeError {
84    fn from(e: bincode::Error) -> Self {
85        SerializeError::BincodeError(Box::new(e))
86    }
87}
88
89/// Serializable map key types
90///
91/// Subset of TypedValue that can be used as map keys.
92/// Mirrors runtime `MapKey` but with owned strings.
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
94pub enum TypedMapKey {
95    Int(i64),
96    Bool(bool),
97    String(String),
98}
99
100impl TypedMapKey {
101    /// Convert to a TypedValue
102    pub fn to_typed_value(&self) -> TypedValue {
103        match self {
104            TypedMapKey::Int(v) => TypedValue::Int(*v),
105            TypedMapKey::Bool(v) => TypedValue::Bool(*v),
106            TypedMapKey::String(v) => TypedValue::String(v.clone()),
107        }
108    }
109
110    /// Convert from runtime MapKey
111    pub fn from_runtime(key: &RuntimeMapKey) -> Self {
112        match key {
113            RuntimeMapKey::Int(v) => TypedMapKey::Int(*v),
114            RuntimeMapKey::Bool(v) => TypedMapKey::Bool(*v),
115            RuntimeMapKey::String(s) => TypedMapKey::String(s.as_str().to_string()),
116        }
117    }
118
119    /// Convert to runtime MapKey (requires global string allocation)
120    pub fn to_runtime(&self) -> RuntimeMapKey {
121        match self {
122            TypedMapKey::Int(v) => RuntimeMapKey::Int(*v),
123            TypedMapKey::Bool(v) => RuntimeMapKey::Bool(*v),
124            TypedMapKey::String(s) => RuntimeMapKey::String(global_string(s.clone())),
125        }
126    }
127}
128
129/// Serializable representation of Seq Values
130///
131/// This type mirrors `Value` but uses owned data suitable for serialization.
132/// Quotations and closures cannot be serialized (they contain code, not data).
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134pub enum TypedValue {
135    Int(i64),
136    Float(f64),
137    Bool(bool),
138    String(String),
139    /// Map with typed keys and values
140    Map(BTreeMap<TypedMapKey, TypedValue>),
141    /// Variant with tag and fields
142    Variant {
143        tag: u32,
144        fields: Vec<TypedValue>,
145    },
146}
147
148impl TypedValue {
149    /// Convert from runtime Value
150    ///
151    /// Returns error if Value contains:
152    /// - Code (Quotation/Closure) - not serializable
153    /// - Non-finite floats (NaN/Infinity) - could cause logic issues
154    pub fn from_value(value: &Value) -> Result<Self, SerializeError> {
155        match value {
156            Value::Int(v) => Ok(TypedValue::Int(*v)),
157            Value::Float(v) => {
158                if !v.is_finite() {
159                    return Err(SerializeError::NonFiniteFloat(*v));
160                }
161                Ok(TypedValue::Float(*v))
162            }
163            Value::Bool(v) => Ok(TypedValue::Bool(*v)),
164            Value::String(s) => Ok(TypedValue::String(s.as_str().to_string())),
165            Value::Map(map) => {
166                let mut typed_map = BTreeMap::new();
167                for (k, v) in map.iter() {
168                    let typed_key = TypedMapKey::from_runtime(k);
169                    let typed_value = TypedValue::from_value(v)?;
170                    typed_map.insert(typed_key, typed_value);
171                }
172                Ok(TypedValue::Map(typed_map))
173            }
174            Value::Variant(data) => {
175                let mut typed_fields = Vec::with_capacity(data.fields.len());
176                for field in data.fields.iter() {
177                    typed_fields.push(TypedValue::from_value(field)?);
178                }
179                Ok(TypedValue::Variant {
180                    tag: data.tag,
181                    fields: typed_fields,
182                })
183            }
184            Value::Quotation { .. } => Err(SerializeError::QuotationNotSerializable),
185            Value::Closure { .. } => Err(SerializeError::ClosureNotSerializable),
186        }
187    }
188
189    /// Convert to runtime Value
190    ///
191    /// Note: Strings are allocated as global strings (not arena)
192    /// to ensure they outlive any strand context.
193    pub fn to_value(&self) -> Value {
194        match self {
195            TypedValue::Int(v) => Value::Int(*v),
196            TypedValue::Float(v) => Value::Float(*v),
197            TypedValue::Bool(v) => Value::Bool(*v),
198            TypedValue::String(s) => Value::String(global_string(s.clone())),
199            TypedValue::Map(map) => {
200                let mut runtime_map = HashMap::new();
201                for (k, v) in map.iter() {
202                    runtime_map.insert(k.to_runtime(), v.to_value());
203                }
204                Value::Map(Box::new(runtime_map))
205            }
206            TypedValue::Variant { tag, fields } => {
207                let runtime_fields: Vec<Value> = fields.iter().map(|f| f.to_value()).collect();
208                Value::Variant(Box::new(VariantData::new(*tag, runtime_fields)))
209            }
210        }
211    }
212
213    /// Try to convert to a map key (fails for Float, Map, Variant)
214    pub fn to_map_key(&self) -> Result<TypedMapKey, SerializeError> {
215        match self {
216            TypedValue::Int(v) => Ok(TypedMapKey::Int(*v)),
217            TypedValue::Bool(v) => Ok(TypedMapKey::Bool(*v)),
218            TypedValue::String(v) => Ok(TypedMapKey::String(v.clone())),
219            TypedValue::Float(_) => Err(SerializeError::InvalidData(
220                "Float cannot be a map key".to_string(),
221            )),
222            TypedValue::Map(_) => Err(SerializeError::InvalidData(
223                "Map cannot be a map key".to_string(),
224            )),
225            TypedValue::Variant { .. } => Err(SerializeError::InvalidData(
226                "Variant cannot be a map key".to_string(),
227            )),
228        }
229    }
230
231    /// Serialize to binary format (bincode)
232    pub fn to_bytes(&self) -> Result<Vec<u8>, SerializeError> {
233        bincode::serialize(self).map_err(SerializeError::from)
234    }
235
236    /// Deserialize from binary format (bincode)
237    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SerializeError> {
238        bincode::deserialize(bytes).map_err(SerializeError::from)
239    }
240
241    /// Convert to human-readable debug string
242    pub fn to_debug_string(&self) -> String {
243        match self {
244            TypedValue::Int(v) => format!("{}", v),
245            TypedValue::Float(v) => format!("{}", v),
246            TypedValue::Bool(v) => format!("{}", v),
247            TypedValue::String(v) => format!("{:?}", v),
248            TypedValue::Map(m) => {
249                let entries: Vec<String> = m
250                    .iter()
251                    .map(|(k, v)| format!("{}: {}", key_to_debug_string(k), v.to_debug_string()))
252                    .collect();
253                format!("{{ {} }}", entries.join(", "))
254            }
255            TypedValue::Variant { tag, fields } => {
256                if fields.is_empty() {
257                    format!("(Variant#{})", tag)
258                } else {
259                    let field_strs: Vec<String> =
260                        fields.iter().map(|f| f.to_debug_string()).collect();
261                    format!("(Variant#{} {})", tag, field_strs.join(" "))
262                }
263            }
264        }
265    }
266}
267
268fn key_to_debug_string(key: &TypedMapKey) -> String {
269    match key {
270        TypedMapKey::Int(v) => format!("{}", v),
271        TypedMapKey::Bool(v) => format!("{}", v),
272        TypedMapKey::String(v) => format!("{:?}", v),
273    }
274}
275
276/// Extension trait for Value to add serialization methods
277pub trait ValueSerialize {
278    /// Convert to serializable TypedValue
279    fn to_typed(&self) -> Result<TypedValue, SerializeError>;
280
281    /// Serialize directly to bytes
282    fn to_bytes(&self) -> Result<Vec<u8>, SerializeError>;
283}
284
285impl ValueSerialize for Value {
286    fn to_typed(&self) -> Result<TypedValue, SerializeError> {
287        TypedValue::from_value(self)
288    }
289
290    fn to_bytes(&self) -> Result<Vec<u8>, SerializeError> {
291        TypedValue::from_value(self)?.to_bytes()
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::seqstring::global_string;
299
300    #[test]
301    fn test_int_roundtrip() {
302        let value = Value::Int(42);
303        let typed = TypedValue::from_value(&value).unwrap();
304        let back = typed.to_value();
305        assert_eq!(value, back);
306    }
307
308    #[test]
309    fn test_float_roundtrip() {
310        let value = Value::Float(1.23456);
311        let typed = TypedValue::from_value(&value).unwrap();
312        let back = typed.to_value();
313        assert_eq!(value, back);
314    }
315
316    #[test]
317    fn test_bool_roundtrip() {
318        let value = Value::Bool(true);
319        let typed = TypedValue::from_value(&value).unwrap();
320        let back = typed.to_value();
321        assert_eq!(value, back);
322    }
323
324    #[test]
325    fn test_string_roundtrip() {
326        let value = Value::String(global_string("hello".to_string()));
327        let typed = TypedValue::from_value(&value).unwrap();
328        let back = typed.to_value();
329        // Compare string contents (not pointer equality)
330        match (&value, &back) {
331            (Value::String(a), Value::String(b)) => assert_eq!(a.as_str(), b.as_str()),
332            _ => panic!("Expected strings"),
333        }
334    }
335
336    #[test]
337    fn test_map_roundtrip() {
338        let mut map = HashMap::new();
339        map.insert(
340            RuntimeMapKey::String(global_string("key".to_string())),
341            Value::Int(42),
342        );
343        map.insert(RuntimeMapKey::Int(1), Value::Bool(true));
344
345        let value = Value::Map(Box::new(map));
346        let typed = TypedValue::from_value(&value).unwrap();
347        let back = typed.to_value();
348
349        // Verify map contents
350        if let Value::Map(m) = back {
351            assert_eq!(m.len(), 2);
352        } else {
353            panic!("Expected map");
354        }
355    }
356
357    #[test]
358    fn test_variant_roundtrip() {
359        let data = VariantData::new(1, vec![Value::Int(100), Value::Bool(false)]);
360        let value = Value::Variant(Box::new(data));
361
362        let typed = TypedValue::from_value(&value).unwrap();
363        let back = typed.to_value();
364
365        if let Value::Variant(v) = back {
366            assert_eq!(v.tag, 1);
367            assert_eq!(v.fields.len(), 2);
368        } else {
369            panic!("Expected variant");
370        }
371    }
372
373    #[test]
374    fn test_quotation_not_serializable() {
375        let value = Value::Quotation {
376            wrapper: 12345,
377            impl_: 12345,
378        };
379        let result = TypedValue::from_value(&value);
380        assert!(matches!(
381            result,
382            Err(SerializeError::QuotationNotSerializable)
383        ));
384    }
385
386    #[test]
387    fn test_closure_not_serializable() {
388        use std::sync::Arc;
389        let value = Value::Closure {
390            fn_ptr: 12345,
391            env: Arc::from(vec![Value::Int(1)].into_boxed_slice()),
392        };
393        let result = TypedValue::from_value(&value);
394        assert!(matches!(
395            result,
396            Err(SerializeError::ClosureNotSerializable)
397        ));
398    }
399
400    #[test]
401    fn test_bytes_roundtrip() {
402        let typed = TypedValue::Map(BTreeMap::from([
403            (TypedMapKey::String("x".to_string()), TypedValue::Int(10)),
404            (TypedMapKey::Int(42), TypedValue::Bool(true)),
405        ]));
406
407        let bytes = typed.to_bytes().unwrap();
408        let parsed = TypedValue::from_bytes(&bytes).unwrap();
409        assert_eq!(typed, parsed);
410    }
411
412    #[test]
413    fn test_bincode_is_compact() {
414        let typed = TypedValue::Int(42);
415        let bytes = typed.to_bytes().unwrap();
416        assert!(
417            bytes.len() < 20,
418            "Expected compact encoding, got {} bytes",
419            bytes.len()
420        );
421    }
422
423    #[test]
424    fn test_debug_string() {
425        let typed = TypedValue::String("hello".to_string());
426        assert_eq!(typed.to_debug_string(), "\"hello\"");
427
428        let typed = TypedValue::Int(42);
429        assert_eq!(typed.to_debug_string(), "42");
430    }
431
432    #[test]
433    fn test_nested_structure() {
434        // Create nested map with variant
435        let inner_variant = TypedValue::Variant {
436            tag: 2,
437            fields: vec![TypedValue::String("inner".to_string())],
438        };
439
440        let mut inner_map = BTreeMap::new();
441        inner_map.insert(TypedMapKey::String("nested".to_string()), inner_variant);
442
443        let outer = TypedValue::Map(inner_map);
444
445        let bytes = outer.to_bytes().unwrap();
446        let parsed = TypedValue::from_bytes(&bytes).unwrap();
447        assert_eq!(outer, parsed);
448    }
449
450    #[test]
451    fn test_nan_not_serializable() {
452        let value = Value::Float(f64::NAN);
453        let result = TypedValue::from_value(&value);
454        assert!(matches!(result, Err(SerializeError::NonFiniteFloat(_))));
455    }
456
457    #[test]
458    fn test_infinity_not_serializable() {
459        let value = Value::Float(f64::INFINITY);
460        let result = TypedValue::from_value(&value);
461        assert!(matches!(result, Err(SerializeError::NonFiniteFloat(_))));
462
463        let value = Value::Float(f64::NEG_INFINITY);
464        let result = TypedValue::from_value(&value);
465        assert!(matches!(result, Err(SerializeError::NonFiniteFloat(_))));
466    }
467
468    #[test]
469    fn test_corrupted_data_returns_error() {
470        // Random bytes that aren't valid bincode
471        let corrupted = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
472        let result = TypedValue::from_bytes(&corrupted);
473        assert!(result.is_err());
474    }
475
476    #[test]
477    fn test_empty_data_returns_error() {
478        let result = TypedValue::from_bytes(&[]);
479        assert!(result.is_err());
480    }
481
482    #[test]
483    fn test_truncated_data_returns_error() {
484        // Serialize valid data, then truncate
485        let typed = TypedValue::String("hello world".to_string());
486        let bytes = typed.to_bytes().unwrap();
487        let truncated = &bytes[..bytes.len() / 2];
488        let result = TypedValue::from_bytes(truncated);
489        assert!(result.is_err());
490    }
491}