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