1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum Value {
11 Null,
13
14 Bool(bool),
16
17 TinyInt(i8),
19
20 SmallInt(i16),
22
23 Int(i32),
25
26 BigInt(i64),
28
29 Float(f32),
31
32 Double(f64),
34
35 Decimal(String),
37
38 Text(String),
40
41 Bytes(Vec<u8>),
43
44 Date(i32),
46
47 Time(i64),
49
50 Timestamp(i64),
52
53 TimestampTz(i64),
55
56 Uuid([u8; 16]),
58
59 Json(serde_json::Value),
61
62 Array(Vec<Value>),
64
65 Default,
67}
68
69impl Value {
70 pub const fn is_null(&self) -> bool {
72 matches!(self, Value::Null)
73 }
74
75 pub const fn type_name(&self) -> &'static str {
77 match self {
78 Value::Null => "NULL",
79 Value::Bool(_) => "BOOLEAN",
80 Value::TinyInt(_) => "TINYINT",
81 Value::SmallInt(_) => "SMALLINT",
82 Value::Int(_) => "INTEGER",
83 Value::BigInt(_) => "BIGINT",
84 Value::Float(_) => "REAL",
85 Value::Double(_) => "DOUBLE",
86 Value::Decimal(_) => "DECIMAL",
87 Value::Text(_) => "TEXT",
88 Value::Bytes(_) => "BLOB",
89 Value::Date(_) => "DATE",
90 Value::Time(_) => "TIME",
91 Value::Timestamp(_) => "TIMESTAMP",
92 Value::TimestampTz(_) => "TIMESTAMPTZ",
93 Value::Uuid(_) => "UUID",
94 Value::Json(_) => "JSON",
95 Value::Array(_) => "ARRAY",
96 Value::Default => "DEFAULT",
97 }
98 }
99
100 pub fn as_bool(&self) -> Option<bool> {
102 match self {
103 Value::Bool(v) => Some(*v),
104 Value::TinyInt(v) => Some(*v != 0),
105 Value::SmallInt(v) => Some(*v != 0),
106 Value::Int(v) => Some(*v != 0),
107 Value::BigInt(v) => Some(*v != 0),
108 _ => None,
109 }
110 }
111
112 pub fn as_i64(&self) -> Option<i64> {
114 match self {
115 Value::TinyInt(v) => Some(i64::from(*v)),
116 Value::SmallInt(v) => Some(i64::from(*v)),
117 Value::Int(v) => Some(i64::from(*v)),
118 Value::BigInt(v) => Some(*v),
119 Value::Bool(v) => Some(if *v { 1 } else { 0 }),
120 _ => None,
121 }
122 }
123
124 pub fn as_f64(&self) -> Option<f64> {
126 match self {
127 Value::Float(v) => Some(f64::from(*v)),
128 Value::Double(v) => Some(*v),
129 Value::TinyInt(v) => Some(f64::from(*v)),
130 Value::SmallInt(v) => Some(f64::from(*v)),
131 Value::Int(v) => Some(f64::from(*v)),
132 Value::BigInt(v) => Some(*v as f64),
133 Value::Decimal(s) => s.parse().ok(),
134 _ => None,
135 }
136 }
137
138 pub fn as_str(&self) -> Option<&str> {
140 match self {
141 Value::Text(s) => Some(s),
142 Value::Decimal(s) => Some(s),
143 _ => None,
144 }
145 }
146
147 pub fn as_bytes(&self) -> Option<&[u8]> {
149 match self {
150 Value::Bytes(b) => Some(b),
151 Value::Text(s) => Some(s.as_bytes()),
152 _ => None,
153 }
154 }
155
156 #[must_use]
176 pub fn from_u64_clamped(v: u64) -> Self {
177 if let Ok(signed) = i64::try_from(v) {
178 Value::BigInt(signed)
179 } else {
180 tracing::warn!(
181 value = v,
182 clamped_to = i64::MAX,
183 "u64 value exceeds i64::MAX; clamping to i64::MAX"
184 );
185 Value::BigInt(i64::MAX)
186 }
187 }
188
189 #[allow(clippy::cast_possible_truncation, clippy::result_large_err)]
206 pub fn to_f32_lossy(&self) -> crate::Result<f32> {
207 match self {
208 Value::Float(v) => Ok(*v),
209 Value::Double(v) => {
210 let converted = *v as f32;
211 if converted.is_infinite() && !v.is_infinite() {
212 return Err(Error::Type(TypeError {
213 expected: "f32-representable value",
214 actual: format!("f64 value {} overflows f32", v),
215 column: None,
216 rust_type: Some("f32"),
217 }));
218 }
219 Ok(converted)
220 }
221 Value::TinyInt(v) => Ok(f32::from(*v)),
222 Value::SmallInt(v) => Ok(f32::from(*v)),
223 Value::Int(v) => Ok(*v as f32),
224 Value::BigInt(v) => Ok(*v as f32),
225 Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
226 other => Err(Error::Type(TypeError {
227 expected: "numeric value",
228 actual: other.type_name().to_string(),
229 column: None,
230 rust_type: Some("f32"),
231 })),
232 }
233 }
234
235 #[allow(clippy::cast_precision_loss, clippy::result_large_err)]
252 pub fn to_f64_lossy(&self) -> crate::Result<f64> {
253 match self {
254 Value::Float(v) => Ok(f64::from(*v)),
255 Value::Double(v) => Ok(*v),
256 Value::TinyInt(v) => Ok(f64::from(*v)),
257 Value::SmallInt(v) => Ok(f64::from(*v)),
258 Value::Int(v) => Ok(f64::from(*v)),
259 Value::BigInt(v) => Ok(*v as f64), Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
261 other => Err(Error::Type(TypeError {
262 expected: "numeric value",
263 actual: other.type_name().to_string(),
264 column: None,
265 rust_type: Some("f64"),
266 })),
267 }
268 }
269}
270
271impl From<bool> for Value {
273 fn from(v: bool) -> Self {
274 Value::Bool(v)
275 }
276}
277
278impl From<i8> for Value {
279 fn from(v: i8) -> Self {
280 Value::TinyInt(v)
281 }
282}
283
284impl From<i16> for Value {
285 fn from(v: i16) -> Self {
286 Value::SmallInt(v)
287 }
288}
289
290impl From<i32> for Value {
291 fn from(v: i32) -> Self {
292 Value::Int(v)
293 }
294}
295
296impl From<i64> for Value {
297 fn from(v: i64) -> Self {
298 Value::BigInt(v)
299 }
300}
301
302impl From<f32> for Value {
303 fn from(v: f32) -> Self {
304 Value::Float(v)
305 }
306}
307
308impl From<f64> for Value {
309 fn from(v: f64) -> Self {
310 Value::Double(v)
311 }
312}
313
314impl From<String> for Value {
315 fn from(v: String) -> Self {
316 Value::Text(v)
317 }
318}
319
320impl From<&str> for Value {
321 fn from(v: &str) -> Self {
322 Value::Text(v.to_string())
323 }
324}
325
326impl From<Vec<u8>> for Value {
327 fn from(v: Vec<u8>) -> Self {
328 Value::Bytes(v)
329 }
330}
331
332impl<T: Into<Value>> From<Option<T>> for Value {
333 fn from(v: Option<T>) -> Self {
334 match v {
335 Some(v) => v.into(),
336 None => Value::Null,
337 }
338 }
339}
340
341impl From<&[u8]> for Value {
342 fn from(v: &[u8]) -> Self {
343 Value::Bytes(v.to_vec())
344 }
345}
346
347impl From<u8> for Value {
348 fn from(v: u8) -> Self {
349 Value::SmallInt(i16::from(v))
350 }
351}
352
353impl From<u16> for Value {
354 fn from(v: u16) -> Self {
355 Value::Int(i32::from(v))
356 }
357}
358
359impl From<u32> for Value {
360 fn from(v: u32) -> Self {
361 Value::BigInt(i64::from(v))
362 }
363}
364
365impl TryFrom<u64> for Value {
370 type Error = Error;
371
372 fn try_from(v: u64) -> Result<Self, Self::Error> {
373 i64::try_from(v).map(Value::BigInt).map_err(|_| {
374 Error::Type(TypeError {
375 expected: "u64 <= i64::MAX",
376 actual: format!("u64 value {} exceeds i64::MAX ({})", v, i64::MAX),
377 column: None,
378 rust_type: Some("u64"),
379 })
380 })
381 }
382}
383
384impl From<serde_json::Value> for Value {
385 fn from(v: serde_json::Value) -> Self {
386 Value::Json(v)
387 }
388}
389
390impl From<[u8; 16]> for Value {
391 fn from(v: [u8; 16]) -> Self {
392 Value::Uuid(v)
393 }
394}
395
396impl From<Vec<String>> for Value {
398 fn from(v: Vec<String>) -> Self {
399 Value::Array(v.into_iter().map(Value::Text).collect())
400 }
401}
402
403impl From<Vec<i32>> for Value {
405 fn from(v: Vec<i32>) -> Self {
406 Value::Array(v.into_iter().map(Value::Int).collect())
407 }
408}
409
410impl From<Vec<i64>> for Value {
412 fn from(v: Vec<i64>) -> Self {
413 Value::Array(v.into_iter().map(Value::BigInt).collect())
414 }
415}
416
417impl From<Vec<f64>> for Value {
419 fn from(v: Vec<f64>) -> Self {
420 Value::Array(v.into_iter().map(Value::Double).collect())
421 }
422}
423
424impl From<Vec<bool>> for Value {
426 fn from(v: Vec<bool>) -> Self {
427 Value::Array(v.into_iter().map(Value::Bool).collect())
428 }
429}
430
431use crate::error::{Error, TypeError};
434
435impl TryFrom<Value> for bool {
436 type Error = Error;
437
438 fn try_from(value: Value) -> Result<Self, Self::Error> {
439 match value {
440 Value::Bool(v) => Ok(v),
441 Value::TinyInt(v) => Ok(v != 0),
442 Value::SmallInt(v) => Ok(v != 0),
443 Value::Int(v) => Ok(v != 0),
444 Value::BigInt(v) => Ok(v != 0),
445 other => Err(Error::Type(TypeError {
446 expected: "bool",
447 actual: other.type_name().to_string(),
448 column: None,
449 rust_type: None,
450 })),
451 }
452 }
453}
454
455impl TryFrom<Value> for i8 {
456 type Error = Error;
457
458 fn try_from(value: Value) -> Result<Self, Self::Error> {
459 match value {
460 Value::TinyInt(v) => Ok(v),
461 Value::Bool(v) => Ok(if v { 1 } else { 0 }),
462 other => Err(Error::Type(TypeError {
463 expected: "i8",
464 actual: other.type_name().to_string(),
465 column: None,
466 rust_type: None,
467 })),
468 }
469 }
470}
471
472impl TryFrom<Value> for i16 {
473 type Error = Error;
474
475 fn try_from(value: Value) -> Result<Self, Self::Error> {
476 match value {
477 Value::TinyInt(v) => Ok(i16::from(v)),
478 Value::SmallInt(v) => Ok(v),
479 Value::Bool(v) => Ok(if v { 1 } else { 0 }),
480 other => Err(Error::Type(TypeError {
481 expected: "i16",
482 actual: other.type_name().to_string(),
483 column: None,
484 rust_type: None,
485 })),
486 }
487 }
488}
489
490impl TryFrom<Value> for i32 {
491 type Error = Error;
492
493 fn try_from(value: Value) -> Result<Self, Self::Error> {
494 match value {
495 Value::TinyInt(v) => Ok(i32::from(v)),
496 Value::SmallInt(v) => Ok(i32::from(v)),
497 Value::Int(v) => Ok(v),
498 Value::Bool(v) => Ok(if v { 1 } else { 0 }),
499 other => Err(Error::Type(TypeError {
500 expected: "i32",
501 actual: other.type_name().to_string(),
502 column: None,
503 rust_type: None,
504 })),
505 }
506 }
507}
508
509impl TryFrom<Value> for i64 {
510 type Error = Error;
511
512 fn try_from(value: Value) -> Result<Self, Self::Error> {
513 match value {
514 Value::TinyInt(v) => Ok(i64::from(v)),
515 Value::SmallInt(v) => Ok(i64::from(v)),
516 Value::Int(v) => Ok(i64::from(v)),
517 Value::BigInt(v) => Ok(v),
518 Value::Bool(v) => Ok(if v { 1 } else { 0 }),
519 other => Err(Error::Type(TypeError {
520 expected: "i64",
521 actual: other.type_name().to_string(),
522 column: None,
523 rust_type: None,
524 })),
525 }
526 }
527}
528
529const F32_MAX_EXACT_INT: i64 = 1 << 24;
531
532impl TryFrom<Value> for f32 {
533 type Error = Error;
534
535 fn try_from(value: Value) -> Result<Self, Self::Error> {
539 match value {
540 Value::Float(v) => Ok(v),
541 #[allow(clippy::cast_possible_truncation)]
542 Value::Double(v) => {
543 let converted = v as f32;
544 if (f64::from(converted) - v).abs() > f64::EPSILON * v.abs().max(1.0) {
546 return Err(Error::Type(TypeError {
547 expected: "f32-representable f64",
548 actual: format!("f64 value {} loses precision as f32", v),
549 column: None,
550 rust_type: Some("f32"),
551 }));
552 }
553 Ok(converted)
554 }
555 Value::TinyInt(v) => Ok(f32::from(v)),
556 Value::SmallInt(v) => Ok(f32::from(v)),
557 #[allow(clippy::cast_possible_truncation)]
558 Value::Int(v) => {
559 if i64::from(v).abs() > F32_MAX_EXACT_INT {
560 return Err(Error::Type(TypeError {
561 expected: "f32-representable i32",
562 actual: format!(
563 "i32 value {} exceeds f32 exact integer range (±{})",
564 v, F32_MAX_EXACT_INT
565 ),
566 column: None,
567 rust_type: Some("f32"),
568 }));
569 }
570 Ok(v as f32)
571 }
572 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
573 Value::BigInt(v) => {
574 if v.unsigned_abs() > F32_MAX_EXACT_INT as u64 {
576 return Err(Error::Type(TypeError {
577 expected: "f32-representable i64",
578 actual: format!(
579 "i64 value {} exceeds f32 exact integer range (±{})",
580 v, F32_MAX_EXACT_INT
581 ),
582 column: None,
583 rust_type: Some("f32"),
584 }));
585 }
586 Ok(v as f32)
587 }
588 Value::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
590 other => Err(Error::Type(TypeError {
591 expected: "f32",
592 actual: other.type_name().to_string(),
593 column: None,
594 rust_type: None,
595 })),
596 }
597 }
598}
599
600const F64_MAX_EXACT_INT: i64 = 1 << 53;
602
603impl TryFrom<Value> for f64 {
604 type Error = Error;
605
606 fn try_from(value: Value) -> Result<Self, Self::Error> {
610 match value {
611 Value::Float(v) => Ok(f64::from(v)),
612 Value::Double(v) => Ok(v),
613 Value::TinyInt(v) => Ok(f64::from(v)),
614 Value::SmallInt(v) => Ok(f64::from(v)),
615 Value::Int(v) => Ok(f64::from(v)),
616 #[allow(clippy::cast_precision_loss)]
617 #[allow(clippy::cast_sign_loss)]
618 Value::BigInt(v) => {
619 if v.unsigned_abs() > F64_MAX_EXACT_INT as u64 {
621 return Err(Error::Type(TypeError {
622 expected: "f64-representable i64",
623 actual: format!(
624 "i64 value {} exceeds f64 exact integer range (±{})",
625 v, F64_MAX_EXACT_INT
626 ),
627 column: None,
628 rust_type: Some("f64"),
629 }));
630 }
631 Ok(v as f64)
632 }
633 Value::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
635 other => Err(Error::Type(TypeError {
636 expected: "f64",
637 actual: other.type_name().to_string(),
638 column: None,
639 rust_type: None,
640 })),
641 }
642 }
643}
644
645impl TryFrom<Value> for String {
646 type Error = Error;
647
648 fn try_from(value: Value) -> Result<Self, Self::Error> {
649 match value {
650 Value::Text(v) => Ok(v),
651 Value::Decimal(v) => Ok(v),
652 other => Err(Error::Type(TypeError {
653 expected: "String",
654 actual: other.type_name().to_string(),
655 column: None,
656 rust_type: None,
657 })),
658 }
659 }
660}
661
662impl TryFrom<Value> for Vec<u8> {
663 type Error = Error;
664
665 fn try_from(value: Value) -> Result<Self, Self::Error> {
666 match value {
667 Value::Bytes(v) => Ok(v),
668 Value::Text(v) => Ok(v.into_bytes()),
669 other => Err(Error::Type(TypeError {
670 expected: "Vec<u8>",
671 actual: other.type_name().to_string(),
672 column: None,
673 rust_type: None,
674 })),
675 }
676 }
677}
678
679impl TryFrom<Value> for serde_json::Value {
680 type Error = Error;
681
682 fn try_from(value: Value) -> Result<Self, Self::Error> {
683 match value {
684 Value::Json(v) => Ok(v),
685 Value::Text(s) => serde_json::from_str(&s).map_err(|e| {
686 Error::Type(TypeError {
687 expected: "valid JSON",
688 actual: format!("invalid JSON: {}", e),
689 column: None,
690 rust_type: None,
691 })
692 }),
693 other => Err(Error::Type(TypeError {
694 expected: "JSON",
695 actual: other.type_name().to_string(),
696 column: None,
697 rust_type: None,
698 })),
699 }
700 }
701}
702
703impl TryFrom<Value> for [u8; 16] {
704 type Error = Error;
705
706 fn try_from(value: Value) -> Result<Self, Self::Error> {
707 match value {
708 Value::Uuid(v) => Ok(v),
709 Value::Bytes(v) if v.len() == 16 => {
710 let mut arr = [0u8; 16];
711 arr.copy_from_slice(&v);
712 Ok(arr)
713 }
714 other => Err(Error::Type(TypeError {
715 expected: "UUID",
716 actual: other.type_name().to_string(),
717 column: None,
718 rust_type: None,
719 })),
720 }
721 }
722}
723
724impl<T> TryFrom<Value> for Option<T>
726where
727 T: TryFrom<Value, Error = Error>,
728{
729 type Error = Error;
730
731 fn try_from(value: Value) -> Result<Self, Self::Error> {
732 match value {
733 Value::Null => Ok(None),
734 v => T::try_from(v).map(Some),
735 }
736 }
737}
738
739impl TryFrom<Value> for Vec<String> {
741 type Error = Error;
742
743 fn try_from(value: Value) -> Result<Self, Self::Error> {
744 match value {
745 Value::Array(arr) => arr.into_iter().map(String::try_from).collect(),
746 other => Err(Error::Type(TypeError {
747 expected: "ARRAY",
748 actual: other.type_name().to_string(),
749 column: None,
750 rust_type: None,
751 })),
752 }
753 }
754}
755
756impl TryFrom<Value> for Vec<i32> {
758 type Error = Error;
759
760 fn try_from(value: Value) -> Result<Self, Self::Error> {
761 match value {
762 Value::Array(arr) => arr.into_iter().map(i32::try_from).collect(),
763 other => Err(Error::Type(TypeError {
764 expected: "ARRAY",
765 actual: other.type_name().to_string(),
766 column: None,
767 rust_type: None,
768 })),
769 }
770 }
771}
772
773impl TryFrom<Value> for Vec<i64> {
775 type Error = Error;
776
777 fn try_from(value: Value) -> Result<Self, Self::Error> {
778 match value {
779 Value::Array(arr) => arr.into_iter().map(i64::try_from).collect(),
780 other => Err(Error::Type(TypeError {
781 expected: "ARRAY",
782 actual: other.type_name().to_string(),
783 column: None,
784 rust_type: None,
785 })),
786 }
787 }
788}
789
790impl TryFrom<Value> for Vec<bool> {
792 type Error = Error;
793
794 fn try_from(value: Value) -> Result<Self, Self::Error> {
795 match value {
796 Value::Array(arr) => arr.into_iter().map(bool::try_from).collect(),
797 other => Err(Error::Type(TypeError {
798 expected: "ARRAY",
799 actual: other.type_name().to_string(),
800 column: None,
801 rust_type: None,
802 })),
803 }
804 }
805}
806
807impl TryFrom<Value> for Vec<f64> {
808 type Error = Error;
809
810 fn try_from(value: Value) -> Result<Self, Self::Error> {
811 match value {
812 Value::Array(arr) => arr.into_iter().map(f64::try_from).collect(),
813 other => Err(Error::Type(TypeError {
814 expected: "ARRAY",
815 actual: other.type_name().to_string(),
816 column: None,
817 rust_type: None,
818 })),
819 }
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826
827 #[test]
828 fn test_from_bool() {
829 let v: Value = true.into();
830 assert_eq!(v, Value::Bool(true));
831 }
832
833 #[test]
834 fn test_from_integers() {
835 assert_eq!(Value::from(42i8), Value::TinyInt(42));
836 assert_eq!(Value::from(42i16), Value::SmallInt(42));
837 assert_eq!(Value::from(42i32), Value::Int(42));
838 assert_eq!(Value::from(42i64), Value::BigInt(42));
839 }
840
841 #[test]
842 fn test_from_unsigned_integers() {
843 assert_eq!(Value::from(42u8), Value::SmallInt(42));
844 assert_eq!(Value::from(42u16), Value::Int(42));
845 assert_eq!(Value::from(42u32), Value::BigInt(42));
846 }
848
849 #[test]
850 fn test_from_floats() {
851 let pi_f32 = std::f32::consts::PI;
852 let pi_f64 = std::f64::consts::PI;
853 assert_eq!(Value::from(pi_f32), Value::Float(pi_f32));
854 assert_eq!(Value::from(pi_f64), Value::Double(pi_f64));
855 }
856
857 #[test]
858 fn test_from_strings() {
859 assert_eq!(Value::from("hello"), Value::Text("hello".to_string()));
860 assert_eq!(
861 Value::from("hello".to_string()),
862 Value::Text("hello".to_string())
863 );
864 }
865
866 #[test]
867 fn test_from_bytes() {
868 let bytes = vec![1u8, 2, 3];
869 assert_eq!(Value::from(bytes.clone()), Value::Bytes(bytes.clone()));
870 assert_eq!(Value::from(bytes.as_slice()), Value::Bytes(bytes));
871 }
872
873 #[test]
874 fn test_from_option() {
875 let some: Value = Some(42i32).into();
876 assert_eq!(some, Value::Int(42));
877
878 let none: Value = Option::<i32>::None.into();
879 assert_eq!(none, Value::Null);
880 }
881
882 #[test]
883 fn test_try_from_bool() {
884 assert!(bool::try_from(Value::Bool(true)).unwrap());
885 assert!(bool::try_from(Value::Int(1)).unwrap());
886 assert!(!bool::try_from(Value::Int(0)).unwrap());
887 assert!(bool::try_from(Value::Text("true".to_string())).is_err());
888 }
889
890 #[test]
891 fn test_try_from_i64() {
892 assert_eq!(i64::try_from(Value::BigInt(42)).unwrap(), 42);
893 assert_eq!(i64::try_from(Value::Int(42)).unwrap(), 42);
894 assert_eq!(i64::try_from(Value::SmallInt(42)).unwrap(), 42);
895 assert_eq!(i64::try_from(Value::TinyInt(42)).unwrap(), 42);
896 assert!(i64::try_from(Value::Text("42".to_string())).is_err());
897 }
898
899 #[test]
900 fn test_try_from_f64() {
901 let pi = std::f64::consts::PI;
902 let pi_f32 = std::f32::consts::PI;
903 let double = f64::try_from(Value::Double(pi)).unwrap();
904 assert!((double - pi).abs() < 1e-12);
905
906 let from_float = f64::try_from(Value::Float(pi_f32)).unwrap();
907 assert!((from_float - f64::from(pi_f32)).abs() < 1e-6);
908
909 let from_int = f64::try_from(Value::Int(42)).unwrap();
910 assert!((from_int - 42.0).abs() < 1e-12);
911 assert!(f64::try_from(Value::Text("3.14".to_string())).is_err());
912 }
913
914 #[test]
915 fn test_try_from_string() {
916 assert_eq!(
917 String::try_from(Value::Text("hello".to_string())).unwrap(),
918 "hello"
919 );
920 assert!(String::try_from(Value::Int(42)).is_err());
921 }
922
923 #[test]
924 fn test_try_from_bytes() {
925 let bytes = vec![1u8, 2, 3];
926 assert_eq!(
927 Vec::<u8>::try_from(Value::Bytes(bytes.clone())).unwrap(),
928 bytes
929 );
930 assert_eq!(
931 Vec::<u8>::try_from(Value::Text("abc".to_string())).unwrap(),
932 b"abc".to_vec()
933 );
934 }
935
936 #[test]
937 fn test_try_from_option() {
938 let result: Option<i32> = Option::try_from(Value::Int(42)).unwrap();
939 assert_eq!(result, Some(42));
940
941 let result: Option<i32> = Option::try_from(Value::Null).unwrap();
942 assert_eq!(result, None);
943 }
944
945 #[test]
946 fn test_round_trip_bool() {
947 let original = true;
948 let value: Value = original.into();
949 let recovered: bool = value.try_into().unwrap();
950 assert_eq!(original, recovered);
951 }
952
953 #[test]
954 fn test_round_trip_i64() {
955 let original: i64 = i64::MAX;
956 let value: Value = original.into();
957 let recovered: i64 = value.try_into().unwrap();
958 assert_eq!(original, recovered);
959 }
960
961 #[test]
962 fn test_round_trip_f64() {
963 let original: f64 = std::f64::consts::PI;
964 let value: Value = original.into();
965 let recovered: f64 = value.try_into().unwrap();
966 assert!((original - recovered).abs() < f64::EPSILON);
967 }
968
969 #[test]
970 fn test_round_trip_string() {
971 let original = "hello world".to_string();
972 let value: Value = original.clone().into();
973 let recovered: String = value.try_into().unwrap();
974 assert_eq!(original, recovered);
975 }
976
977 #[test]
978 fn test_round_trip_bytes() {
979 let original = vec![0u8, 127, 255];
980 let value: Value = original.clone().into();
981 let recovered: Vec<u8> = value.try_into().unwrap();
982 assert_eq!(original, recovered);
983 }
984
985 #[test]
986 fn test_is_null() {
987 assert!(Value::Null.is_null());
988 assert!(!Value::Int(0).is_null());
989 assert!(!Value::Bool(false).is_null());
990 }
991
992 #[test]
993 fn test_as_i64() {
994 assert_eq!(Value::BigInt(42).as_i64(), Some(42));
995 assert_eq!(Value::Int(42).as_i64(), Some(42));
996 assert_eq!(Value::Null.as_i64(), None);
997 assert_eq!(Value::Text("42".to_string()).as_i64(), None);
998 }
999
1000 #[test]
1001 fn test_as_str() {
1002 assert_eq!(Value::Text("hello".to_string()).as_str(), Some("hello"));
1003 assert_eq!(
1004 Value::Decimal("123.45".to_string()).as_str(),
1005 Some("123.45")
1006 );
1007 assert_eq!(Value::Int(42).as_str(), None);
1008 }
1009
1010 #[test]
1011 fn test_type_name() {
1012 assert_eq!(Value::Null.type_name(), "NULL");
1013 assert_eq!(Value::Bool(true).type_name(), "BOOLEAN");
1014 assert_eq!(Value::Int(42).type_name(), "INTEGER");
1015 assert_eq!(Value::Text(String::new()).type_name(), "TEXT");
1016 }
1017
1018 #[test]
1019 fn test_edge_cases() {
1020 let value: Value = "".into();
1022 let recovered: String = value.try_into().unwrap();
1023 assert_eq!(recovered, "");
1024
1025 let value: Value = Vec::<u8>::new().into();
1027 let recovered: Vec<u8> = value.try_into().unwrap();
1028 assert!(recovered.is_empty());
1029
1030 let value: Value = i64::MAX.into();
1032 let recovered: i64 = value.try_into().unwrap();
1033 assert_eq!(recovered, i64::MAX);
1034
1035 let value: Value = i64::MIN.into();
1036 let recovered: i64 = value.try_into().unwrap();
1037 assert_eq!(recovered, i64::MIN);
1038 }
1039
1040 #[test]
1041 fn test_array_string_roundtrip() {
1042 let v: Value = vec!["a".to_string(), "b".to_string()].into();
1043 assert_eq!(
1044 v,
1045 Value::Array(vec![
1046 Value::Text("a".to_string()),
1047 Value::Text("b".to_string())
1048 ])
1049 );
1050 let recovered: Vec<String> = v.try_into().unwrap();
1051 assert_eq!(recovered, vec!["a", "b"]);
1052 }
1053
1054 #[test]
1055 fn test_array_i32_roundtrip() {
1056 let v: Value = vec![1i32, 2, 3].into();
1057 assert_eq!(
1058 v,
1059 Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
1060 );
1061 let recovered: Vec<i32> = v.try_into().unwrap();
1062 assert_eq!(recovered, vec![1, 2, 3]);
1063 }
1064
1065 #[test]
1066 fn test_array_empty() {
1067 let v: Value = Vec::<String>::new().into();
1068 assert_eq!(v, Value::Array(vec![]));
1069 let recovered: Vec<String> = v.try_into().unwrap();
1070 assert!(recovered.is_empty());
1071 }
1072
1073 #[test]
1074 fn test_array_type_error() {
1075 let v = Value::Text("not an array".to_string());
1076 let result: Result<Vec<String>, _> = v.try_into();
1077 assert!(result.is_err());
1078 }
1079
1080 #[test]
1081 fn test_try_from_u64_success() {
1082 let v: Value = Value::try_from(42u64).unwrap();
1084 assert_eq!(v, Value::BigInt(42));
1085
1086 let v: Value = Value::try_from(i64::MAX as u64).unwrap();
1088 assert_eq!(v, Value::BigInt(i64::MAX));
1089
1090 let v: Value = Value::try_from(0u64).unwrap();
1092 assert_eq!(v, Value::BigInt(0));
1093 }
1094
1095 #[test]
1096 fn test_try_from_u64_overflow_error() {
1097 let result = Value::try_from(u64::MAX);
1099 assert!(result.is_err());
1100 let err = result.unwrap_err();
1101 assert!(matches!(err, Error::Type(_)));
1102
1103 let result = Value::try_from((i64::MAX as u64) + 1);
1105 assert!(result.is_err());
1106 }
1107
1108 #[test]
1109 fn test_from_u64_clamped_normal() {
1110 assert_eq!(Value::from_u64_clamped(0), Value::BigInt(0));
1112 assert_eq!(Value::from_u64_clamped(42), Value::BigInt(42));
1113 assert_eq!(
1114 Value::from_u64_clamped(i64::MAX as u64),
1115 Value::BigInt(i64::MAX)
1116 );
1117 }
1118
1119 #[test]
1120 fn test_from_u64_clamped_overflow() {
1121 assert_eq!(Value::from_u64_clamped(u64::MAX), Value::BigInt(i64::MAX));
1123 assert_eq!(
1124 Value::from_u64_clamped((i64::MAX as u64) + 1),
1125 Value::BigInt(i64::MAX)
1126 );
1127 }
1128
1129 const F32_MAX_EXACT: i64 = 1 << 24; const F64_MAX_EXACT: i64 = 1 << 53; #[test]
1135 fn test_f32_from_double_precision_ok() {
1136 let v: f32 = Value::Double(1.5).try_into().unwrap();
1138 assert!((v - 1.5).abs() < f32::EPSILON);
1139
1140 let v: f32 = Value::Double(0.0).try_into().unwrap();
1141 assert!((v - 0.0).abs() < f32::EPSILON);
1142 }
1143
1144 #[test]
1145 fn test_f32_from_double_precision_loss() {
1146 let high_precision = 1e20_f64;
1149 let result = f32::try_from(Value::Double(high_precision));
1150 assert!(result.is_err());
1151
1152 let precise_value = 16_777_217.0_f64; let result2 = f32::try_from(Value::Double(precise_value));
1156 assert!(result2.is_err());
1157 }
1158
1159 #[test]
1160 #[allow(clippy::cast_possible_truncation)]
1161 fn test_f32_from_int_boundary() {
1162 let boundary = F32_MAX_EXACT as i32;
1164 let v: f32 = Value::Int(boundary).try_into().unwrap();
1165 assert!((v - F32_MAX_EXACT as f32).abs() < 1.0);
1166
1167 let over_boundary = (F32_MAX_EXACT + 1) as i32;
1169 let result = f32::try_from(Value::Int(over_boundary));
1170 assert!(result.is_err());
1171
1172 let v: f32 = Value::Int(-boundary).try_into().unwrap();
1174 assert!((v - -(F32_MAX_EXACT as f32)).abs() < 1.0);
1175 }
1176
1177 #[test]
1178 fn test_f32_from_bigint_boundary() {
1179 let v: f32 = Value::BigInt(F32_MAX_EXACT).try_into().unwrap();
1181 assert!((v - F32_MAX_EXACT as f32).abs() < 1.0);
1182
1183 let result = f32::try_from(Value::BigInt(F32_MAX_EXACT + 1));
1185 assert!(result.is_err());
1186 }
1187
1188 #[test]
1189 fn test_f64_from_bigint_boundary() {
1190 let v: f64 = Value::BigInt(F64_MAX_EXACT).try_into().unwrap();
1192 assert!((v - F64_MAX_EXACT as f64).abs() < 1.0);
1193
1194 let result = f64::try_from(Value::BigInt(F64_MAX_EXACT + 1));
1196 assert!(result.is_err());
1197 }
1198
1199 #[test]
1200 fn test_f32_lossy_accepts_large_values() {
1201 assert!(Value::BigInt(i64::MAX).to_f32_lossy().is_ok());
1203 assert!(
1204 Value::Double(1.000_000_119_209_289_6)
1205 .to_f32_lossy()
1206 .is_ok()
1207 );
1208 assert!(Value::Int(i32::MAX).to_f32_lossy().is_ok());
1209 }
1210
1211 #[test]
1212 fn test_f32_lossy_rejects_overflow() {
1213 let result = Value::Double(f64::MAX).to_f32_lossy();
1215 assert!(result.is_err());
1216 }
1217
1218 #[test]
1219 fn test_f64_lossy_accepts_large_integers() {
1220 assert!(Value::BigInt(i64::MAX).to_f64_lossy().is_ok());
1222 assert!(Value::BigInt(i64::MIN).to_f64_lossy().is_ok());
1223 }
1224
1225 #[test]
1226 fn test_f64_lossy_rejects_non_numeric() {
1227 let result = Value::Text("not a number".to_string()).to_f64_lossy();
1228 assert!(result.is_err());
1229 }
1230
1231 #[test]
1234 fn test_u64_error_message_includes_value() {
1235 let big_val = u64::MAX;
1236 let result = Value::try_from(big_val);
1237 assert!(result.is_err());
1238 let err = result.unwrap_err();
1239 let msg = err.to_string();
1240 assert!(
1242 msg.contains("18446744073709551615") || msg.contains(&big_val.to_string()),
1243 "Error should include the u64 value, got: {}",
1244 msg
1245 );
1246 }
1247
1248 #[test]
1249 fn test_f32_precision_error_is_descriptive() {
1250 let result = f32::try_from(Value::BigInt(i64::MAX));
1251 assert!(result.is_err());
1252 let err = result.unwrap_err();
1253 let msg = err.to_string();
1254 assert!(
1256 msg.contains("f32") && (msg.contains("exact") || msg.contains("precision")),
1257 "Error should describe f32 precision issue, got: {}",
1258 msg
1259 );
1260 }
1261
1262 #[test]
1263 fn test_f64_precision_error_is_descriptive() {
1264 let result = f64::try_from(Value::BigInt(i64::MAX));
1265 assert!(result.is_err());
1266 let err = result.unwrap_err();
1267 let msg = err.to_string();
1268 assert!(
1269 msg.contains("f64") && (msg.contains("exact") || msg.contains("precision")),
1270 "Error should describe f64 precision issue, got: {}",
1271 msg
1272 );
1273 }
1274
1275 #[test]
1278 fn test_f64_negative_boundary() {
1279 let neg_boundary = -(1i64 << 53);
1281 let v: f64 = Value::BigInt(neg_boundary).try_into().unwrap();
1282 assert!((v - neg_boundary as f64).abs() < 1.0);
1283
1284 let result = f64::try_from(Value::BigInt(neg_boundary - 1));
1286 assert!(result.is_err());
1287 }
1288
1289 #[test]
1290 fn test_f32_negative_boundary() {
1291 let neg_boundary = -(F32_MAX_EXACT);
1292 let v: f32 = Value::BigInt(neg_boundary).try_into().unwrap();
1293 assert!((v - neg_boundary as f32).abs() < 1.0);
1294
1295 let result = f32::try_from(Value::BigInt(neg_boundary - 1));
1297 assert!(result.is_err());
1298 }
1299
1300 #[test]
1303 fn test_conversion_errors_are_type_errors() {
1304 use crate::Error;
1305
1306 let err = Value::try_from(u64::MAX).unwrap_err();
1308 assert!(
1309 matches!(err, Error::Type(_)),
1310 "u64 overflow should be TypeError"
1311 );
1312
1313 let err = f32::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1315 assert!(
1316 matches!(err, Error::Type(_)),
1317 "f32 precision loss should be TypeError"
1318 );
1319
1320 let err = f64::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1322 assert!(
1323 matches!(err, Error::Type(_)),
1324 "f64 precision loss should be TypeError"
1325 );
1326 }
1327
1328 #[test]
1329 fn test_conversion_errors_include_expected_type() {
1330 let err = f32::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1332 assert!(
1333 err.to_string().to_lowercase().contains("f32"),
1334 "Error should mention f32, got: {}",
1335 err
1336 );
1337
1338 let err = f64::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1340 assert!(
1341 err.to_string().to_lowercase().contains("f64"),
1342 "Error should mention f64, got: {}",
1343 err
1344 );
1345 }
1346
1347 #[test]
1350 fn test_u64_roundtrip_within_range() {
1351 let original = 42u64;
1353 let value: Value = original.try_into().unwrap();
1354 let recovered: i64 = value.try_into().unwrap();
1355 assert_eq!(recovered, original as i64);
1356
1357 let original = i64::MAX as u64;
1359 let value: Value = original.try_into().unwrap();
1360 let recovered: i64 = value.try_into().unwrap();
1361 assert_eq!(recovered, i64::MAX);
1362 }
1363
1364 #[test]
1365 fn test_f32_roundtrip_preserves_value() {
1366 let original = std::f32::consts::PI;
1367 let value: Value = original.into();
1368 let recovered: f32 = value.try_into().unwrap();
1369 assert!((original - recovered).abs() < f32::EPSILON);
1370 }
1371
1372 #[test]
1373 fn test_f64_i64_roundtrip() {
1374 let original = 12345i64;
1376 let value = Value::BigInt(original);
1377 let as_f64: f64 = value.try_into().unwrap();
1378 assert!((as_f64 - original as f64).abs() < 1e-10);
1379 }
1380
1381 #[test]
1384 fn test_u64_boundary_edge_cases() {
1385 let v: Value = 0u64.try_into().unwrap();
1387 assert_eq!(v, Value::BigInt(0));
1388
1389 let v: Value = ((i64::MAX - 1) as u64).try_into().unwrap();
1391 assert_eq!(v, Value::BigInt(i64::MAX - 1));
1392
1393 let v: Value = (i64::MAX as u64).try_into().unwrap();
1395 assert_eq!(v, Value::BigInt(i64::MAX));
1396 }
1397
1398 #[test]
1399 fn test_i64_min_max_to_f64() {
1400 let result = f64::try_from(Value::BigInt(i64::MAX));
1402 assert!(result.is_err());
1403
1404 let result = f64::try_from(Value::BigInt(i64::MIN));
1406 assert!(result.is_err());
1407 }
1408
1409 #[test]
1410 fn test_f32_from_float_is_lossless() {
1411 let original = std::f32::consts::E;
1413 let v: f32 = Value::Float(original).try_into().unwrap();
1414 assert!((v - original).abs() < f32::EPSILON);
1415 }
1416}