1use crate::error::{DocError, Result};
12use crate::metadata::TableColumn;
13use crate::traits::DocConverter;
14use crate::types::DocValue;
15use std::any::{Any, TypeId};
16use std::collections::HashMap;
17use std::marker::PhantomData;
18
19use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
21
22pub trait ErasedConverter: Send + Sync {
36 fn to_doc_value_erased(&self, value: &dyn Any, column: &TableColumn) -> Result<DocValue>;
42
43 fn from_doc_value_erased(&self, value: &DocValue, column: &TableColumn)
49 -> Result<Box<dyn Any>>;
50}
51
52struct TypedConverter<T: 'static, C: DocConverter<T>> {
57 converter: C,
58 _phantom: PhantomData<fn() -> T>,
61}
62
63impl<T: 'static, C: DocConverter<T>> TypedConverter<T, C> {
64 fn new(converter: C) -> Self {
66 Self {
67 converter,
68 _phantom: PhantomData,
69 }
70 }
71}
72
73impl<T: 'static, C: DocConverter<T> + Send + Sync + 'static> ErasedConverter
74 for TypedConverter<T, C>
75{
76 fn to_doc_value_erased(&self, value: &dyn Any, column: &TableColumn) -> Result<DocValue> {
77 let typed = value
78 .downcast_ref::<T>()
79 .ok_or_else(|| DocError::Conversion {
80 field: column.field_name.clone(),
81 value: format!("{value:?}"),
82 message: format!(
83 "type mismatch: expected {}, found a different concrete type",
84 std::any::type_name::<T>()
85 ),
86 })?;
87 self.converter.to_doc_value(typed, column)
88 }
89
90 fn from_doc_value_erased(
91 &self,
92 value: &DocValue,
93 column: &TableColumn,
94 ) -> Result<Box<dyn Any>> {
95 let typed = self.converter.from_doc_value(value, column)?;
96 Ok(Box::new(typed))
97 }
98}
99
100#[derive(Default)]
142pub struct ConverterRegistry {
143 converters: HashMap<TypeId, Box<dyn ErasedConverter>>,
144 name_to_type: HashMap<String, TypeId>,
146}
147
148impl ConverterRegistry {
149 #[must_use]
151 pub fn new() -> Self {
152 Self {
153 converters: HashMap::new(),
154 name_to_type: HashMap::new(),
155 }
156 }
157
158 pub fn register<T: 'static, C: DocConverter<T> + Send + Sync + 'static>(
164 &mut self,
165 converter: C,
166 ) -> bool {
167 let type_id = TypeId::of::<T>();
168 let existed = self.converters.contains_key(&type_id);
169 let erased: Box<dyn ErasedConverter> = Box::new(TypedConverter::<T, C>::new(converter));
170 self.converters.insert(type_id, erased);
171 !existed
172 }
173
174 pub fn register_named<T: 'static, C: DocConverter<T> + Send + Sync + 'static>(
182 &mut self,
183 name: &str,
184 converter: C,
185 ) -> bool {
186 let type_id = TypeId::of::<T>();
187 let existed = self.converters.contains_key(&type_id);
188 let erased: Box<dyn ErasedConverter> = Box::new(TypedConverter::<T, C>::new(converter));
189 self.converters.insert(type_id, erased);
190 self.name_to_type.insert(name.to_owned(), type_id);
191 !existed
192 }
193
194 #[must_use]
196 pub fn contains<T: 'static>(&self) -> bool {
197 self.converters.contains_key(&TypeId::of::<T>())
198 }
199
200 #[must_use]
204 pub fn find_converter<T: 'static>(&self) -> Option<&dyn ErasedConverter> {
205 self.converters
206 .get(&TypeId::of::<T>())
207 .map(std::convert::AsRef::as_ref)
208 }
209
210 #[must_use]
214 pub fn find_converter_by_name(&self, name: &str) -> Option<&dyn ErasedConverter> {
215 self.name_to_type
216 .get(name)
217 .and_then(|type_id| self.converters.get(type_id))
218 .map(std::convert::AsRef::as_ref)
219 }
220
221 pub fn to_doc_value<V: 'static + std::fmt::Debug>(
229 &self,
230 value: &V,
231 column: &TableColumn,
232 ) -> Result<DocValue> {
233 if let Some(converter) = self.find_converter::<V>() {
234 return converter.to_doc_value_erased(value as &dyn Any, column);
235 }
236 fallback_to_doc_value(value, column)
238 }
239
240 pub fn from_doc_value<V: 'static>(&self, value: &DocValue, column: &TableColumn) -> Result<V> {
246 if let Some(converter) = self.find_converter::<V>() {
247 let boxed = converter.from_doc_value_erased(value, column)?;
248 return boxed
249 .downcast::<V>()
250 .map(|b| *b)
251 .map_err(|_| DocError::Conversion {
252 field: column.field_name.clone(),
253 value: format!("{value:?}"),
254 message: format!(
255 "converter returned wrong concrete type for {}",
256 std::any::type_name::<V>()
257 ),
258 });
259 }
260 fallback_from_doc_value(value, column)
261 }
262}
263
264impl std::fmt::Debug for ConverterRegistry {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 f.debug_struct("ConverterRegistry")
267 .field("count", &self.converters.len())
268 .field("named_count", &self.name_to_type.len())
269 .finish_non_exhaustive()
270 }
271}
272
273trait FallbackConvert: Sized {
282 fn to_doc_value_from_ref(&self) -> DocValue;
283 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self>;
284}
285
286impl FallbackConvert for String {
288 fn to_doc_value_from_ref(&self) -> DocValue {
289 DocValue::String(self.clone())
290 }
291 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
292 match value {
293 DocValue::String(s) => Ok(s.clone()),
294 DocValue::Int(n) => Ok(n.to_string()),
295 DocValue::Float(n) => Ok(n.to_string()),
296 DocValue::Bool(b) => Ok(b.to_string()),
297 DocValue::Empty => Ok(String::new()),
298 other => Err(DocError::Conversion {
299 field: column.field_name.clone(),
300 value: format!("{other:?}"),
301 message: "cannot convert to String".to_owned(),
302 }),
303 }
304 }
305}
306
307impl FallbackConvert for i64 {
308 fn to_doc_value_from_ref(&self) -> DocValue {
309 DocValue::Int(*self)
310 }
311 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
312 match value {
313 DocValue::Int(n) => Ok(*n),
314 DocValue::String(s) => s.parse().map_err(|_| DocError::Conversion {
315 field: column.field_name.clone(),
316 value: s.clone(),
317 message: "cannot parse as i64".to_owned(),
318 }),
319 other => Err(DocError::Conversion {
320 field: column.field_name.clone(),
321 value: format!("{other:?}"),
322 message: "cannot convert to i64".to_owned(),
323 }),
324 }
325 }
326}
327
328impl FallbackConvert for i32 {
329 fn to_doc_value_from_ref(&self) -> DocValue {
330 DocValue::Int(i64::from(*self))
331 }
332 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
333 match value {
334 DocValue::Int(n) => Ok(*n as i32),
335 DocValue::String(s) => s.parse().map_err(|_| DocError::Conversion {
336 field: column.field_name.clone(),
337 value: s.clone(),
338 message: "cannot parse as i32".to_owned(),
339 }),
340 other => Err(DocError::Conversion {
341 field: column.field_name.clone(),
342 value: format!("{other:?}"),
343 message: "cannot convert to i32".to_owned(),
344 }),
345 }
346 }
347}
348
349impl FallbackConvert for u32 {
350 fn to_doc_value_from_ref(&self) -> DocValue {
351 DocValue::Int(i64::from(*self))
352 }
353 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
354 match value {
355 DocValue::Int(n) => Ok(*n as u32),
356 DocValue::String(s) => s.parse().map_err(|_| DocError::Conversion {
357 field: column.field_name.clone(),
358 value: s.clone(),
359 message: "cannot parse as u32".to_owned(),
360 }),
361 other => Err(DocError::Conversion {
362 field: column.field_name.clone(),
363 value: format!("{other:?}"),
364 message: "cannot convert to u32".to_owned(),
365 }),
366 }
367 }
368}
369
370impl FallbackConvert for f64 {
371 fn to_doc_value_from_ref(&self) -> DocValue {
372 DocValue::Float(*self)
373 }
374 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
375 match value {
376 DocValue::Float(n) => Ok(*n),
377 DocValue::Int(n) => Ok(*n as f64),
378 DocValue::String(s) => s.parse().map_err(|_| DocError::Conversion {
379 field: column.field_name.clone(),
380 value: s.clone(),
381 message: "cannot parse as f64".to_owned(),
382 }),
383 other => Err(DocError::Conversion {
384 field: column.field_name.clone(),
385 value: format!("{other:?}"),
386 message: "cannot convert to f64".to_owned(),
387 }),
388 }
389 }
390}
391
392impl FallbackConvert for bool {
393 fn to_doc_value_from_ref(&self) -> DocValue {
394 DocValue::Bool(*self)
395 }
396 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
397 match value {
398 DocValue::Bool(b) => Ok(*b),
399 DocValue::String(s) => {
400 let lower = s.to_lowercase();
401 if lower == "true" || lower == "1" || lower == "yes" {
402 Ok(true)
403 } else if lower == "false" || lower == "0" || lower == "no" {
404 Ok(false)
405 } else {
406 Err(DocError::Conversion {
407 field: column.field_name.clone(),
408 value: s.clone(),
409 message: "cannot parse as bool".to_owned(),
410 })
411 }
412 }
413 DocValue::Int(n) => Ok(*n != 0),
414 other => Err(DocError::Conversion {
415 field: column.field_name.clone(),
416 value: format!("{other:?}"),
417 message: "cannot convert to bool".to_owned(),
418 }),
419 }
420 }
421}
422
423impl FallbackConvert for DateTime<Utc> {
424 fn to_doc_value_from_ref(&self) -> DocValue {
425 DocValue::DateTime(*self)
426 }
427 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
428 match value {
429 DocValue::DateTime(dt) => Ok(*dt),
430 DocValue::String(s) => s.parse().map_err(|_| DocError::Conversion {
431 field: column.field_name.clone(),
432 value: s.clone(),
433 message: "cannot parse as DateTime<Utc>".to_owned(),
434 }),
435 other => Err(DocError::Conversion {
436 field: column.field_name.clone(),
437 value: format!("{other:?}"),
438 message: "cannot convert to DateTime<Utc>".to_owned(),
439 }),
440 }
441 }
442}
443
444impl FallbackConvert for NaiveDate {
445 fn to_doc_value_from_ref(&self) -> DocValue {
446 DocValue::Date(*self)
447 }
448 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
449 match value {
450 DocValue::Date(d) => Ok(*d),
451 DocValue::DateTime(dt) => Ok(dt.date_naive()),
452 DocValue::String(s) => {
453 NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|_| DocError::Conversion {
454 field: column.field_name.clone(),
455 value: s.clone(),
456 message: "cannot parse as NaiveDate (expected YYYY-MM-DD)".to_owned(),
457 })
458 }
459 other => Err(DocError::Conversion {
460 field: column.field_name.clone(),
461 value: format!("{other:?}"),
462 message: "cannot convert to NaiveDate".to_owned(),
463 }),
464 }
465 }
466}
467
468impl FallbackConvert for NaiveDateTime {
469 fn to_doc_value_from_ref(&self) -> DocValue {
470 DocValue::NaiveDateTime(*self)
471 }
472 fn from_doc_value(value: &DocValue, column: &TableColumn) -> Result<Self> {
473 match value {
474 DocValue::NaiveDateTime(ndt) => Ok(*ndt),
475 DocValue::DateTime(dt) => Ok(dt.naive_utc()),
476 DocValue::String(s) => {
477 NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S").map_err(|_| {
478 DocError::Conversion {
479 field: column.field_name.clone(),
480 value: s.clone(),
481 message: "cannot parse as NaiveDateTime".to_owned(),
482 }
483 })
484 }
485 other => Err(DocError::Conversion {
486 field: column.field_name.clone(),
487 value: format!("{other:?}"),
488 message: "cannot convert to NaiveDateTime".to_owned(),
489 }),
490 }
491 }
492}
493
494fn fallback_to_doc_value<V: 'static + std::fmt::Debug>(
495 value: &V,
496 _column: &TableColumn,
497) -> Result<DocValue> {
498 let type_id = TypeId::of::<V>();
499
500 if type_id == TypeId::of::<String>() {
501 let any_val = value as &dyn Any;
504 if let Some(s) = any_val.downcast_ref::<String>() {
505 return Ok(<String as FallbackConvert>::to_doc_value_from_ref(s));
506 }
507 }
508 if type_id == TypeId::of::<i64>() {
509 let any_val = value as &dyn Any;
510 if let Some(n) = any_val.downcast_ref::<i64>() {
511 return Ok(<i64 as FallbackConvert>::to_doc_value_from_ref(n));
512 }
513 }
514 if type_id == TypeId::of::<i32>() {
515 let any_val = value as &dyn Any;
516 if let Some(n) = any_val.downcast_ref::<i32>() {
517 return Ok(<i32 as FallbackConvert>::to_doc_value_from_ref(n));
518 }
519 }
520 if type_id == TypeId::of::<u32>() {
521 let any_val = value as &dyn Any;
522 if let Some(n) = any_val.downcast_ref::<u32>() {
523 return Ok(<u32 as FallbackConvert>::to_doc_value_from_ref(n));
524 }
525 }
526 if type_id == TypeId::of::<f64>() {
527 let any_val = value as &dyn Any;
528 if let Some(n) = any_val.downcast_ref::<f64>() {
529 return Ok(<f64 as FallbackConvert>::to_doc_value_from_ref(n));
530 }
531 }
532 if type_id == TypeId::of::<bool>() {
533 let any_val = value as &dyn Any;
534 if let Some(b) = any_val.downcast_ref::<bool>() {
535 return Ok(<bool as FallbackConvert>::to_doc_value_from_ref(b));
536 }
537 }
538 if type_id == TypeId::of::<DateTime<Utc>>() {
539 let any_val = value as &dyn Any;
540 if let Some(dt) = any_val.downcast_ref::<DateTime<Utc>>() {
541 return Ok(<DateTime<Utc> as FallbackConvert>::to_doc_value_from_ref(
542 dt,
543 ));
544 }
545 }
546 if type_id == TypeId::of::<NaiveDate>() {
547 let any_val = value as &dyn Any;
548 if let Some(d) = any_val.downcast_ref::<NaiveDate>() {
549 return Ok(<NaiveDate as FallbackConvert>::to_doc_value_from_ref(d));
550 }
551 }
552 if type_id == TypeId::of::<NaiveDateTime>() {
553 let any_val = value as &dyn Any;
554 if let Some(ndt) = any_val.downcast_ref::<NaiveDateTime>() {
555 return Ok(<NaiveDateTime as FallbackConvert>::to_doc_value_from_ref(
556 ndt,
557 ));
558 }
559 }
560
561 Ok(DocValue::String(format!("{value:?}")))
563}
564
565fn fallback_from_doc_value<V: 'static>(value: &DocValue, column: &TableColumn) -> Result<V> {
566 let type_id = TypeId::of::<V>();
567
568 let err = |msg: &str| -> Result<V> {
569 Err(DocError::Conversion {
570 field: column.field_name.clone(),
571 value: format!("{value:?}"),
572 message: msg.to_owned(),
573 })
574 };
575
576 if type_id == TypeId::of::<String>() {
577 let s: String = <String as FallbackConvert>::from_doc_value(value, column)?;
578 let any_box: Box<dyn Any> = Box::new(s);
580 match any_box.downcast::<V>() {
581 Ok(boxed) => Ok(*boxed),
582 Err(_) => err("type mismatch for String"),
583 }
584 } else if type_id == TypeId::of::<i64>() {
585 let n: i64 = <i64 as FallbackConvert>::from_doc_value(value, column)?;
586 let any_box: Box<dyn Any> = Box::new(n);
587 match any_box.downcast::<V>() {
588 Ok(boxed) => Ok(*boxed),
589 Err(_) => err("type mismatch for i64"),
590 }
591 } else if type_id == TypeId::of::<i32>() {
592 let n: i32 = <i32 as FallbackConvert>::from_doc_value(value, column)?;
593 let any_box: Box<dyn Any> = Box::new(n);
594 match any_box.downcast::<V>() {
595 Ok(boxed) => Ok(*boxed),
596 Err(_) => err("type mismatch for i32"),
597 }
598 } else if type_id == TypeId::of::<u32>() {
599 let n: u32 = <u32 as FallbackConvert>::from_doc_value(value, column)?;
600 let any_box: Box<dyn Any> = Box::new(n);
601 match any_box.downcast::<V>() {
602 Ok(boxed) => Ok(*boxed),
603 Err(_) => err("type mismatch for u32"),
604 }
605 } else if type_id == TypeId::of::<f64>() {
606 let n: f64 = <f64 as FallbackConvert>::from_doc_value(value, column)?;
607 let any_box: Box<dyn Any> = Box::new(n);
608 match any_box.downcast::<V>() {
609 Ok(boxed) => Ok(*boxed),
610 Err(_) => err("type mismatch for f64"),
611 }
612 } else if type_id == TypeId::of::<bool>() {
613 let b: bool = <bool as FallbackConvert>::from_doc_value(value, column)?;
614 let any_box: Box<dyn Any> = Box::new(b);
615 match any_box.downcast::<V>() {
616 Ok(boxed) => Ok(*boxed),
617 Err(_) => err("type mismatch for bool"),
618 }
619 } else if type_id == TypeId::of::<DateTime<Utc>>() {
620 let dt: DateTime<Utc> = <DateTime<Utc> as FallbackConvert>::from_doc_value(value, column)?;
621 let any_box: Box<dyn Any> = Box::new(dt);
622 match any_box.downcast::<V>() {
623 Ok(boxed) => Ok(*boxed),
624 Err(_) => err("type mismatch for DateTime<Utc>"),
625 }
626 } else if type_id == TypeId::of::<NaiveDate>() {
627 let d: NaiveDate = <NaiveDate as FallbackConvert>::from_doc_value(value, column)?;
628 let any_box: Box<dyn Any> = Box::new(d);
629 match any_box.downcast::<V>() {
630 Ok(boxed) => Ok(*boxed),
631 Err(_) => err("type mismatch for NaiveDate"),
632 }
633 } else if type_id == TypeId::of::<NaiveDateTime>() {
634 let ndt: NaiveDateTime = <NaiveDateTime as FallbackConvert>::from_doc_value(value, column)?;
635 let any_box: Box<dyn Any> = Box::new(ndt);
636 match any_box.downcast::<V>() {
637 Ok(boxed) => Ok(*boxed),
638 Err(_) => err("type mismatch for NaiveDateTime"),
639 }
640 } else {
641 err(&format!(
642 "no converter registered for type {}",
643 std::any::type_name::<V>()
644 ))
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651 use crate::types::ImageData;
652 use chrono::{TimeZone, Utc};
653
654 fn test_column() -> TableColumn {
655 TableColumn::new("Test", "test", 0)
656 }
657
658 #[test]
663 fn empty_registry() {
664 let r = ConverterRegistry::new();
665 assert!(!r.contains::<String>());
666 let dbg = format!("{r:?}");
667 assert!(dbg.contains("count: 0"));
668 }
669
670 #[test]
671 fn fallback_string_from_string_value() {
672 let r = ConverterRegistry::new();
673 let v = DocValue::String("hello".into());
674 let result: String = r.from_doc_value(&v, &test_column()).unwrap();
675 assert_eq!(result, "hello");
676 }
677
678 #[test]
679 fn fallback_string_from_int_value() {
680 let r = ConverterRegistry::new();
681 let v = DocValue::Int(42);
682 let result: String = r.from_doc_value(&v, &test_column()).unwrap();
683 assert_eq!(result, "42");
684 }
685
686 #[test]
687 fn fallback_string_from_float_value() {
688 let r = ConverterRegistry::new();
689 let v = DocValue::Float(std::f64::consts::PI);
690 let result: String = r.from_doc_value(&v, &test_column()).unwrap();
691 assert_eq!(result, "3.141592653589793");
692 }
693
694 #[test]
695 fn fallback_string_from_bool_value() {
696 let r = ConverterRegistry::new();
697 let v = DocValue::Bool(true);
698 let result: String = r.from_doc_value(&v, &test_column()).unwrap();
699 assert_eq!(result, "true");
700 }
701
702 #[test]
703 fn fallback_string_from_empty() {
704 let r = ConverterRegistry::new();
705 let v = DocValue::Empty;
706 let result: String = r.from_doc_value(&v, &test_column()).unwrap();
707 assert_eq!(result, "");
708 }
709
710 #[test]
711 fn fallback_string_from_image_fails() {
712 let r = ConverterRegistry::new();
713 let v = DocValue::Image(ImageData {
714 bytes: vec![],
715 extension: "png".into(),
716 width: None,
717 height: None,
718 alt_text: None,
719 });
720 let result: std::result::Result<String, _> = r.from_doc_value(&v, &test_column());
721 assert!(result.is_err());
722 }
723
724 #[test]
725 fn fallback_i64_from_int() {
726 let r = ConverterRegistry::new();
727 let v = DocValue::Int(100);
728 let result: i64 = r.from_doc_value(&v, &test_column()).unwrap();
729 assert_eq!(result, 100);
730 }
731
732 #[test]
733 fn fallback_i64_from_string_parse() {
734 let r = ConverterRegistry::new();
735 let v = DocValue::String("256".into());
736 let result: i64 = r.from_doc_value(&v, &test_column()).unwrap();
737 assert_eq!(result, 256);
738 }
739
740 #[test]
741 fn fallback_i64_from_invalid_string() {
742 let r = ConverterRegistry::new();
743 let v = DocValue::String("abc".into());
744 let result: std::result::Result<i64, _> = r.from_doc_value(&v, &test_column());
745 assert!(result.is_err());
746 }
747
748 #[test]
749 fn fallback_f64_from_float() {
750 let r = ConverterRegistry::new();
751 let v = DocValue::Float(2.5);
752 let result: f64 = r.from_doc_value(&v, &test_column()).unwrap();
753 assert!((result - 2.5).abs() < f64::EPSILON);
754 }
755
756 #[test]
757 fn fallback_f64_from_string_parse() {
758 let r = ConverterRegistry::new();
759 let v = DocValue::String("1.5".into());
760 let result: f64 = r.from_doc_value(&v, &test_column()).unwrap();
761 assert!((result - 1.5).abs() < f64::EPSILON);
762 }
763
764 #[test]
765 fn fallback_bool_from_bool() {
766 let r = ConverterRegistry::new();
767 let v = DocValue::Bool(false);
768 let result: bool = r.from_doc_value(&v, &test_column()).unwrap();
769 assert!(!result);
770 }
771
772 #[test]
773 fn fallback_to_doc_value_string() {
774 let r = ConverterRegistry::new();
775 let val = String::from("test");
776 let result = r.to_doc_value(&val, &test_column()).unwrap();
777 assert!(matches!(result, DocValue::String(s) if s == "test"));
778 }
779
780 #[test]
781 fn fallback_to_doc_value_i64() {
782 let r = ConverterRegistry::new();
783 let val: i64 = 42;
784 let result = r.to_doc_value(&val, &test_column()).unwrap();
785 assert!(matches!(result, DocValue::Int(42)));
786 }
787
788 #[test]
789 fn fallback_to_doc_value_i32() {
790 let r = ConverterRegistry::new();
791 let val: i32 = 7;
792 let result = r.to_doc_value(&val, &test_column()).unwrap();
793 assert!(matches!(result, DocValue::Int(7)));
794 }
795
796 #[test]
797 fn fallback_to_doc_value_u32() {
798 let r = ConverterRegistry::new();
799 let val: u32 = 99;
800 let result = r.to_doc_value(&val, &test_column()).unwrap();
801 assert!(matches!(result, DocValue::Int(99)));
802 }
803
804 #[test]
805 fn fallback_to_doc_value_f64() {
806 let r = ConverterRegistry::new();
807 let val: f64 = 1.5;
808 let result = r.to_doc_value(&val, &test_column()).unwrap();
809 assert!(matches!(result, DocValue::Float(f) if (f - 1.5).abs() < f64::EPSILON));
810 }
811
812 #[test]
813 fn fallback_to_doc_value_bool() {
814 let r = ConverterRegistry::new();
815 let val = true;
816 let result = r.to_doc_value(&val, &test_column()).unwrap();
817 assert!(matches!(result, DocValue::Bool(true)));
818 }
819
820 #[test]
821 fn fallback_to_doc_value_datetime() {
822 let r = ConverterRegistry::new();
823 let val = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
824 let result = r.to_doc_value(&val, &test_column()).unwrap();
825 assert!(matches!(result, DocValue::DateTime(_)));
826 }
827
828 #[test]
829 fn fallback_to_doc_value_naive_date() {
830 let r = ConverterRegistry::new();
831 let val = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
832 let result = r.to_doc_value(&val, &test_column()).unwrap();
833 assert!(matches!(result, DocValue::Date(_)));
834 }
835
836 #[test]
837 fn fallback_to_doc_value_naive_datetime() {
838 let r = ConverterRegistry::new();
839 let val = NaiveDate::from_ymd_opt(2024, 6, 1)
840 .unwrap()
841 .and_hms_opt(12, 0, 0)
842 .unwrap();
843 let result = r.to_doc_value(&val, &test_column()).unwrap();
844 assert!(matches!(result, DocValue::NaiveDateTime(_)));
845 }
846
847 #[test]
848 fn fallback_i32_roundtrip() {
849 let r = ConverterRegistry::new();
850 let v = DocValue::Int(42);
851 let result: i32 = r.from_doc_value(&v, &test_column()).unwrap();
852 assert_eq!(result, 42);
853 }
854
855 #[test]
856 fn fallback_u32_roundtrip() {
857 let r = ConverterRegistry::new();
858 let v = DocValue::Int(42);
859 let result: u32 = r.from_doc_value(&v, &test_column()).unwrap();
860 assert_eq!(result, 42);
861 }
862
863 #[test]
864 fn fallback_datetime_from_string() {
865 let r = ConverterRegistry::new();
866 let v = DocValue::DateTime(Utc.timestamp_opt(1_700_000_000, 0).unwrap());
867 let result: DateTime<Utc> = r.from_doc_value(&v, &test_column()).unwrap();
868 assert_eq!(result.timestamp(), 1_700_000_000);
869 }
870
871 #[test]
872 fn fallback_naive_date_from_date() {
873 let r = ConverterRegistry::new();
874 let d = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
875 let v = DocValue::Date(d);
876 let result: NaiveDate = r.from_doc_value(&v, &test_column()).unwrap();
877 assert_eq!(result, d);
878 }
879
880 #[test]
881 fn fallback_naive_datetime_from_ndt() {
882 let r = ConverterRegistry::new();
883 let ndt = NaiveDate::from_ymd_opt(2024, 6, 1)
884 .unwrap()
885 .and_hms_opt(12, 0, 0)
886 .unwrap();
887 let v = DocValue::NaiveDateTime(ndt);
888 let result: NaiveDateTime = r.from_doc_value(&v, &test_column()).unwrap();
889 assert_eq!(result, ndt);
890 }
891
892 #[test]
893 fn fallback_i64_from_float_error() {
894 let r = ConverterRegistry::new();
895 let v = DocValue::Float(std::f64::consts::PI);
896 let result: std::result::Result<i64, _> = r.from_doc_value(&v, &test_column());
897 assert!(result.is_err());
898 }
899
900 #[test]
901 fn fallback_i64_from_bool_error() {
902 let r = ConverterRegistry::new();
903 let v = DocValue::Bool(true);
904 let result: std::result::Result<i64, _> = r.from_doc_value(&v, &test_column());
905 assert!(result.is_err());
906 }
907
908 #[test]
909 fn fallback_i64_from_empty_error() {
910 let r = ConverterRegistry::new();
911 let v = DocValue::Empty;
912 let result: std::result::Result<i64, _> = r.from_doc_value(&v, &test_column());
913 assert!(result.is_err());
914 }
915
916 #[test]
917 fn fallback_i32_from_string_parse() {
918 let r = ConverterRegistry::new();
919 let v = DocValue::String("42".into());
920 let result: i32 = r.from_doc_value(&v, &test_column()).unwrap();
921 assert_eq!(result, 42);
922 }
923
924 #[test]
925 fn fallback_i32_from_invalid_string() {
926 let r = ConverterRegistry::new();
927 let v = DocValue::String("abc".into());
928 let result: std::result::Result<i32, _> = r.from_doc_value(&v, &test_column());
929 assert!(result.is_err());
930 }
931
932 #[test]
933 fn fallback_i32_from_float_error() {
934 let r = ConverterRegistry::new();
935 let v = DocValue::Float(std::f64::consts::PI);
936 let result: std::result::Result<i32, _> = r.from_doc_value(&v, &test_column());
937 assert!(result.is_err());
938 }
939
940 #[test]
941 fn fallback_u32_from_string_parse() {
942 let r = ConverterRegistry::new();
943 let v = DocValue::String("42".into());
944 let result: u32 = r.from_doc_value(&v, &test_column()).unwrap();
945 assert_eq!(result, 42);
946 }
947
948 #[test]
949 fn fallback_u32_from_invalid_string() {
950 let r = ConverterRegistry::new();
951 let v = DocValue::String("abc".into());
952 let result: std::result::Result<u32, _> = r.from_doc_value(&v, &test_column());
953 assert!(result.is_err());
954 }
955
956 #[test]
957 fn fallback_u32_from_float_error() {
958 let r = ConverterRegistry::new();
959 let v = DocValue::Float(std::f64::consts::PI);
960 let result: std::result::Result<u32, _> = r.from_doc_value(&v, &test_column());
961 assert!(result.is_err());
962 }
963
964 #[test]
965 fn fallback_f64_from_int() {
966 let r = ConverterRegistry::new();
967 let v = DocValue::Int(42);
968 let result: f64 = r.from_doc_value(&v, &test_column()).unwrap();
969 assert!((result - 42.0).abs() < f64::EPSILON);
970 }
971
972 #[test]
973 fn fallback_f64_from_invalid_string() {
974 let r = ConverterRegistry::new();
975 let v = DocValue::String("abc".into());
976 let result: std::result::Result<f64, _> = r.from_doc_value(&v, &test_column());
977 assert!(result.is_err());
978 }
979
980 #[test]
981 fn fallback_f64_from_bool_error() {
982 let r = ConverterRegistry::new();
983 let v = DocValue::Bool(true);
984 let result: std::result::Result<f64, _> = r.from_doc_value(&v, &test_column());
985 assert!(result.is_err());
986 }
987
988 #[test]
989 fn fallback_bool_from_string_true() {
990 let r = ConverterRegistry::new();
991 for s in &["true", "True", "TRUE", "1", "yes", "Yes"] {
992 let v = DocValue::String(s.to_string());
993 let result: bool = r.from_doc_value(&v, &test_column()).unwrap();
994 assert!(result, "failed for {s}");
995 }
996 }
997
998 #[test]
999 fn fallback_bool_from_string_false() {
1000 let r = ConverterRegistry::new();
1001 for s in &["false", "False", "FALSE", "0", "no", "No"] {
1002 let v = DocValue::String(s.to_string());
1003 let result: bool = r.from_doc_value(&v, &test_column()).unwrap();
1004 assert!(!result, "failed for {s}");
1005 }
1006 }
1007
1008 #[test]
1009 fn fallback_bool_from_invalid_string() {
1010 let r = ConverterRegistry::new();
1011 let v = DocValue::String("maybe".into());
1012 let result: std::result::Result<bool, _> = r.from_doc_value(&v, &test_column());
1013 assert!(result.is_err());
1014 }
1015
1016 #[test]
1017 fn fallback_bool_from_int() {
1018 let r = ConverterRegistry::new();
1019 let v = DocValue::Int(1);
1020 let result: bool = r.from_doc_value(&v, &test_column()).unwrap();
1021 assert!(result);
1022 let v2 = DocValue::Int(0);
1023 let result2: bool = r.from_doc_value(&v2, &test_column()).unwrap();
1024 assert!(!result2);
1025 }
1026
1027 #[test]
1028 fn fallback_bool_from_float_error() {
1029 let r = ConverterRegistry::new();
1030 let v = DocValue::Float(1.0);
1031 let result: std::result::Result<bool, _> = r.from_doc_value(&v, &test_column());
1032 assert!(result.is_err());
1033 }
1034
1035 #[test]
1036 fn fallback_datetime_from_datetime() {
1037 let r = ConverterRegistry::new();
1038 let dt = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
1039 let v = DocValue::DateTime(dt);
1040 let result: DateTime<Utc> = r.from_doc_value(&v, &test_column()).unwrap();
1041 assert_eq!(result.timestamp(), 1_700_000_000);
1042 }
1043
1044 #[test]
1045 fn fallback_datetime_from_string_parse() {
1046 let r = ConverterRegistry::new();
1047 let v = DocValue::String("2023-11-14T22:13:20Z".into());
1048 let result: DateTime<Utc> = r.from_doc_value(&v, &test_column()).unwrap();
1049 assert_eq!(result.timestamp(), 1_700_000_000);
1050 }
1051
1052 #[test]
1053 fn fallback_datetime_from_invalid_string() {
1054 let r = ConverterRegistry::new();
1055 let v = DocValue::String("not-a-date".into());
1056 let result: std::result::Result<DateTime<Utc>, _> = r.from_doc_value(&v, &test_column());
1057 assert!(result.is_err());
1058 }
1059
1060 #[test]
1061 fn fallback_datetime_from_int_error() {
1062 let r = ConverterRegistry::new();
1063 let v = DocValue::Int(100);
1064 let result: std::result::Result<DateTime<Utc>, _> = r.from_doc_value(&v, &test_column());
1065 assert!(result.is_err());
1066 }
1067
1068 #[test]
1069 fn fallback_naive_date_from_string_parse() {
1070 let r = ConverterRegistry::new();
1071 let v = DocValue::String("2024-01-15".into());
1072 let result: NaiveDate = r.from_doc_value(&v, &test_column()).unwrap();
1073 assert_eq!(result, NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1074 }
1075
1076 #[test]
1077 fn fallback_naive_date_from_datetime() {
1078 let r = ConverterRegistry::new();
1079 let dt = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
1080 let v = DocValue::DateTime(dt);
1081 let result: NaiveDate = r.from_doc_value(&v, &test_column()).unwrap();
1082 assert_eq!(result, dt.date_naive());
1083 }
1084
1085 #[test]
1086 fn fallback_naive_date_from_invalid_string() {
1087 let r = ConverterRegistry::new();
1088 let v = DocValue::String("not-a-date".into());
1089 let result: std::result::Result<NaiveDate, _> = r.from_doc_value(&v, &test_column());
1090 assert!(result.is_err());
1091 }
1092
1093 #[test]
1094 fn fallback_naive_date_from_int_error() {
1095 let r = ConverterRegistry::new();
1096 let v = DocValue::Int(100);
1097 let result: std::result::Result<NaiveDate, _> = r.from_doc_value(&v, &test_column());
1098 assert!(result.is_err());
1099 }
1100
1101 #[test]
1102 fn fallback_naive_datetime_from_string_parse() {
1103 let r = ConverterRegistry::new();
1104 let v = DocValue::String("2024-06-01 12:00:00".into());
1105 let result: NaiveDateTime = r.from_doc_value(&v, &test_column()).unwrap();
1106 let expected = NaiveDate::from_ymd_opt(2024, 6, 1)
1107 .unwrap()
1108 .and_hms_opt(12, 0, 0)
1109 .unwrap();
1110 assert_eq!(result, expected);
1111 }
1112
1113 #[test]
1114 fn fallback_naive_datetime_from_datetime() {
1115 let r = ConverterRegistry::new();
1116 let dt = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
1117 let v = DocValue::DateTime(dt);
1118 let result: NaiveDateTime = r.from_doc_value(&v, &test_column()).unwrap();
1119 assert_eq!(result, dt.naive_utc());
1120 }
1121
1122 #[test]
1123 fn fallback_naive_datetime_from_invalid_string() {
1124 let r = ConverterRegistry::new();
1125 let v = DocValue::String("not-a-datetime".into());
1126 let result: std::result::Result<NaiveDateTime, _> = r.from_doc_value(&v, &test_column());
1127 assert!(result.is_err());
1128 }
1129
1130 #[test]
1131 fn fallback_naive_datetime_from_int_error() {
1132 let r = ConverterRegistry::new();
1133 let v = DocValue::Int(100);
1134 let result: std::result::Result<NaiveDateTime, _> = r.from_doc_value(&v, &test_column());
1135 assert!(result.is_err());
1136 }
1137
1138 #[test]
1139 fn fallback_to_doc_value_debug_fallback() {
1140 let r = ConverterRegistry::new();
1141 let val: Vec<u8> = vec![1, 2, 3];
1144 let result = r.to_doc_value(&val, &test_column());
1145 assert!(result.is_ok());
1146 assert!(matches!(result.unwrap(), DocValue::String(s) if s.contains('1')));
1147 }
1148
1149 #[test]
1150 fn fallback_from_doc_value_unsupported_type() {
1151 let r = ConverterRegistry::new();
1152 let v = DocValue::String("test".into());
1153 let result: std::result::Result<Vec<u8>, _> = r.from_doc_value(&v, &test_column());
1155 assert!(result.is_err());
1156 }
1157
1158 #[test]
1159 fn registry_contains_after_register() {
1160 let r = ConverterRegistry::new();
1161 assert!(!r.contains::<String>());
1162 }
1165
1166 #[test]
1167 fn registry_debug_format() {
1168 let r = ConverterRegistry::new();
1169 let dbg = format!("{r:?}");
1170 assert!(dbg.contains("ConverterRegistry"));
1171 assert!(dbg.contains("count: 0"));
1172 }
1173
1174 #[test]
1175 fn fallback_string_from_richtext() {
1176 let r = ConverterRegistry::new();
1177 let v = DocValue::RichText(vec![]);
1178 let result: std::result::Result<String, _> = r.from_doc_value(&v, &test_column());
1179 assert!(result.is_err());
1180 }
1181
1182 #[test]
1183 fn fallback_string_from_datetime() {
1184 let r = ConverterRegistry::new();
1185 let dt = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
1186 let v = DocValue::DateTime(dt);
1187 let result: std::result::Result<String, _> = r.from_doc_value(&v, &test_column());
1188 assert!(result.is_err());
1189 }
1190
1191 #[derive(Debug, Clone, PartialEq, Eq)]
1197 enum Status {
1198 Active,
1199 Inactive,
1200 }
1201
1202 struct StatusConverter;
1204
1205 impl DocConverter<Status> for StatusConverter {
1206 fn support_type() -> TypeId {
1207 TypeId::of::<Status>()
1208 }
1209
1210 fn to_doc_value(&self, value: &Status, _column: &TableColumn) -> Result<DocValue> {
1211 match value {
1212 Status::Active => Ok(DocValue::String("ACTIVE".into())),
1213 Status::Inactive => Ok(DocValue::String("INACTIVE".into())),
1214 }
1215 }
1216
1217 fn from_doc_value(&self, value: &DocValue, column: &TableColumn) -> Result<Status> {
1218 match value {
1219 DocValue::String(s) => match s.as_str() {
1220 "ACTIVE" => Ok(Status::Active),
1221 "INACTIVE" => Ok(Status::Inactive),
1222 _ => Err(DocError::Conversion {
1223 field: column.field_name.clone(),
1224 value: s.clone(),
1225 message: "unknown status value".to_owned(),
1226 }),
1227 },
1228 _ => Err(DocError::Conversion {
1229 field: column.field_name.clone(),
1230 value: format!("{value:?}"),
1231 message: "expected string for Status".to_owned(),
1232 }),
1233 }
1234 }
1235 }
1236
1237 struct TimesTenConverter;
1239
1240 impl DocConverter<i32> for TimesTenConverter {
1241 fn support_type() -> TypeId {
1242 TypeId::of::<i32>()
1243 }
1244
1245 fn to_doc_value(&self, value: &i32, _column: &TableColumn) -> Result<DocValue> {
1246 Ok(DocValue::Int(i64::from(*value * 10)))
1247 }
1248
1249 fn from_doc_value(&self, value: &DocValue, column: &TableColumn) -> Result<i32> {
1250 match value {
1251 DocValue::Int(n) => Ok((*n / 10) as i32),
1252 _ => Err(DocError::Conversion {
1253 field: column.field_name.clone(),
1254 value: format!("{value:?}"),
1255 message: "expected int for i32".to_owned(),
1256 }),
1257 }
1258 }
1259 }
1260
1261 #[test]
1262 fn register_and_find_status_converter() {
1263 let mut r = ConverterRegistry::new();
1264 let is_new = r.register::<Status, _>(StatusConverter);
1265 assert!(is_new, "first registration should return true");
1266 assert!(r.contains::<Status>());
1267 assert!(r.find_converter::<Status>().is_some());
1268 }
1269
1270 #[test]
1271 fn register_returns_false_on_overwrite() {
1272 let mut r = ConverterRegistry::new();
1273 let first = r.register::<Status, _>(StatusConverter);
1274 assert!(first);
1275 let second = r.register::<Status, _>(StatusConverter);
1276 assert!(
1277 !second,
1278 "second registration should return false (overwrite)"
1279 );
1280 }
1281
1282 #[test]
1283 fn find_converter_unregistered_returns_none() {
1284 let r = ConverterRegistry::new();
1285 assert!(r.find_converter::<Status>().is_none());
1286 assert!(r.find_converter::<i64>().is_none());
1287 }
1288
1289 #[test]
1290 fn custom_converter_to_doc_value_roundtrip() {
1291 let mut r = ConverterRegistry::new();
1292 r.register::<Status, _>(StatusConverter);
1293
1294 let col = test_column();
1295
1296 let active_val = r.to_doc_value(&Status::Active, &col).unwrap();
1298 assert!(matches!(active_val, DocValue::String(ref s) if s == "ACTIVE"));
1299
1300 let inactive_val = r.to_doc_value(&Status::Inactive, &col).unwrap();
1301 assert!(matches!(inactive_val, DocValue::String(ref s) if s == "INACTIVE"));
1302
1303 let active_back: Status = r.from_doc_value(&active_val, &col).unwrap();
1305 assert_eq!(active_back, Status::Active);
1306
1307 let inactive_back: Status = r.from_doc_value(&inactive_val, &col).unwrap();
1308 assert_eq!(inactive_back, Status::Inactive);
1309 }
1310
1311 #[test]
1312 fn custom_converter_from_doc_value_error() {
1313 let mut r = ConverterRegistry::new();
1314 r.register::<Status, _>(StatusConverter);
1315
1316 let col = test_column();
1317 let bad_val = DocValue::String("UNKNOWN".into());
1318 let result: std::result::Result<Status, _> = r.from_doc_value(&bad_val, &col);
1319 assert!(result.is_err());
1320 }
1321
1322 #[test]
1323 fn custom_converter_overrides_fallback() {
1324 let mut r = ConverterRegistry::new();
1326 r.register::<i32, _>(TimesTenConverter);
1327
1328 let col = test_column();
1329
1330 let val = r.to_doc_value(&7i32, &col).unwrap();
1332 assert!(matches!(val, DocValue::Int(70)));
1333
1334 let back: i32 = r.from_doc_value(&val, &col).unwrap();
1336 assert_eq!(back, 7);
1337 }
1338
1339 #[test]
1340 fn find_converter_by_name_registered() {
1341 let mut r = ConverterRegistry::new();
1342 r.register_named::<Status, _>("StatusConverter", StatusConverter);
1343
1344 assert!(r.find_converter::<Status>().is_some());
1345 assert!(r.find_converter_by_name("StatusConverter").is_some());
1346 assert!(r.find_converter_by_name("NonExistent").is_none());
1347 }
1348
1349 #[test]
1350 fn find_converter_by_name_unregistered() {
1351 let r = ConverterRegistry::new();
1352 assert!(r.find_converter_by_name("Anything").is_none());
1353 }
1354
1355 #[test]
1356 fn register_named_returns_correct_bool() {
1357 let mut r = ConverterRegistry::new();
1358 let first = r.register_named::<Status, _>("StatusConverter", StatusConverter);
1359 assert!(first);
1360 let second = r.register_named::<Status, _>("StatusConverter", StatusConverter);
1361 assert!(!second);
1362 }
1363
1364 #[test]
1365 fn multiple_types_in_same_registry() {
1366 let mut r = ConverterRegistry::new();
1367 r.register::<Status, _>(StatusConverter);
1368 r.register_named::<i32, _>("TimesTen", TimesTenConverter);
1369
1370 assert!(r.contains::<Status>());
1371 assert!(r.contains::<i32>());
1372 assert!(!r.contains::<String>());
1373 assert!(r.find_converter_by_name("TimesTen").is_some());
1374 assert!(r.find_converter_by_name("StatusConverter").is_none()); }
1376
1377 #[test]
1378 fn registry_debug_shows_count() {
1379 let mut r = ConverterRegistry::new();
1380 r.register::<Status, _>(StatusConverter);
1381 r.register::<i32, _>(TimesTenConverter);
1382
1383 let dbg = format!("{r:?}");
1384 assert!(dbg.contains("count: 2"));
1385 }
1386
1387 #[test]
1388 fn find_converter_erased_to_doc_value() {
1389 let mut r = ConverterRegistry::new();
1391 r.register::<Status, _>(StatusConverter);
1392
1393 let col = test_column();
1394 let converter = r.find_converter::<Status>().unwrap();
1395 let val = converter
1396 .to_doc_value_erased(&Status::Active as &dyn Any, &col)
1397 .unwrap();
1398 assert!(matches!(val, DocValue::String(ref s) if s == "ACTIVE"));
1399 }
1400
1401 #[test]
1402 fn find_converter_erased_from_doc_value() {
1403 let mut r = ConverterRegistry::new();
1404 r.register::<Status, _>(StatusConverter);
1405
1406 let col = test_column();
1407 let converter = r.find_converter::<Status>().unwrap();
1408 let val = DocValue::String("INACTIVE".into());
1409 let boxed = converter.from_doc_value_erased(&val, &col).unwrap();
1410 let status = boxed.downcast::<Status>().unwrap();
1411 assert_eq!(*status, Status::Inactive);
1412 }
1413
1414 #[test]
1415 fn find_converter_by_name_roundtrip() {
1416 let mut r = ConverterRegistry::new();
1417 r.register_named::<Status, _>("StatusConverter", StatusConverter);
1418
1419 let col = test_column();
1420 let converter = r.find_converter_by_name("StatusConverter").unwrap();
1421
1422 let val = converter
1423 .to_doc_value_erased(&Status::Active as &dyn Any, &col)
1424 .unwrap();
1425 assert!(matches!(val, DocValue::String(ref s) if s == "ACTIVE"));
1426
1427 let boxed = converter.from_doc_value_erased(&val, &col).unwrap();
1428 let status = boxed.downcast::<Status>().unwrap();
1429 assert_eq!(*status, Status::Active);
1430 }
1431}