1use std::collections::HashMap;
7use std::fmt;
8use indexmap::IndexMap;
9
10use crate::{Error, FieldType, Schema, Union, Value};
11
12#[derive(Debug)]
18pub enum ConvertError {
19 MissingField {
21 struct_name: String,
22 field: String,
23 },
24 TypeMismatch {
26 expected: String,
27 got: String,
28 path: String,
29 },
30 Nested {
32 path: String,
33 source: Box<ConvertError>,
34 },
35 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
72pub trait ToTeaLeaf {
78 fn to_tealeaf_value(&self) -> Value;
80
81 fn collect_schemas() -> IndexMap<String, Schema> {
86 IndexMap::new()
87 }
88
89 fn collect_unions() -> IndexMap<String, Union> {
94 IndexMap::new()
95 }
96
97 fn tealeaf_field_type() -> FieldType;
99}
100
101pub trait FromTeaLeaf: Sized {
103 fn from_tealeaf_value(value: &Value) -> Result<Self, ConvertError>;
105}
106
107pub trait ToTeaLeafExt: ToTeaLeaf + Sized {
109 fn to_tealeaf_doc(&self, key: &str) -> crate::TeaLeaf {
111 crate::TeaLeaf::from_dto(key, self)
112 }
113
114 fn to_tl_string(&self, key: &str) -> String {
116 self.to_tealeaf_doc(key).to_tl_with_schemas()
117 }
118
119 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 fn to_tealeaf_json(&self, key: &str) -> crate::Result<String> {
131 self.to_tealeaf_doc(key).to_json()
132 }
133}
134
135impl<T: ToTeaLeaf> ToTeaLeafExt for T {}
137
138impl 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
259impl 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
269impl<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
291pub 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
366impl<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
412macro_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
442impl 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
590impl 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
604impl<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
697macro_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#[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}