Skip to main content

crous_core/
traits.rs

1//! Trait for types that can be serialized/deserialized with the Crous format.
2//!
3//! This is the trait generated by `#[derive(Crous)]`. It provides schema-bound
4//! encoding with stable field IDs and schema fingerprints.
5//!
6//! # Blanket implementations
7//!
8//! The following types have built-in `Crous` implementations:
9//!
10//! | Rust type | Crous `Value` | Notes |
11//! |-----------|---------------|-------|
12//! | `bool` | `Bool` | |
13//! | `u8`, `u16`, `u32`, `u64`, `usize` | `UInt` | widened to `u64` |
14//! | `u128` | `UInt` | panics if value > `u64::MAX` |
15//! | `i8`, `i16`, `i32`, `i64`, `isize` | `Int` | widened to `i64` |
16//! | `i128` | `Int` | panics if value outside `i64` range |
17//! | `f32`, `f64` | `Float` | `f32` widened to `f64` |
18//! | `String`, `Box<str>` | `Str` | |
19//! | `Vec<u8>` | `Array` of `UInt` | use [`CrousBytes`] for `Value::Bytes` |
20//! | `Vec<T: Crous>` | `Array` | |
21//! | `Option<T: Crous>` | `T` or `Null` | |
22//! | `Box<T: Crous>` | delegates to `T` | |
23//! | [`CrousBytes`] | `Bytes` | newtype for raw binary blobs |
24//! | `HashMap<String, T>` | `Object` | insertion-order not guaranteed |
25//! | `BTreeMap<String, T>` | `Object` | sorted by key |
26//! | `(A,)` through `(A,B,C,D,E,F)` | `Array` | heterogeneous tuples |
27
28use crate::error::Result;
29use crate::value::Value;
30use std::collections::{BTreeMap, HashMap};
31
32/// Trait for types that can encode/decode to/from the Crous binary format.
33///
34/// Implementations are typically generated by `#[derive(Crous)]` from
35/// the `crous-derive` crate, which assigns stable field IDs and computes
36/// schema fingerprints.
37///
38/// # Example (manual implementation)
39/// ```
40/// use crous_core::{Crous, Value};
41///
42/// struct Point { x: f64, y: f64 }
43///
44/// impl Crous for Point {
45///     fn to_crous_value(&self) -> Value {
46///         Value::Object(vec![
47///             ("x".into(), Value::Float(self.x)),
48///             ("y".into(), Value::Float(self.y)),
49///         ])
50///     }
51///
52///     fn from_crous_value(value: &Value) -> crous_core::Result<Self> {
53///         match value {
54///             Value::Object(fields) => {
55///                 let mut x = 0.0;
56///                 let mut y = 0.0;
57///                 for (k, v) in fields {
58///                     match k.as_str() {
59///                         "x" => x = v.as_float().unwrap_or(0.0),
60///                         "y" => y = v.as_float().unwrap_or(0.0),
61///                         _ => {} // skip unknown fields for forward compat
62///                     }
63///                 }
64///                 Ok(Point { x, y })
65///             }
66///             _ => Err(crous_core::CrousError::SchemaMismatch(
67///                 "expected object for Point".into()
68///             )),
69///         }
70///     }
71///
72///     fn schema_fingerprint() -> u64 { 0x1234567890abcdef }
73///     fn type_name() -> &'static str { "Point" }
74/// }
75/// ```
76pub trait Crous: Sized {
77    /// Convert this value to an owned `Value` for encoding.
78    fn to_crous_value(&self) -> Value;
79
80    /// Construct this type from a decoded `Value`.
81    fn from_crous_value(value: &Value) -> Result<Self>;
82
83    /// A stable fingerprint (XXH64) of this type's schema.
84    /// Used for schema-on-write validation.
85    fn schema_fingerprint() -> u64;
86
87    /// The human-readable type name for diagnostics.
88    fn type_name() -> &'static str;
89
90    /// Encode this value directly to Crous binary bytes.
91    ///
92    /// Convenience method that creates an `Encoder`, encodes the value,
93    /// and returns the finished byte vector.
94    fn to_crous_bytes(&self) -> Result<Vec<u8>> {
95        let val = self.to_crous_value();
96        let mut enc = crate::encoder::Encoder::new();
97        enc.encode_value(&val)?;
98        enc.finish()
99    }
100
101    /// Decode from Crous binary bytes using default limits.
102    fn from_crous_bytes(data: &[u8]) -> Result<Self> {
103        let mut dec = crate::decoder::Decoder::new(data);
104        let val = dec.decode_next()?.to_owned_value();
105        Self::from_crous_value(&val)
106    }
107
108    /// Decode from Crous binary bytes with custom limits.
109    ///
110    /// This enforces all `Limits` during binary decode: nesting depth,
111    /// memory, block size, string length, and item count.
112    fn from_crous_bytes_with_limits(data: &[u8], limits: crate::limits::Limits) -> Result<Self> {
113        let mut dec = crate::decoder::Decoder::with_limits(data, limits);
114        let val = dec.decode_next()?.to_owned_value();
115        Self::from_crous_value(&val)
116    }
117}
118
119// ---------------------------------------------------------------------------
120// Blanket impls for primitive types
121// ---------------------------------------------------------------------------
122
123// --- Bool ---
124
125impl Crous for bool {
126    fn to_crous_value(&self) -> Value {
127        Value::Bool(*self)
128    }
129
130    fn from_crous_value(value: &Value) -> Result<Self> {
131        match value {
132            Value::Bool(b) => Ok(*b),
133            _ => Err(crate::error::CrousError::SchemaMismatch(
134                "expected bool".into(),
135            )),
136        }
137    }
138
139    fn schema_fingerprint() -> u64 {
140        0x0004
141    }
142    fn type_name() -> &'static str {
143        "bool"
144    }
145}
146
147// --- Unsigned integers ---
148
149/// Helper macro: implement Crous for unsigned integer types that fit in u64.
150macro_rules! impl_crous_uint {
151    ($($ty:ty => $fp:expr, $name:expr);+ $(;)?) => { $(
152        impl Crous for $ty {
153            fn to_crous_value(&self) -> Value {
154                Value::UInt(*self as u64)
155            }
156
157            fn from_crous_value(value: &Value) -> Result<Self> {
158                match value {
159                    Value::UInt(n) => {
160                        <$ty>::try_from(*n).map_err(|_| crate::error::CrousError::SchemaMismatch(
161                            format!("uint {} out of range for {}", n, $name)
162                        ))
163                    }
164                    _ => Err(crate::error::CrousError::SchemaMismatch(
165                        format!("expected uint for {}", $name)
166                    )),
167                }
168            }
169
170            fn schema_fingerprint() -> u64 { $fp }
171            fn type_name() -> &'static str { $name }
172        }
173    )+ };
174}
175
176impl_crous_uint! {
177    u8   => 0x0002_0001, "u8";
178    u16  => 0x0002_0002, "u16";
179    u32  => 0x0002_0003, "u32";
180    u64  => 0x0002,      "u64";
181}
182
183// usize: same wire representation as u64
184impl Crous for usize {
185    fn to_crous_value(&self) -> Value {
186        Value::UInt(*self as u64)
187    }
188
189    fn from_crous_value(value: &Value) -> Result<Self> {
190        match value {
191            Value::UInt(n) => {
192                usize::try_from(*n).map_err(|_| {
193                    crate::error::CrousError::SchemaMismatch(format!(
194                        "uint {n} out of range for usize"
195                    ))
196                })
197            }
198            _ => Err(crate::error::CrousError::SchemaMismatch(
199                "expected uint for usize".into(),
200            )),
201        }
202    }
203
204    fn schema_fingerprint() -> u64 {
205        0x0002_0005
206    }
207    fn type_name() -> &'static str {
208        "usize"
209    }
210}
211
212// u128: encode as UInt, but panics on encode if > u64::MAX
213impl Crous for u128 {
214    fn to_crous_value(&self) -> Value {
215        Value::UInt(u64::try_from(*self).expect("u128 value exceeds u64::MAX for Crous encoding"))
216    }
217
218    fn from_crous_value(value: &Value) -> Result<Self> {
219        match value {
220            Value::UInt(n) => Ok(u128::from(*n)),
221            _ => Err(crate::error::CrousError::SchemaMismatch(
222                "expected uint for u128".into(),
223            )),
224        }
225    }
226
227    fn schema_fingerprint() -> u64 {
228        0x0002_0006
229    }
230    fn type_name() -> &'static str {
231        "u128"
232    }
233}
234
235// --- Signed integers ---
236
237/// Helper macro: implement Crous for signed integer types that fit in i64.
238macro_rules! impl_crous_int {
239    ($($ty:ty => $fp:expr, $name:expr);+ $(;)?) => { $(
240        impl Crous for $ty {
241            fn to_crous_value(&self) -> Value {
242                Value::Int(i64::from(*self))
243            }
244
245            fn from_crous_value(value: &Value) -> Result<Self> {
246                match value {
247                    Value::Int(n) => {
248                        <$ty>::try_from(*n).map_err(|_| crate::error::CrousError::SchemaMismatch(
249                            format!("int {} out of range for {}", n, $name)
250                        ))
251                    }
252                    // Also accept UInt for small non-negative values (cross-compat).
253                    Value::UInt(n) => {
254                        let as_i64 = i64::try_from(*n).map_err(|_| {
255                            crate::error::CrousError::SchemaMismatch(
256                                format!("uint {} out of range for {}", n, $name)
257                            )
258                        })?;
259                        <$ty>::try_from(as_i64).map_err(|_| {
260                            crate::error::CrousError::SchemaMismatch(
261                                format!("uint {} out of range for {}", n, $name)
262                            )
263                        })
264                    }
265                    _ => Err(crate::error::CrousError::SchemaMismatch(
266                        format!("expected int for {}", $name)
267                    )),
268                }
269            }
270
271            fn schema_fingerprint() -> u64 { $fp }
272            fn type_name() -> &'static str { $name }
273        }
274    )+ };
275}
276
277impl_crous_int! {
278    i8   => 0x0003_0001, "i8";
279    i16  => 0x0003_0002, "i16";
280    i32  => 0x0003_0003, "i32";
281    i64  => 0x0003,      "i64";
282}
283
284// isize: same wire representation as i64
285impl Crous for isize {
286    fn to_crous_value(&self) -> Value {
287        Value::Int(*self as i64)
288    }
289
290    fn from_crous_value(value: &Value) -> Result<Self> {
291        match value {
292            Value::Int(n) => {
293                isize::try_from(*n).map_err(|_| {
294                    crate::error::CrousError::SchemaMismatch(format!(
295                        "int {n} out of range for isize"
296                    ))
297                })
298            }
299            Value::UInt(n) => {
300                let as_i64 = i64::try_from(*n).map_err(|_| {
301                    crate::error::CrousError::SchemaMismatch(format!(
302                        "uint {n} out of range for isize"
303                    ))
304                })?;
305                isize::try_from(as_i64).map_err(|_| {
306                    crate::error::CrousError::SchemaMismatch(format!(
307                        "int {as_i64} out of range for isize"
308                    ))
309                })
310            }
311            _ => Err(crate::error::CrousError::SchemaMismatch(
312                "expected int for isize".into(),
313            )),
314        }
315    }
316
317    fn schema_fingerprint() -> u64 {
318        0x0003_0005
319    }
320    fn type_name() -> &'static str {
321        "isize"
322    }
323}
324
325// i128: encode as Int, but panics on encode if outside i64 range
326impl Crous for i128 {
327    fn to_crous_value(&self) -> Value {
328        Value::Int(i64::try_from(*self).expect("i128 value exceeds i64 range for Crous encoding"))
329    }
330
331    fn from_crous_value(value: &Value) -> Result<Self> {
332        match value {
333            Value::Int(n) => Ok(i128::from(*n)),
334            Value::UInt(n) => Ok(i128::from(*n)),
335            _ => Err(crate::error::CrousError::SchemaMismatch(
336                "expected int for i128".into(),
337            )),
338        }
339    }
340
341    fn schema_fingerprint() -> u64 {
342        0x0003_0006
343    }
344    fn type_name() -> &'static str {
345        "i128"
346    }
347}
348
349// --- Floating point ---
350
351impl Crous for f64 {
352    fn to_crous_value(&self) -> Value {
353        Value::Float(*self)
354    }
355
356    fn from_crous_value(value: &Value) -> Result<Self> {
357        match value {
358            Value::Float(f) => Ok(*f),
359            _ => Err(crate::error::CrousError::SchemaMismatch(
360                "expected float".into(),
361            )),
362        }
363    }
364
365    fn schema_fingerprint() -> u64 {
366        0x0005
367    }
368    fn type_name() -> &'static str {
369        "f64"
370    }
371}
372
373impl Crous for f32 {
374    fn to_crous_value(&self) -> Value {
375        Value::Float(f64::from(*self))
376    }
377
378    fn from_crous_value(value: &Value) -> Result<Self> {
379        match value {
380            Value::Float(f) => Ok(*f as f32),
381            _ => Err(crate::error::CrousError::SchemaMismatch(
382                "expected float for f32".into(),
383            )),
384        }
385    }
386
387    fn schema_fingerprint() -> u64 {
388        0x0005_0001
389    }
390    fn type_name() -> &'static str {
391        "f32"
392    }
393}
394
395// --- String types ---
396
397impl Crous for String {
398    fn to_crous_value(&self) -> Value {
399        Value::Str(self.clone())
400    }
401
402    fn from_crous_value(value: &Value) -> Result<Self> {
403        match value {
404            Value::Str(s) => Ok(s.clone()),
405            _ => Err(crate::error::CrousError::SchemaMismatch(
406                "expected string".into(),
407            )),
408        }
409    }
410
411    fn schema_fingerprint() -> u64 {
412        0x0001
413    }
414    fn type_name() -> &'static str {
415        "String"
416    }
417}
418
419impl Crous for Box<str> {
420    fn to_crous_value(&self) -> Value {
421        Value::Str(self.to_string())
422    }
423
424    fn from_crous_value(value: &Value) -> Result<Self> {
425        match value {
426            Value::Str(s) => Ok(s.clone().into_boxed_str()),
427            _ => Err(crate::error::CrousError::SchemaMismatch(
428                "expected string for Box<str>".into(),
429            )),
430        }
431    }
432
433    fn schema_fingerprint() -> u64 {
434        0x0001_0001
435    }
436    fn type_name() -> &'static str {
437        "Box<str>"
438    }
439}
440
441// --- Raw bytes newtype ---
442
443/// Newtype wrapper for `Vec<u8>` that serializes as `Value::Bytes` (binary blob).
444///
445/// Use this instead of `Vec<u8>` when you want the raw-bytes wire encoding.
446/// Plain `Vec<u8>` encodes as an array of unsigned integers via the generic
447/// `Vec<T: Crous>` impl.
448///
449/// # Example
450/// ```
451/// use crous_core::{Crous, CrousBytes, Value};
452///
453/// let blob = CrousBytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
454/// assert!(matches!(blob.to_crous_value(), Value::Bytes(_)));
455///
456/// let arr: Vec<u8> = vec![1, 2, 3];
457/// // Vec<u8> uses the generic Vec<T> impl → Array of UInts
458/// assert!(matches!(arr.to_crous_value(), Value::Array(_)));
459/// ```
460#[derive(Debug, Clone, PartialEq, Eq, Hash)]
461pub struct CrousBytes(pub Vec<u8>);
462
463impl CrousBytes {
464    /// Create a new `CrousBytes` from a byte vector.
465    pub fn new(bytes: Vec<u8>) -> Self {
466        Self(bytes)
467    }
468
469    /// Consume and return the inner `Vec<u8>`.
470    pub fn into_inner(self) -> Vec<u8> {
471        self.0
472    }
473
474    /// Borrow the inner bytes as a slice.
475    pub fn as_bytes(&self) -> &[u8] {
476        &self.0
477    }
478}
479
480impl From<Vec<u8>> for CrousBytes {
481    fn from(v: Vec<u8>) -> Self {
482        Self(v)
483    }
484}
485
486impl From<CrousBytes> for Vec<u8> {
487    fn from(b: CrousBytes) -> Self {
488        b.0
489    }
490}
491
492impl AsRef<[u8]> for CrousBytes {
493    fn as_ref(&self) -> &[u8] {
494        &self.0
495    }
496}
497
498impl Crous for CrousBytes {
499    fn to_crous_value(&self) -> Value {
500        Value::Bytes(self.0.clone())
501    }
502
503    fn from_crous_value(value: &Value) -> Result<Self> {
504        match value {
505            Value::Bytes(b) => Ok(CrousBytes(b.clone())),
506            _ => Err(crate::error::CrousError::SchemaMismatch(
507                "expected bytes".into(),
508            )),
509        }
510    }
511
512    fn schema_fingerprint() -> u64 {
513        0x0006
514    }
515    fn type_name() -> &'static str {
516        "CrousBytes"
517    }
518}
519
520// --- Generic containers ---
521
522impl<T: Crous> Crous for Vec<T> {
523    fn to_crous_value(&self) -> Value {
524        Value::Array(self.iter().map(|item| item.to_crous_value()).collect())
525    }
526
527    fn from_crous_value(value: &Value) -> Result<Self> {
528        match value {
529            Value::Array(items) => items.iter().map(T::from_crous_value).collect(),
530            _ => Err(crate::error::CrousError::SchemaMismatch(
531                "expected array".into(),
532            )),
533        }
534    }
535
536    fn schema_fingerprint() -> u64 {
537        T::schema_fingerprint()
538            .wrapping_mul(31)
539            .wrapping_add(0x0010)
540    }
541
542    fn type_name() -> &'static str {
543        "Vec"
544    }
545}
546
547impl<T: Crous> Crous for Option<T> {
548    fn to_crous_value(&self) -> Value {
549        match self {
550            Some(v) => v.to_crous_value(),
551            None => Value::Null,
552        }
553    }
554
555    fn from_crous_value(value: &Value) -> Result<Self> {
556        match value {
557            Value::Null => Ok(None),
558            other => Ok(Some(T::from_crous_value(other)?)),
559        }
560    }
561
562    fn schema_fingerprint() -> u64 {
563        T::schema_fingerprint()
564            .wrapping_mul(37)
565            .wrapping_add(0x0020)
566    }
567
568    fn type_name() -> &'static str {
569        "Option"
570    }
571}
572
573impl<T: Crous> Crous for Box<T> {
574    fn to_crous_value(&self) -> Value {
575        (**self).to_crous_value()
576    }
577
578    fn from_crous_value(value: &Value) -> Result<Self> {
579        T::from_crous_value(value).map(Box::new)
580    }
581
582    fn schema_fingerprint() -> u64 {
583        T::schema_fingerprint()
584    }
585
586    fn type_name() -> &'static str {
587        T::type_name()
588    }
589}
590
591// --- Map types → Object ---
592
593impl<T: Crous> Crous for HashMap<String, T> {
594    fn to_crous_value(&self) -> Value {
595        Value::Object(
596            self.iter()
597                .map(|(k, v)| (k.clone(), v.to_crous_value()))
598                .collect(),
599        )
600    }
601
602    fn from_crous_value(value: &Value) -> Result<Self> {
603        match value {
604            Value::Object(entries) => {
605                let mut map = HashMap::with_capacity(entries.len());
606                for (k, v) in entries {
607                    map.insert(k.clone(), T::from_crous_value(v)?);
608                }
609                Ok(map)
610            }
611            _ => Err(crate::error::CrousError::SchemaMismatch(
612                "expected object for HashMap".into(),
613            )),
614        }
615    }
616
617    fn schema_fingerprint() -> u64 {
618        T::schema_fingerprint()
619            .wrapping_mul(41)
620            .wrapping_add(0x0030)
621    }
622
623    fn type_name() -> &'static str {
624        "HashMap"
625    }
626}
627
628impl<T: Crous> Crous for BTreeMap<String, T> {
629    fn to_crous_value(&self) -> Value {
630        Value::Object(
631            self.iter()
632                .map(|(k, v)| (k.clone(), v.to_crous_value()))
633                .collect(),
634        )
635    }
636
637    fn from_crous_value(value: &Value) -> Result<Self> {
638        match value {
639            Value::Object(entries) => {
640                let mut map = BTreeMap::new();
641                for (k, v) in entries {
642                    map.insert(k.clone(), T::from_crous_value(v)?);
643                }
644                Ok(map)
645            }
646            _ => Err(crate::error::CrousError::SchemaMismatch(
647                "expected object for BTreeMap".into(),
648            )),
649        }
650    }
651
652    fn schema_fingerprint() -> u64 {
653        T::schema_fingerprint()
654            .wrapping_mul(43)
655            .wrapping_add(0x0031)
656    }
657
658    fn type_name() -> &'static str {
659        "BTreeMap"
660    }
661}
662
663// --- Tuple impls (heterogeneous, encoded as Array) ---
664
665macro_rules! impl_crous_tuple {
666    ($fp:expr, $($idx:tt : $T:ident),+) => {
667        impl<$($T: Crous),+> Crous for ($($T,)+) {
668            fn to_crous_value(&self) -> Value {
669                Value::Array(vec![
670                    $(self.$idx.to_crous_value()),+
671                ])
672            }
673
674            fn from_crous_value(value: &Value) -> Result<Self> {
675                match value {
676                    Value::Array(items) => {
677                        let mut iter = items.iter();
678                        Ok(($(
679                            $T::from_crous_value(
680                                iter.next().ok_or_else(|| {
681                                    crate::error::CrousError::SchemaMismatch(
682                                        "tuple: not enough array elements".into()
683                                    )
684                                })?
685                            )?,
686                        )+))
687                    }
688                    _ => Err(crate::error::CrousError::SchemaMismatch(
689                        "expected array for tuple".into(),
690                    )),
691                }
692            }
693
694            fn schema_fingerprint() -> u64 { $fp }
695            fn type_name() -> &'static str { "tuple" }
696        }
697    };
698}
699
700impl_crous_tuple!(0x0040_0001, 0: A);
701impl_crous_tuple!(0x0040_0002, 0: A, 1: B);
702impl_crous_tuple!(0x0040_0003, 0: A, 1: B, 2: C);
703impl_crous_tuple!(0x0040_0004, 0: A, 1: B, 2: C, 3: D);
704impl_crous_tuple!(0x0040_0005, 0: A, 1: B, 2: C, 3: D, 4: E);
705impl_crous_tuple!(0x0040_0006, 0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
706
707// --- Unit type ---
708
709impl Crous for () {
710    fn to_crous_value(&self) -> Value {
711        Value::Null
712    }
713
714    fn from_crous_value(value: &Value) -> Result<Self> {
715        match value {
716            Value::Null => Ok(()),
717            _ => Err(crate::error::CrousError::SchemaMismatch(
718                "expected null for ()".into(),
719            )),
720        }
721    }
722
723    fn schema_fingerprint() -> u64 {
724        0x0000
725    }
726    fn type_name() -> &'static str {
727        "()"
728    }
729}
730
731// ---------------------------------------------------------------------------
732// Tests
733// ---------------------------------------------------------------------------
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn u8_roundtrip() {
741        let v: u8 = 200;
742        let val = v.to_crous_value();
743        assert_eq!(val, Value::UInt(200));
744        assert_eq!(u8::from_crous_value(&val).unwrap(), 200);
745    }
746
747    #[test]
748    fn u8_overflow() {
749        let val = Value::UInt(256);
750        assert!(u8::from_crous_value(&val).is_err());
751    }
752
753    #[test]
754    fn u16_roundtrip() {
755        let v: u16 = 60000;
756        let val = v.to_crous_value();
757        assert_eq!(u16::from_crous_value(&val).unwrap(), 60000);
758    }
759
760    #[test]
761    fn u32_roundtrip() {
762        let v: u32 = u32::MAX;
763        let val = v.to_crous_value();
764        assert_eq!(u32::from_crous_value(&val).unwrap(), u32::MAX);
765    }
766
767    #[test]
768    fn i8_roundtrip() {
769        let v: i8 = -42;
770        let val = v.to_crous_value();
771        assert_eq!(val, Value::Int(-42));
772        assert_eq!(i8::from_crous_value(&val).unwrap(), -42);
773    }
774
775    #[test]
776    fn i8_overflow() {
777        let val = Value::Int(200);
778        assert!(i8::from_crous_value(&val).is_err());
779    }
780
781    #[test]
782    fn i16_roundtrip() {
783        let v: i16 = -30000;
784        let val = v.to_crous_value();
785        assert_eq!(i16::from_crous_value(&val).unwrap(), -30000);
786    }
787
788    #[test]
789    fn i32_roundtrip() {
790        let v: i32 = i32::MIN;
791        let val = v.to_crous_value();
792        assert_eq!(i32::from_crous_value(&val).unwrap(), i32::MIN);
793    }
794
795    #[test]
796    fn i32_from_uint_compat() {
797        // Signed types should accept UInt values when they fit.
798        let val = Value::UInt(42);
799        assert_eq!(i32::from_crous_value(&val).unwrap(), 42);
800    }
801
802    #[test]
803    fn f32_roundtrip() {
804        let v: f32 = 2.78;
805        let val = v.to_crous_value();
806        assert!(matches!(val, Value::Float(_)));
807        let back = f32::from_crous_value(&val).unwrap();
808        assert!((back - 2.78).abs() < 1e-5);
809    }
810
811    #[test]
812    fn vec_u8_is_array() {
813        let v: Vec<u8> = vec![1, 2, 3];
814        let val = v.to_crous_value();
815        // Vec<u8> uses the generic Vec<T> impl → Array of UInts.
816        assert!(matches!(val, Value::Array(_)));
817        let back = Vec::<u8>::from_crous_value(&val).unwrap();
818        assert_eq!(back, vec![1, 2, 3]);
819    }
820
821    #[test]
822    fn crous_bytes_is_bytes() {
823        let blob = CrousBytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
824        let val = blob.to_crous_value();
825        assert!(matches!(val, Value::Bytes(_)));
826        let back = CrousBytes::from_crous_value(&val).unwrap();
827        assert_eq!(back.0, vec![0xDE, 0xAD, 0xBE, 0xEF]);
828    }
829
830    #[test]
831    fn box_str_roundtrip() {
832        let s: Box<str> = "hello".into();
833        let val = s.to_crous_value();
834        assert_eq!(val, Value::Str("hello".into()));
835        let back = Box::<str>::from_crous_value(&val).unwrap();
836        assert_eq!(&*back, "hello");
837    }
838
839    #[test]
840    fn box_t_roundtrip() {
841        let v: Box<u32> = Box::new(42);
842        let val = v.to_crous_value();
843        assert_eq!(val, Value::UInt(42));
844        let back = Box::<u32>::from_crous_value(&val).unwrap();
845        assert_eq!(*back, 42);
846    }
847
848    #[test]
849    fn hashmap_roundtrip() {
850        let mut map = HashMap::new();
851        map.insert("x".to_string(), 10u64);
852        map.insert("y".to_string(), 20u64);
853
854        let val = map.to_crous_value();
855        assert!(matches!(val, Value::Object(_)));
856
857        let back = HashMap::<String, u64>::from_crous_value(&val).unwrap();
858        assert_eq!(back.get("x"), Some(&10));
859        assert_eq!(back.get("y"), Some(&20));
860    }
861
862    #[test]
863    fn btreemap_roundtrip() {
864        let mut map = BTreeMap::new();
865        map.insert("a".to_string(), Value::Bool(true));
866        map.insert("b".to_string(), Value::UInt(7));
867
868        // BTreeMap<String, Value> doesn't work since Value doesn't impl Crous,
869        // but BTreeMap<String, bool> does.
870        let mut m2 = BTreeMap::new();
871        m2.insert("flag".to_string(), true);
872        m2.insert("enabled".to_string(), false);
873        let val = m2.to_crous_value();
874        let back = BTreeMap::<String, bool>::from_crous_value(&val).unwrap();
875        assert_eq!(back.get("flag"), Some(&true));
876        assert_eq!(back.get("enabled"), Some(&false));
877    }
878
879    #[test]
880    fn tuple2_roundtrip() {
881        let t = (42u32, "hello".to_string());
882        let val = t.to_crous_value();
883        assert!(matches!(val, Value::Array(_)));
884        let back = <(u32, String)>::from_crous_value(&val).unwrap();
885        assert_eq!(back, (42, "hello".to_string()));
886    }
887
888    #[test]
889    fn tuple3_roundtrip() {
890        let t = (true, -5i16, 2.78f64);
891        let val = t.to_crous_value();
892        let back = <(bool, i16, f64)>::from_crous_value(&val).unwrap();
893        assert!(back.0);
894        assert_eq!(back.1, -5);
895        assert!((back.2 - 2.78).abs() < 1e-10);
896    }
897
898    #[test]
899    fn unit_roundtrip() {
900        let val = ().to_crous_value();
901        assert_eq!(val, Value::Null);
902        assert_eq!(<()>::from_crous_value(&val).unwrap(), ());
903    }
904
905    #[test]
906    fn usize_roundtrip() {
907        let v: usize = 999;
908        let val = v.to_crous_value();
909        assert_eq!(usize::from_crous_value(&val).unwrap(), 999);
910    }
911
912    #[test]
913    fn isize_roundtrip() {
914        let v: isize = -999;
915        let val = v.to_crous_value();
916        assert_eq!(isize::from_crous_value(&val).unwrap(), -999);
917    }
918
919    #[test]
920    fn u128_small_roundtrip() {
921        let v: u128 = 123456;
922        let val = v.to_crous_value();
923        assert_eq!(u128::from_crous_value(&val).unwrap(), 123456);
924    }
925
926    #[test]
927    fn i128_small_roundtrip() {
928        let v: i128 = -123456;
929        let val = v.to_crous_value();
930        assert_eq!(i128::from_crous_value(&val).unwrap(), -123456);
931    }
932
933    #[test]
934    fn nested_option_vec() {
935        let v: Option<Vec<u8>> = Some(vec![10, 20, 30]);
936        let val = v.to_crous_value();
937        let back = Option::<Vec<u8>>::from_crous_value(&val).unwrap();
938        assert_eq!(back, Some(vec![10, 20, 30]));
939    }
940
941    #[test]
942    fn option_none() {
943        let v: Option<u8> = None;
944        let val = v.to_crous_value();
945        assert_eq!(val, Value::Null);
946        let back = Option::<u8>::from_crous_value(&val).unwrap();
947        assert_eq!(back, None);
948    }
949
950    #[test]
951    fn full_binary_roundtrip_u8() {
952        // Verify u8 survives encode → binary → decode.
953        let v: u8 = 42;
954        let bytes = v.to_crous_bytes().unwrap();
955        let back = u8::from_crous_bytes(&bytes).unwrap();
956        assert_eq!(back, 42);
957    }
958
959    #[test]
960    fn full_binary_roundtrip_crous_bytes() {
961        let blob = CrousBytes(vec![1, 2, 3, 4]);
962        let bytes = blob.to_crous_bytes().unwrap();
963        let back = CrousBytes::from_crous_bytes(&bytes).unwrap();
964        assert_eq!(back.0, vec![1, 2, 3, 4]);
965    }
966
967    #[test]
968    fn full_binary_roundtrip_tuple() {
969        let t = (100u16, "world".to_string(), false);
970        let bytes = t.to_crous_bytes().unwrap();
971        let back = <(u16, String, bool)>::from_crous_bytes(&bytes).unwrap();
972        assert_eq!(back, (100, "world".to_string(), false));
973    }
974
975    #[test]
976    fn full_binary_roundtrip_hashmap() {
977        let mut m = HashMap::new();
978        m.insert("key".to_string(), 99u32);
979        let bytes = m.to_crous_bytes().unwrap();
980        let back = HashMap::<String, u32>::from_crous_bytes(&bytes).unwrap();
981        assert_eq!(back.get("key"), Some(&99));
982    }
983}