Skip to main content

tealeaf/
convert.rs

1//! DTO conversion traits for TeaLeaf documents
2//!
3//! This module provides the `ToTeaLeaf` and `FromTeaLeaf` traits for converting
4//! between Rust types and TeaLeaf `Value`s, along with automatic schema collection.
5
6use std::collections::HashMap;
7use std::fmt;
8use indexmap::IndexMap;
9
10use crate::{Error, FieldType, Schema, Union, Value};
11
12// =============================================================================
13// ConvertError
14// =============================================================================
15
16/// Errors that occur during DTO-to-TeaLeaf conversion
17#[derive(Debug)]
18pub enum ConvertError {
19    /// A required field was missing from the TeaLeaf Value
20    MissingField {
21        struct_name: String,
22        field: String,
23    },
24    /// A Value variant did not match the expected Rust type
25    TypeMismatch {
26        expected: String,
27        got: String,
28        path: String,
29    },
30    /// A nested conversion failed
31    Nested {
32        path: String,
33        source: Box<ConvertError>,
34    },
35    /// A custom error message
36    Custom(String),
37}
38
39impl fmt::Display for ConvertError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            ConvertError::MissingField { struct_name, field } => {
43                write!(f, "Missing field '{}' in struct '{}'", field, struct_name)
44            }
45            ConvertError::TypeMismatch {
46                expected,
47                got,
48                path,
49            } => {
50                write!(
51                    f,
52                    "Type mismatch at '{}': expected {}, got {}",
53                    path, expected, got
54                )
55            }
56            ConvertError::Nested { path, source } => {
57                write!(f, "At '{}': {}", path, source)
58            }
59            ConvertError::Custom(msg) => write!(f, "{}", msg),
60        }
61    }
62}
63
64impl std::error::Error for ConvertError {}
65
66impl From<ConvertError> for Error {
67    fn from(e: ConvertError) -> Self {
68        Error::ParseError(e.to_string())
69    }
70}
71
72// =============================================================================
73// Core Traits
74// =============================================================================
75
76/// Convert a Rust type into a TeaLeaf `Value` and collect associated schemas.
77pub trait ToTeaLeaf {
78    /// Convert this value to a TeaLeaf `Value`.
79    fn to_tealeaf_value(&self) -> Value;
80
81    /// Collect all schemas required by this type and its nested types.
82    ///
83    /// Returns a map from schema name to Schema definition.
84    /// Default implementation returns an empty map (for primitives).
85    fn collect_schemas() -> IndexMap<String, Schema> {
86        IndexMap::new()
87    }
88
89    /// Collect all union definitions required by this type and its nested types.
90    ///
91    /// Returns a map from union name to Union definition.
92    /// Default implementation returns an empty map (for primitives and structs without enum fields).
93    fn collect_unions() -> IndexMap<String, Union> {
94        IndexMap::new()
95    }
96
97    /// The TeaLeaf field type that represents this type in a schema.
98    fn tealeaf_field_type() -> FieldType;
99}
100
101/// Convert a TeaLeaf `Value` back into a Rust type.
102pub trait FromTeaLeaf: Sized {
103    /// Attempt to reconstruct this type from a TeaLeaf `Value`.
104    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError>;
105}
106
107/// Extension trait providing convenience methods on types implementing `ToTeaLeaf`.
108pub trait ToTeaLeafExt: ToTeaLeaf + Sized {
109    /// Convert to a TeaLeaf document with a single root entry.
110    fn to_tealeaf_doc(&self, key: &str) -> crate::TeaLeaf {
111        crate::TeaLeaf::from_dto(key, self)
112    }
113
114    /// Convert to TeaLeaf text format string.
115    fn to_tl_string(&self, key: &str) -> String {
116        self.to_tealeaf_doc(key).to_tl_with_schemas()
117    }
118
119    /// Compile to binary .tlbx format.
120    fn to_tlbx(
121        &self,
122        key: &str,
123        path: impl AsRef<std::path::Path>,
124        compress: bool,
125    ) -> crate::Result<()> {
126        self.to_tealeaf_doc(key).compile(path, compress)
127    }
128
129    /// Convert to JSON string (pretty-printed).
130    fn to_tealeaf_json(&self, key: &str) -> crate::Result<String> {
131        self.to_tealeaf_doc(key).to_json()
132    }
133}
134
135// Blanket implementation: any ToTeaLeaf type gets convenience methods
136impl<T: ToTeaLeaf> ToTeaLeafExt for T {}
137
138// =============================================================================
139// Primitive ToTeaLeaf Implementations
140// =============================================================================
141
142impl ToTeaLeaf for bool {
143    fn to_tealeaf_value(&self) -> Value {
144        Value::Bool(*self)
145    }
146    fn tealeaf_field_type() -> FieldType {
147        FieldType::new("bool")
148    }
149}
150
151impl ToTeaLeaf for i8 {
152    fn to_tealeaf_value(&self) -> Value {
153        Value::Int(*self as i64)
154    }
155    fn tealeaf_field_type() -> FieldType {
156        FieldType::new("int8")
157    }
158}
159
160impl ToTeaLeaf for i16 {
161    fn to_tealeaf_value(&self) -> Value {
162        Value::Int(*self as i64)
163    }
164    fn tealeaf_field_type() -> FieldType {
165        FieldType::new("int16")
166    }
167}
168
169impl ToTeaLeaf for i32 {
170    fn to_tealeaf_value(&self) -> Value {
171        Value::Int(*self as i64)
172    }
173    fn tealeaf_field_type() -> FieldType {
174        FieldType::new("int")
175    }
176}
177
178impl ToTeaLeaf for i64 {
179    fn to_tealeaf_value(&self) -> Value {
180        Value::Int(*self)
181    }
182    fn tealeaf_field_type() -> FieldType {
183        FieldType::new("int64")
184    }
185}
186
187impl ToTeaLeaf for u8 {
188    fn to_tealeaf_value(&self) -> Value {
189        Value::UInt(*self as u64)
190    }
191    fn tealeaf_field_type() -> FieldType {
192        FieldType::new("uint8")
193    }
194}
195
196impl ToTeaLeaf for u16 {
197    fn to_tealeaf_value(&self) -> Value {
198        Value::UInt(*self as u64)
199    }
200    fn tealeaf_field_type() -> FieldType {
201        FieldType::new("uint16")
202    }
203}
204
205impl ToTeaLeaf for u32 {
206    fn to_tealeaf_value(&self) -> Value {
207        Value::UInt(*self as u64)
208    }
209    fn tealeaf_field_type() -> FieldType {
210        FieldType::new("uint")
211    }
212}
213
214impl ToTeaLeaf for u64 {
215    fn to_tealeaf_value(&self) -> Value {
216        Value::UInt(*self)
217    }
218    fn tealeaf_field_type() -> FieldType {
219        FieldType::new("uint64")
220    }
221}
222
223impl ToTeaLeaf for f32 {
224    fn to_tealeaf_value(&self) -> Value {
225        Value::Float(*self as f64)
226    }
227    fn tealeaf_field_type() -> FieldType {
228        FieldType::new("float32")
229    }
230}
231
232impl ToTeaLeaf for f64 {
233    fn to_tealeaf_value(&self) -> Value {
234        Value::Float(*self)
235    }
236    fn tealeaf_field_type() -> FieldType {
237        FieldType::new("float")
238    }
239}
240
241impl ToTeaLeaf for String {
242    fn to_tealeaf_value(&self) -> Value {
243        Value::String(self.clone())
244    }
245    fn tealeaf_field_type() -> FieldType {
246        FieldType::new("string")
247    }
248}
249
250impl ToTeaLeaf for &str {
251    fn to_tealeaf_value(&self) -> Value {
252        Value::String(self.to_string())
253    }
254    fn tealeaf_field_type() -> FieldType {
255        FieldType::new("string")
256    }
257}
258
259// Vec<u8> is special: maps to Bytes, not Array
260impl ToTeaLeaf for Vec<u8> {
261    fn to_tealeaf_value(&self) -> Value {
262        Value::Bytes(self.clone())
263    }
264    fn tealeaf_field_type() -> FieldType {
265        FieldType::new("bytes")
266    }
267}
268
269// =============================================================================
270// Generic ToTeaLeaf Implementations
271// =============================================================================
272
273impl<T: ToTeaLeaf> ToTeaLeaf for Option<T> {
274    fn to_tealeaf_value(&self) -> Value {
275        match self {
276            Some(v) => v.to_tealeaf_value(),
277            None => Value::Null,
278        }
279    }
280    fn collect_schemas() -> IndexMap<String, Schema> {
281        T::collect_schemas()
282    }
283    fn collect_unions() -> IndexMap<String, Union> {
284        T::collect_unions()
285    }
286    fn tealeaf_field_type() -> FieldType {
287        T::tealeaf_field_type().nullable()
288    }
289}
290
291/// Marker trait to exclude `u8` from generic `Vec<T>` impl.
292/// `Vec<u8>` has its own specialization mapping to `Value::Bytes`.
293pub trait NotU8 {}
294impl NotU8 for bool {}
295impl NotU8 for i8 {}
296impl NotU8 for i16 {}
297impl NotU8 for i32 {}
298impl NotU8 for i64 {}
299impl NotU8 for u16 {}
300impl NotU8 for u32 {}
301impl NotU8 for u64 {}
302impl NotU8 for f32 {}
303impl NotU8 for f64 {}
304impl NotU8 for String {}
305impl<T> NotU8 for Vec<T> {}
306impl<T> NotU8 for Option<T> {}
307impl<K, V> NotU8 for HashMap<K, V> {}
308impl<K, V> NotU8 for IndexMap<K, V> {}
309impl<T> NotU8 for Box<T> {}
310impl<T> NotU8 for std::sync::Arc<T> {}
311impl<T> NotU8 for std::rc::Rc<T> {}
312
313impl<T: ToTeaLeaf + NotU8> ToTeaLeaf for Vec<T> {
314    fn to_tealeaf_value(&self) -> Value {
315        Value::Array(self.iter().map(|v| v.to_tealeaf_value()).collect())
316    }
317    fn collect_schemas() -> IndexMap<String, Schema> {
318        T::collect_schemas()
319    }
320    fn collect_unions() -> IndexMap<String, Union> {
321        T::collect_unions()
322    }
323    fn tealeaf_field_type() -> FieldType {
324        T::tealeaf_field_type().array()
325    }
326}
327
328impl<V: ToTeaLeaf> ToTeaLeaf for HashMap<String, V> {
329    fn to_tealeaf_value(&self) -> Value {
330        Value::Object(
331            self.iter()
332                .map(|(k, v)| (k.clone(), v.to_tealeaf_value()))
333                .collect(),
334        )
335    }
336    fn collect_schemas() -> IndexMap<String, Schema> {
337        V::collect_schemas()
338    }
339    fn collect_unions() -> IndexMap<String, Union> {
340        V::collect_unions()
341    }
342    fn tealeaf_field_type() -> FieldType {
343        FieldType::new("object")
344    }
345}
346
347impl<V: ToTeaLeaf> ToTeaLeaf for IndexMap<String, V> {
348    fn to_tealeaf_value(&self) -> Value {
349        Value::Object(
350            self.iter()
351                .map(|(k, v)| (k.clone(), v.to_tealeaf_value()))
352                .collect(),
353        )
354    }
355    fn collect_schemas() -> IndexMap<String, Schema> {
356        V::collect_schemas()
357    }
358    fn collect_unions() -> IndexMap<String, Union> {
359        V::collect_unions()
360    }
361    fn tealeaf_field_type() -> FieldType {
362        FieldType::new("object")
363    }
364}
365
366// Transparent wrappers
367impl<T: ToTeaLeaf> ToTeaLeaf for Box<T> {
368    fn to_tealeaf_value(&self) -> Value {
369        (**self).to_tealeaf_value()
370    }
371    fn collect_schemas() -> IndexMap<String, Schema> {
372        T::collect_schemas()
373    }
374    fn collect_unions() -> IndexMap<String, Union> {
375        T::collect_unions()
376    }
377    fn tealeaf_field_type() -> FieldType {
378        T::tealeaf_field_type()
379    }
380}
381
382impl<T: ToTeaLeaf> ToTeaLeaf for std::sync::Arc<T> {
383    fn to_tealeaf_value(&self) -> Value {
384        (**self).to_tealeaf_value()
385    }
386    fn collect_schemas() -> IndexMap<String, Schema> {
387        T::collect_schemas()
388    }
389    fn collect_unions() -> IndexMap<String, Union> {
390        T::collect_unions()
391    }
392    fn tealeaf_field_type() -> FieldType {
393        T::tealeaf_field_type()
394    }
395}
396
397impl<T: ToTeaLeaf> ToTeaLeaf for std::rc::Rc<T> {
398    fn to_tealeaf_value(&self) -> Value {
399        (**self).to_tealeaf_value()
400    }
401    fn collect_schemas() -> IndexMap<String, Schema> {
402        T::collect_schemas()
403    }
404    fn collect_unions() -> IndexMap<String, Union> {
405        T::collect_unions()
406    }
407    fn tealeaf_field_type() -> FieldType {
408        T::tealeaf_field_type()
409    }
410}
411
412// Tuple implementations (2 through 6 elements)
413macro_rules! impl_to_tealeaf_tuple {
414    ($($idx:tt: $T:ident),+) => {
415        impl<$($T: ToTeaLeaf),+> ToTeaLeaf for ($($T,)+) {
416            fn to_tealeaf_value(&self) -> Value {
417                Value::Array(vec![$(self.$idx.to_tealeaf_value()),+])
418            }
419            fn collect_schemas() -> IndexMap<String, Schema> {
420                let mut schemas = IndexMap::new();
421                $(schemas.extend($T::collect_schemas());)+
422                schemas
423            }
424            fn collect_unions() -> IndexMap<String, Union> {
425                let mut unions = IndexMap::new();
426                $(unions.extend($T::collect_unions());)+
427                unions
428            }
429            fn tealeaf_field_type() -> FieldType {
430                FieldType::new("tuple")
431            }
432        }
433    };
434}
435
436impl_to_tealeaf_tuple!(0: A, 1: B);
437impl_to_tealeaf_tuple!(0: A, 1: B, 2: C);
438impl_to_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D);
439impl_to_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
440impl_to_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
441
442// =============================================================================
443// Primitive FromTeaLeaf Implementations
444// =============================================================================
445
446impl FromTeaLeaf for bool {
447    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
448        value.as_bool().ok_or_else(|| ConvertError::TypeMismatch {
449            expected: "bool".into(),
450            got: format!("{:?}", value.tl_type()),
451            path: String::new(),
452        })
453    }
454}
455
456impl FromTeaLeaf for i8 {
457    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
458        value
459            .as_int()
460            .map(|i| i as i8)
461            .ok_or_else(|| ConvertError::TypeMismatch {
462                expected: "int8".into(),
463                got: format!("{:?}", value.tl_type()),
464                path: String::new(),
465            })
466    }
467}
468
469impl FromTeaLeaf for i16 {
470    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
471        value
472            .as_int()
473            .map(|i| i as i16)
474            .ok_or_else(|| ConvertError::TypeMismatch {
475                expected: "int16".into(),
476                got: format!("{:?}", value.tl_type()),
477                path: String::new(),
478            })
479    }
480}
481
482impl FromTeaLeaf for i32 {
483    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
484        value
485            .as_int()
486            .map(|i| i as i32)
487            .ok_or_else(|| ConvertError::TypeMismatch {
488                expected: "int".into(),
489                got: format!("{:?}", value.tl_type()),
490                path: String::new(),
491            })
492    }
493}
494
495impl FromTeaLeaf for i64 {
496    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
497        value.as_int().ok_or_else(|| ConvertError::TypeMismatch {
498            expected: "int64".into(),
499            got: format!("{:?}", value.tl_type()),
500            path: String::new(),
501        })
502    }
503}
504
505impl FromTeaLeaf for u8 {
506    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
507        value
508            .as_uint()
509            .map(|u| u as u8)
510            .ok_or_else(|| ConvertError::TypeMismatch {
511                expected: "uint8".into(),
512                got: format!("{:?}", value.tl_type()),
513                path: String::new(),
514            })
515    }
516}
517
518impl FromTeaLeaf for u16 {
519    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
520        value
521            .as_uint()
522            .map(|u| u as u16)
523            .ok_or_else(|| ConvertError::TypeMismatch {
524                expected: "uint16".into(),
525                got: format!("{:?}", value.tl_type()),
526                path: String::new(),
527            })
528    }
529}
530
531impl FromTeaLeaf for u32 {
532    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
533        value
534            .as_uint()
535            .map(|u| u as u32)
536            .ok_or_else(|| ConvertError::TypeMismatch {
537                expected: "uint".into(),
538                got: format!("{:?}", value.tl_type()),
539                path: String::new(),
540            })
541    }
542}
543
544impl FromTeaLeaf for u64 {
545    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
546        value.as_uint().ok_or_else(|| ConvertError::TypeMismatch {
547            expected: "uint64".into(),
548            got: format!("{:?}", value.tl_type()),
549            path: String::new(),
550        })
551    }
552}
553
554impl FromTeaLeaf for f32 {
555    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
556        value
557            .as_float()
558            .map(|f| f as f32)
559            .ok_or_else(|| ConvertError::TypeMismatch {
560                expected: "float32".into(),
561                got: format!("{:?}", value.tl_type()),
562                path: String::new(),
563            })
564    }
565}
566
567impl FromTeaLeaf for f64 {
568    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
569        value.as_float().ok_or_else(|| ConvertError::TypeMismatch {
570            expected: "float".into(),
571            got: format!("{:?}", value.tl_type()),
572            path: String::new(),
573        })
574    }
575}
576
577impl FromTeaLeaf for String {
578    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
579        value
580            .as_str()
581            .map(|s| s.to_string())
582            .ok_or_else(|| ConvertError::TypeMismatch {
583                expected: "string".into(),
584                got: format!("{:?}", value.tl_type()),
585                path: String::new(),
586            })
587    }
588}
589
590// Vec<u8> from Bytes
591impl FromTeaLeaf for Vec<u8> {
592    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
593        value
594            .as_bytes()
595            .map(|b| b.to_vec())
596            .ok_or_else(|| ConvertError::TypeMismatch {
597                expected: "bytes".into(),
598                got: format!("{:?}", value.tl_type()),
599                path: String::new(),
600            })
601    }
602}
603
604// =============================================================================
605// Generic FromTeaLeaf Implementations
606// =============================================================================
607
608impl<T: FromTeaLeaf> FromTeaLeaf for Option<T> {
609    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
610        if value.is_null() {
611            Ok(None)
612        } else {
613            T::from_tealeaf_value(value).map(Some)
614        }
615    }
616}
617
618impl<T: FromTeaLeaf + NotU8> FromTeaLeaf for Vec<T> {
619    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
620        let arr = value.as_array().ok_or_else(|| ConvertError::TypeMismatch {
621            expected: "array".into(),
622            got: format!("{:?}", value.tl_type()),
623            path: String::new(),
624        })?;
625        arr.iter()
626            .enumerate()
627            .map(|(i, v)| {
628                T::from_tealeaf_value(v).map_err(|e| ConvertError::Nested {
629                    path: format!("[{}]", i),
630                    source: Box::new(e),
631                })
632            })
633            .collect()
634    }
635}
636
637impl<V: FromTeaLeaf> FromTeaLeaf for HashMap<String, V> {
638    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
639        let obj = value
640            .as_object()
641            .ok_or_else(|| ConvertError::TypeMismatch {
642                expected: "object".into(),
643                got: format!("{:?}", value.tl_type()),
644                path: String::new(),
645            })?;
646        let mut map = HashMap::new();
647        for (k, v) in obj {
648            let val = V::from_tealeaf_value(v).map_err(|e| ConvertError::Nested {
649                path: k.clone(),
650                source: Box::new(e),
651            })?;
652            map.insert(k.clone(), val);
653        }
654        Ok(map)
655    }
656}
657
658impl<V: FromTeaLeaf> FromTeaLeaf for IndexMap<String, V> {
659    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
660        let obj = value
661            .as_object()
662            .ok_or_else(|| ConvertError::TypeMismatch {
663                expected: "object".into(),
664                got: format!("{:?}", value.tl_type()),
665                path: String::new(),
666            })?;
667        let mut map = IndexMap::new();
668        for (k, v) in obj {
669            let val = V::from_tealeaf_value(v).map_err(|e| ConvertError::Nested {
670                path: k.clone(),
671                source: Box::new(e),
672            })?;
673            map.insert(k.clone(), val);
674        }
675        Ok(map)
676    }
677}
678
679impl<T: FromTeaLeaf> FromTeaLeaf for Box<T> {
680    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
681        T::from_tealeaf_value(value).map(Box::new)
682    }
683}
684
685impl<T: FromTeaLeaf> FromTeaLeaf for std::sync::Arc<T> {
686    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
687        T::from_tealeaf_value(value).map(std::sync::Arc::new)
688    }
689}
690
691impl<T: FromTeaLeaf> FromTeaLeaf for std::rc::Rc<T> {
692    fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
693        T::from_tealeaf_value(value).map(std::rc::Rc::new)
694    }
695}
696
697// Tuple FromTeaLeaf implementations
698macro_rules! impl_from_tealeaf_tuple {
699    ($($idx:tt: $T:ident),+) => {
700        impl<$($T: FromTeaLeaf),+> FromTeaLeaf for ($($T,)+) {
701            fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError> {
702                let arr = value.as_array().ok_or_else(|| ConvertError::TypeMismatch {
703                    expected: "tuple (array)".into(),
704                    got: format!("{:?}", value.tl_type()),
705                    path: String::new(),
706                })?;
707                Ok(($(
708                    $T::from_tealeaf_value(
709                        arr.get($idx).ok_or_else(|| ConvertError::MissingField {
710                            struct_name: "tuple".into(),
711                            field: format!("index {}", $idx),
712                        })?
713                    ).map_err(|e| ConvertError::Nested {
714                        path: format!("[{}]", $idx),
715                        source: Box::new(e),
716                    })?,
717                )+))
718            }
719        }
720    };
721}
722
723impl_from_tealeaf_tuple!(0: A, 1: B);
724impl_from_tealeaf_tuple!(0: A, 1: B, 2: C);
725impl_from_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D);
726impl_from_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
727impl_from_tealeaf_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
728
729// =============================================================================
730// Tests
731// =============================================================================
732
733#[cfg(test)]
734mod tests {
735    use super::*;
736
737    #[test]
738    fn test_primitive_to_tealeaf() {
739        assert_eq!(42i64.to_tealeaf_value(), Value::Int(42));
740        assert_eq!(true.to_tealeaf_value(), Value::Bool(true));
741        assert_eq!("hello".to_tealeaf_value(), Value::String("hello".into()));
742        assert_eq!(3.14f64.to_tealeaf_value(), Value::Float(3.14));
743        assert_eq!(42u32.to_tealeaf_value(), Value::UInt(42));
744    }
745
746    #[test]
747    fn test_primitive_from_tealeaf() {
748        assert_eq!(i64::from_tealeaf_value(&Value::Int(42)).unwrap(), 42);
749        assert_eq!(
750            bool::from_tealeaf_value(&Value::Bool(true)).unwrap(),
751            true
752        );
753        assert_eq!(
754            String::from_tealeaf_value(&Value::String("hi".into())).unwrap(),
755            "hi"
756        );
757        assert_eq!(
758            f64::from_tealeaf_value(&Value::Float(3.14)).unwrap(),
759            3.14
760        );
761    }
762
763    #[test]
764    fn test_option_roundtrip() {
765        let some: Option<i64> = Some(42);
766        let none: Option<i64> = None;
767        assert_eq!(some.to_tealeaf_value(), Value::Int(42));
768        assert_eq!(none.to_tealeaf_value(), Value::Null);
769        assert_eq!(
770            Option::<i64>::from_tealeaf_value(&Value::Int(42)).unwrap(),
771            Some(42)
772        );
773        assert_eq!(
774            Option::<i64>::from_tealeaf_value(&Value::Null).unwrap(),
775            None
776        );
777    }
778
779    #[test]
780    fn test_vec_roundtrip() {
781        let v = vec![1i64, 2, 3];
782        let val = v.to_tealeaf_value();
783        assert_eq!(
784            val,
785            Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
786        );
787        assert_eq!(Vec::<i64>::from_tealeaf_value(&val).unwrap(), vec![1, 2, 3]);
788    }
789
790    #[test]
791    fn test_vec_u8_as_bytes() {
792        let v: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
793        let val = v.to_tealeaf_value();
794        assert_eq!(val, Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]));
795        assert_eq!(Vec::<u8>::from_tealeaf_value(&val).unwrap(), v);
796    }
797
798    #[test]
799    fn test_hashmap_roundtrip() {
800        let mut map = HashMap::new();
801        map.insert("key".to_string(), 42i64);
802        let val = map.to_tealeaf_value();
803        let restored = HashMap::<String, i64>::from_tealeaf_value(&val).unwrap();
804        assert_eq!(restored.get("key"), Some(&42));
805    }
806
807    #[test]
808    fn test_field_type_primitives() {
809        assert_eq!(i32::tealeaf_field_type(), FieldType::new("int"));
810        assert_eq!(String::tealeaf_field_type(), FieldType::new("string"));
811        assert_eq!(
812            Option::<i32>::tealeaf_field_type(),
813            FieldType::new("int").nullable()
814        );
815        assert_eq!(
816            Vec::<String>::tealeaf_field_type(),
817            FieldType::new("string").array()
818        );
819        assert_eq!(Vec::<u8>::tealeaf_field_type(), FieldType::new("bytes"));
820    }
821
822    #[test]
823    fn test_type_mismatch_error() {
824        let err = i64::from_tealeaf_value(&Value::String("oops".into())).unwrap_err();
825        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
826    }
827
828    #[test]
829    fn test_box_transparent() {
830        let boxed = Box::new(42i64);
831        assert_eq!(boxed.to_tealeaf_value(), Value::Int(42));
832        assert_eq!(
833            Box::<i64>::from_tealeaf_value(&Value::Int(42)).unwrap(),
834            Box::new(42)
835        );
836    }
837
838    #[test]
839    fn test_box_collect_schemas() {
840        let schemas = Box::<i64>::collect_schemas();
841        assert!(schemas.is_empty());
842    }
843
844    #[test]
845    fn test_box_field_type() {
846        assert_eq!(Box::<i64>::tealeaf_field_type(), FieldType::new("int64"));
847        assert_eq!(Box::<String>::tealeaf_field_type(), FieldType::new("string"));
848    }
849
850    #[test]
851    fn test_f64_field_type() {
852        assert_eq!(f64::tealeaf_field_type(), FieldType::new("float"));
853    }
854
855    #[test]
856    fn test_i8_from_wrong_type() {
857        let err = i8::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
858        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
859    }
860
861    #[test]
862    fn test_i16_from_wrong_type() {
863        let err = i16::from_tealeaf_value(&Value::Bool(true)).unwrap_err();
864        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
865    }
866
867    #[test]
868    fn test_i32_from_wrong_type() {
869        let err = i32::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
870        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
871    }
872
873    #[test]
874    fn test_u8_from_wrong_type() {
875        let err = u8::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
876        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
877    }
878
879    #[test]
880    fn test_u16_from_wrong_type() {
881        let err = u16::from_tealeaf_value(&Value::Bool(false)).unwrap_err();
882        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
883    }
884
885    #[test]
886    fn test_u32_from_wrong_type() {
887        let err = u32::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
888        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
889    }
890
891    #[test]
892    fn test_u64_from_wrong_type() {
893        let err = u64::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
894        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
895    }
896
897    #[test]
898    fn test_f32_from_wrong_type() {
899        let err = f32::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
900        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
901    }
902
903    #[test]
904    fn test_f64_from_wrong_type() {
905        let err = f64::from_tealeaf_value(&Value::String("nope".into())).unwrap_err();
906        assert!(matches!(err, ConvertError::TypeMismatch { .. }));
907    }
908
909    #[test]
910    fn test_to_tlbx_convenience() {
911        let val = 42i64;
912        let dir = std::env::temp_dir();
913        let path = dir.join("test_convert_to_tlbx.tlbx");
914        val.to_tlbx("num", &path, false).unwrap();
915        assert!(path.exists());
916        std::fs::remove_file(&path).ok();
917    }
918
919    #[test]
920    fn test_to_tealeaf_json_convenience() {
921        let val = 42i64;
922        let json = val.to_tealeaf_json("num").unwrap();
923        assert!(json.contains("42"));
924    }
925
926    #[test]
927    fn test_tuple_roundtrip() {
928        let t = (1i64, "hello".to_string());
929        let val = t.to_tealeaf_value();
930        assert_eq!(
931            val,
932            Value::Array(vec![Value::Int(1), Value::String("hello".into())])
933        );
934        let restored = <(i64, String)>::from_tealeaf_value(&val).unwrap();
935        assert_eq!(restored, (1, "hello".to_string()));
936    }
937
938    #[test]
939    fn test_nested_option_vec() {
940        let v: Option<Vec<i32>> = Some(vec![1, 2, 3]);
941        let val = v.to_tealeaf_value();
942        assert_eq!(
943            val,
944            Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
945        );
946        let restored = Option::<Vec<i32>>::from_tealeaf_value(&val).unwrap();
947        assert_eq!(restored, Some(vec![1, 2, 3]));
948    }
949
950    #[test]
951    fn test_convert_error_display() {
952        let err = ConvertError::MissingField {
953            struct_name: "User".into(),
954            field: "name".into(),
955        };
956        assert_eq!(err.to_string(), "Missing field 'name' in struct 'User'");
957
958        let err = ConvertError::TypeMismatch {
959            expected: "int".into(),
960            got: "String".into(),
961            path: "User.age".into(),
962        };
963        assert_eq!(
964            err.to_string(),
965            "Type mismatch at 'User.age': expected int, got String"
966        );
967    }
968}