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