1use std::any::Any;
2use std::fmt;
3use std::sync::Arc;
4
5use serde::ser::{SerializeSeq, Serializer};
6
7use crate::error::{Error, Result};
8
9#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub enum Value {
18 Null,
20 Bool(bool),
22 I8(i8),
24 I16(i16),
26 I32(i32),
28 I64(i64),
30 U8(u8),
32 U16(u16),
34 U32(u32),
36 U64(u64),
38 F32(f32),
40 F64(f64),
42 Text(String),
44 Bytes(Vec<u8>),
46 Array(Vec<Value>),
48 #[cfg(feature = "chrono")]
50 Date(chrono::NaiveDate),
51 #[cfg(feature = "chrono")]
53 Time(chrono::NaiveTime),
54 #[cfg(feature = "chrono")]
58 DateTime(chrono::NaiveDateTime),
59 #[cfg(feature = "chrono")]
68 TimestampTz(chrono::DateTime<chrono::Utc>),
69 #[cfg(feature = "uuid")]
71 Uuid(uuid::Uuid),
72 #[cfg(feature = "decimal")]
76 Decimal(rust_decimal::Decimal),
77 #[cfg(feature = "json")]
79 Json(serde_json::Value),
80 Custom(Arc<dyn CustomValue>),
83}
84
85pub trait CustomValue: fmt::Debug + Send + Sync + 'static {
94 fn type_name(&self) -> &'static str;
96
97 fn as_any(&self) -> &dyn Any;
99
100 fn to_plain(&self) -> Value {
104 Value::Null
105 }
106}
107
108impl Value {
109 pub fn array<T: ToValue, I: IntoIterator<Item = T>>(items: I) -> Value {
115 Value::Array(items.into_iter().map(ToValue::to_value).collect())
116 }
117
118 pub fn custom<C: CustomValue>(value: C) -> Value {
120 Value::Custom(Arc::new(value))
121 }
122
123 pub fn is_null(&self) -> bool {
125 matches!(self, Value::Null)
126 }
127
128 pub fn type_name(&self) -> &'static str {
130 match self {
131 Value::Null => "NULL",
132 Value::Bool(_) => "bool",
133 Value::I8(_) => "i8",
134 Value::I16(_) => "i16",
135 Value::I32(_) => "i32",
136 Value::I64(_) => "i64",
137 Value::U8(_) => "u8",
138 Value::U16(_) => "u16",
139 Value::U32(_) => "u32",
140 Value::U64(_) => "u64",
141 Value::F32(_) => "f32",
142 Value::F64(_) => "f64",
143 Value::Text(_) => "text",
144 Value::Bytes(_) => "bytes",
145 Value::Array(_) => "array",
146 #[cfg(feature = "chrono")]
147 Value::Date(_) => "date",
148 #[cfg(feature = "chrono")]
149 Value::Time(_) => "time",
150 #[cfg(feature = "chrono")]
151 Value::DateTime(_) => "datetime",
152 #[cfg(feature = "chrono")]
153 Value::TimestampTz(_) => "timestamptz",
154 #[cfg(feature = "uuid")]
155 Value::Uuid(_) => "uuid",
156 #[cfg(feature = "decimal")]
157 Value::Decimal(_) => "decimal",
158 #[cfg(feature = "json")]
159 Value::Json(_) => "json",
160 Value::Custom(c) => c.type_name(),
161 }
162 }
163}
164
165impl PartialEq for Value {
166 fn eq(&self, other: &Self) -> bool {
167 use Value::*;
168 match (self, other) {
169 (Null, Null) => true,
170 (Bool(a), Bool(b)) => a == b,
171 (I8(a), I8(b)) => a == b,
172 (I16(a), I16(b)) => a == b,
173 (I32(a), I32(b)) => a == b,
174 (I64(a), I64(b)) => a == b,
175 (U8(a), U8(b)) => a == b,
176 (U16(a), U16(b)) => a == b,
177 (U32(a), U32(b)) => a == b,
178 (U64(a), U64(b)) => a == b,
179 (F32(a), F32(b)) => a == b,
180 (F64(a), F64(b)) => a == b,
181 (Text(a), Text(b)) => a == b,
182 (Bytes(a), Bytes(b)) => a == b,
183 (Array(a), Array(b)) => a == b,
184 #[cfg(feature = "chrono")]
185 (Date(a), Date(b)) => a == b,
186 #[cfg(feature = "chrono")]
187 (Time(a), Time(b)) => a == b,
188 #[cfg(feature = "chrono")]
189 (DateTime(a), DateTime(b)) => a == b,
190 #[cfg(feature = "chrono")]
191 (TimestampTz(a), TimestampTz(b)) => a == b,
192 #[cfg(feature = "uuid")]
193 (Uuid(a), Uuid(b)) => a == b,
194 #[cfg(feature = "decimal")]
198 (Decimal(a), Decimal(b)) => a == b,
199 #[cfg(feature = "json")]
200 (Json(a), Json(b)) => a == b,
201 (Custom(a), Custom(b)) => Arc::ptr_eq(a, b),
204 _ => false,
205 }
206 }
207}
208
209impl serde::Serialize for Value {
215 fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
216 match self {
217 Value::Null => s.serialize_none(),
218 Value::Bool(v) => s.serialize_bool(*v),
219 Value::I8(v) => s.serialize_i8(*v),
220 Value::I16(v) => s.serialize_i16(*v),
221 Value::I32(v) => s.serialize_i32(*v),
222 Value::I64(v) => s.serialize_i64(*v),
223 Value::U8(v) => s.serialize_u8(*v),
224 Value::U16(v) => s.serialize_u16(*v),
225 Value::U32(v) => s.serialize_u32(*v),
226 Value::U64(v) => s.serialize_u64(*v),
227 Value::F32(v) => s.serialize_f32(*v),
228 Value::F64(v) => s.serialize_f64(*v),
229 Value::Text(v) => s.serialize_str(v),
230 Value::Bytes(v) => s.serialize_bytes(v),
231 Value::Array(items) => {
232 let mut seq = s.serialize_seq(Some(items.len()))?;
233 for item in items {
234 seq.serialize_element(item)?;
235 }
236 seq.end()
237 }
238 #[cfg(feature = "chrono")]
244 Value::Date(v) => s.collect_str(&v.format("%Y-%m-%d")),
245 #[cfg(feature = "chrono")]
246 Value::Time(v) => s.collect_str(&v.format("%H:%M:%S%.f")),
247 #[cfg(feature = "chrono")]
248 Value::DateTime(v) => s.collect_str(&v.format("%Y-%m-%dT%H:%M:%S%.f")),
249 #[cfg(feature = "chrono")]
250 Value::TimestampTz(v) => {
251 s.collect_str(&v.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true))
252 }
253 #[cfg(feature = "uuid")]
255 Value::Uuid(v) => s.collect_str(v),
256 #[cfg(feature = "decimal")]
259 Value::Decimal(v) => s.collect_str(v),
260 #[cfg(feature = "json")]
263 Value::Json(v) => v.serialize(s),
264 Value::Custom(c) => match c.to_plain() {
265 Value::Custom(_) => s.serialize_none(),
266 plain => plain.serialize(s),
267 },
268 }
269 }
270}
271
272pub trait ToValue {
274 fn to_value(self) -> Value;
276}
277
278impl ToValue for Value {
279 fn to_value(self) -> Value {
280 self
281 }
282}
283
284impl<T: ToValue> ToValue for Option<T> {
286 fn to_value(self) -> Value {
287 match self {
288 Some(v) => v.to_value(),
289 None => Value::Null,
290 }
291 }
292}
293
294macro_rules! to_value_direct {
295 ($($t:ty => $variant:ident),* $(,)?) => { $(
296 impl ToValue for $t {
297 fn to_value(self) -> Value {
298 Value::$variant(self)
299 }
300 }
301 )* };
302}
303
304to_value_direct! {
305 bool => Bool,
306 i8 => I8, i16 => I16, i32 => I32, i64 => I64,
307 u8 => U8, u16 => U16, u32 => U32, u64 => U64,
308 f32 => F32, f64 => F64,
309 String => Text,
310 Vec<u8> => Bytes,
311}
312
313impl ToValue for &str {
314 fn to_value(self) -> Value {
315 Value::Text(self.to_owned())
316 }
317}
318
319impl ToValue for &String {
320 fn to_value(self) -> Value {
321 Value::Text(self.clone())
322 }
323}
324
325impl ToValue for std::borrow::Cow<'_, str> {
326 fn to_value(self) -> Value {
327 Value::Text(self.into_owned())
328 }
329}
330
331impl ToValue for &[u8] {
332 fn to_value(self) -> Value {
333 Value::Bytes(self.to_vec())
334 }
335}
336
337impl ToValue for isize {
340 fn to_value(self) -> Value {
341 Value::I64(self as i64)
342 }
343}
344
345impl ToValue for usize {
346 fn to_value(self) -> Value {
347 Value::U64(self as u64)
348 }
349}
350
351impl ToValue for () {
353 fn to_value(self) -> Value {
354 Value::Null
355 }
356}
357
358impl<T: CustomValue> ToValue for Arc<T> {
359 fn to_value(self) -> Value {
360 Value::Custom(self)
361 }
362}
363
364#[cfg(feature = "chrono")]
365mod chrono_impls {
366 use super::{FromValue, ToValue, Value};
367 use crate::error::{Error, Result};
368 use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
369
370 impl ToValue for NaiveDate {
371 fn to_value(self) -> Value {
372 Value::Date(self)
373 }
374 }
375
376 impl ToValue for NaiveTime {
377 fn to_value(self) -> Value {
378 Value::Time(self)
379 }
380 }
381
382 impl ToValue for NaiveDateTime {
383 fn to_value(self) -> Value {
384 Value::DateTime(self)
385 }
386 }
387
388 impl<Tz: TimeZone> ToValue for DateTime<Tz> {
393 fn to_value(self) -> Value {
394 Value::TimestampTz(self.with_timezone(&Utc))
395 }
396 }
397
398 impl FromValue for NaiveDate {
406 fn from_value(v: Value) -> Result<Self> {
407 let found = v.type_name();
408 match v {
409 Value::Date(d) => Ok(d),
410 Value::Text(s) => s
411 .parse()
412 .map_err(|_| Error::type_mismatch("NaiveDate", found)),
413 _ => Err(Error::type_mismatch("NaiveDate", found)),
414 }
415 }
416 }
417
418 impl FromValue for NaiveTime {
419 fn from_value(v: Value) -> Result<Self> {
420 let found = v.type_name();
421 match v {
422 Value::Time(t) => Ok(t),
423 Value::Text(s) => s
424 .parse()
425 .map_err(|_| Error::type_mismatch("NaiveTime", found)),
426 _ => Err(Error::type_mismatch("NaiveTime", found)),
427 }
428 }
429 }
430
431 impl FromValue for NaiveDateTime {
432 fn from_value(v: Value) -> Result<Self> {
433 let found = v.type_name();
434 match v {
435 Value::DateTime(dt) => Ok(dt),
436 Value::Text(s) => s
437 .parse()
438 .or_else(|_| NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S%.f"))
439 .map_err(|_| Error::type_mismatch("NaiveDateTime", found)),
440 _ => Err(Error::type_mismatch("NaiveDateTime", found)),
441 }
442 }
443 }
444
445 impl FromValue for DateTime<Utc> {
446 fn from_value(v: Value) -> Result<Self> {
447 let found = v.type_name();
448 match v {
449 Value::TimestampTz(dt) => Ok(dt),
450 Value::Text(s) => DateTime::parse_from_rfc3339(&s)
451 .map(|dt| dt.with_timezone(&Utc))
452 .map_err(|_| Error::type_mismatch("DateTime<Utc>", found)),
453 _ => Err(Error::type_mismatch("DateTime<Utc>", found)),
454 }
455 }
456 }
457}
458
459#[cfg(feature = "uuid")]
460mod uuid_impls {
461 use super::{FromValue, ToValue, Value};
462 use crate::error::{Error, Result};
463 use uuid::Uuid;
464
465 impl ToValue for Uuid {
466 fn to_value(self) -> Value {
467 Value::Uuid(self)
468 }
469 }
470
471 impl FromValue for Uuid {
472 fn from_value(v: Value) -> Result<Self> {
473 let found = v.type_name();
474 match v {
475 Value::Uuid(u) => Ok(u),
476 Value::Text(s) => {
480 Uuid::parse_str(&s).map_err(|_| Error::type_mismatch("Uuid", found))
481 }
482 Value::Bytes(b) => {
483 Uuid::from_slice(&b).map_err(|_| Error::type_mismatch("Uuid", found))
484 }
485 _ => Err(Error::type_mismatch("Uuid", found)),
486 }
487 }
488 }
489}
490
491#[cfg(feature = "decimal")]
492mod decimal_impls {
493 use super::{FromValue, ToValue, Value};
494 use crate::error::{Error, Result};
495 use rust_decimal::Decimal;
496
497 impl ToValue for Decimal {
498 fn to_value(self) -> Value {
499 Value::Decimal(self)
500 }
501 }
502
503 impl FromValue for Decimal {
504 fn from_value(v: Value) -> Result<Self> {
505 let found = v.type_name();
506 match v {
512 Value::Decimal(d) => Ok(d),
513 Value::Text(s) => s
514 .parse()
515 .map_err(|_| Error::type_mismatch("Decimal", found)),
516 Value::I8(x) => Ok(Decimal::from(x)),
517 Value::I16(x) => Ok(Decimal::from(x)),
518 Value::I32(x) => Ok(Decimal::from(x)),
519 Value::I64(x) => Ok(Decimal::from(x)),
520 Value::U8(x) => Ok(Decimal::from(x)),
521 Value::U16(x) => Ok(Decimal::from(x)),
522 Value::U32(x) => Ok(Decimal::from(x)),
523 Value::U64(x) => Ok(Decimal::from(x)),
524 _ => Err(Error::type_mismatch("Decimal", found)),
525 }
526 }
527 }
528}
529
530#[cfg(feature = "json")]
531mod json_impls {
532 use super::{FromValue, ToValue, Value};
533 use crate::error::{Error, Result};
534
535 impl ToValue for serde_json::Value {
536 fn to_value(self) -> Value {
537 Value::Json(self)
538 }
539 }
540
541 impl FromValue for serde_json::Value {
542 fn from_value(v: Value) -> Result<Self> {
543 let found = v.type_name();
544 match v {
545 Value::Json(j) => Ok(j),
546 Value::Text(s) => serde_json::from_str(&s)
549 .map_err(|_| Error::type_mismatch("serde_json::Value", found)),
550 _ => Err(Error::type_mismatch("serde_json::Value", found)),
551 }
552 }
553 }
554}
555
556pub trait FromValue: Sized {
558 fn from_value(v: Value) -> Result<Self>;
560}
561
562impl FromValue for Value {
563 fn from_value(v: Value) -> Result<Self> {
564 Ok(v)
565 }
566}
567
568impl<T: FromValue> FromValue for Option<T> {
570 fn from_value(v: Value) -> Result<Self> {
571 match v {
572 Value::Null => Ok(None),
573 other => T::from_value(other).map(Some),
574 }
575 }
576}
577
578macro_rules! from_value_int {
579 ($($t:ty),* $(,)?) => { $(
580 impl FromValue for $t {
581 fn from_value(v: Value) -> Result<Self> {
582 let found = v.type_name();
583 let converted = match v {
586 Value::I8(x) => <$t>::try_from(x).ok(),
587 Value::I16(x) => <$t>::try_from(x).ok(),
588 Value::I32(x) => <$t>::try_from(x).ok(),
589 Value::I64(x) => <$t>::try_from(x).ok(),
590 Value::U8(x) => <$t>::try_from(x).ok(),
591 Value::U16(x) => <$t>::try_from(x).ok(),
592 Value::U32(x) => <$t>::try_from(x).ok(),
593 Value::U64(x) => <$t>::try_from(x).ok(),
594 _ => return Err(Error::type_mismatch(stringify!($t), found)),
595 };
596 converted.ok_or(Error::type_mismatch(stringify!($t), found))
597 }
598 }
599 )* };
600}
601
602from_value_int!(i8, i16, i32, i64, u8, u16, u32, u64);
603
604macro_rules! from_value_float {
605 ($($t:ty),* $(,)?) => { $(
606 impl FromValue for $t {
607 #[allow(clippy::cast_lossless, clippy::cast_precision_loss)]
608 fn from_value(v: Value) -> Result<Self> {
609 let found = v.type_name();
610 match v {
611 Value::F32(x) => Ok(x as $t),
612 Value::F64(x) => Ok(x as $t),
613 Value::I8(x) => Ok(x as $t),
614 Value::I16(x) => Ok(x as $t),
615 Value::I32(x) => Ok(x as $t),
616 Value::I64(x) => Ok(x as $t),
617 Value::U8(x) => Ok(x as $t),
618 Value::U16(x) => Ok(x as $t),
619 Value::U32(x) => Ok(x as $t),
620 Value::U64(x) => Ok(x as $t),
621 _ => Err(Error::type_mismatch(stringify!($t), found)),
622 }
623 }
624 }
625 )* };
626}
627
628from_value_float!(f32, f64);
629
630impl FromValue for bool {
631 fn from_value(v: Value) -> Result<Self> {
632 match v {
633 Value::Bool(b) => Ok(b),
634 other => Err(Error::type_mismatch("bool", other.type_name())),
635 }
636 }
637}
638
639impl FromValue for String {
640 fn from_value(v: Value) -> Result<Self> {
641 match v {
642 Value::Text(s) => Ok(s),
643 other => Err(Error::type_mismatch("String", other.type_name())),
644 }
645 }
646}
647
648impl FromValue for Vec<u8> {
649 fn from_value(v: Value) -> Result<Self> {
650 match v {
651 Value::Bytes(b) => Ok(b),
652 Value::Text(s) => Ok(s.into_bytes()),
655 other => Err(Error::type_mismatch("Vec<u8>", other.type_name())),
656 }
657 }
658}
659
660pub fn from_value_array<T: FromValue>(v: Value) -> Result<Vec<T>> {
666 match v {
667 Value::Array(items) => items.into_iter().map(T::from_value).collect(),
668 other => Err(Error::type_mismatch("array", other.type_name())),
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[derive(Debug)]
677 struct Point(i32, i32);
678
679 impl CustomValue for Point {
680 fn type_name(&self) -> &'static str {
681 "point"
682 }
683
684 fn as_any(&self) -> &dyn Any {
685 self
686 }
687
688 fn to_plain(&self) -> Value {
689 Value::Text(format!("({},{})", self.0, self.1))
690 }
691 }
692
693 #[derive(Debug)]
694 struct Opaque;
695
696 impl CustomValue for Opaque {
697 fn type_name(&self) -> &'static str {
698 "opaque"
699 }
700
701 fn as_any(&self) -> &dyn Any {
702 self
703 }
704 }
705
706 fn json(v: Value) -> serde_json::Value {
707 serde_json::to_value(v).expect("Value must serialise")
708 }
709
710 #[test]
711 fn serialises_as_the_bare_scalar_not_a_tagged_variant() {
712 assert_eq!(json(Value::I32(100)), serde_json::json!(100));
713 assert_eq!(json(Value::I64(-7)), serde_json::json!(-7));
714 assert_eq!(json(Value::U8(3)), serde_json::json!(3));
715 assert_eq!(json(Value::Text("100".into())), serde_json::json!("100"));
716 assert_eq!(json(Value::Bool(true)), serde_json::json!(true));
717 assert_eq!(json(Value::F64(1.5)), serde_json::json!(1.5));
718 assert_eq!(json(Value::Null), serde_json::Value::Null);
719 }
720
721 #[test]
722 fn serialises_a_whole_arg_list_as_a_plain_json_array() {
723 let args = vec![Value::I32(100), Value::Text("Stephen".into())];
725 assert_eq!(
726 serde_json::to_value(&args).unwrap(),
727 serde_json::json!([100, "Stephen"])
728 );
729 }
730
731 #[test]
732 fn serialises_arrays_and_bytes_structurally() {
733 assert_eq!(
734 json(Value::array([1i32, 2, 3])),
735 serde_json::json!([1, 2, 3])
736 );
737 assert_eq!(json(Value::Bytes(vec![1, 2])), serde_json::json!([1, 2]));
738 }
739
740 #[test]
741 fn custom_values_serialise_through_their_plain_form() {
742 assert_eq!(json(Value::custom(Point(1, 2))), serde_json::json!("(1,2)"));
743 assert_eq!(json(Value::custom(Opaque)), serde_json::Value::Null);
744 }
745
746 #[test]
747 fn custom_values_are_downcastable_by_a_backend() {
748 let v = Value::custom(Point(3, 4));
749 let Value::Custom(c) = &v else {
750 panic!("expected a custom value");
751 };
752 let p = c.as_any().downcast_ref::<Point>().expect("downcast");
753 assert_eq!((p.0, p.1), (3, 4));
754 assert_eq!(v.type_name(), "point");
755 }
756
757 #[test]
758 fn option_none_binds_as_null() {
759 assert_eq!(None::<i32>.to_value(), Value::Null);
760 assert_eq!(Some(4i32).to_value(), Value::I32(4));
761 assert_eq!(Some("a").to_value(), Value::Text("a".into()));
762 assert!(None::<String>.to_value().is_null());
763 }
764
765 #[test]
766 fn to_value_covers_the_obvious_primitives() {
767 assert_eq!(true.to_value(), Value::Bool(true));
768 assert_eq!(1i16.to_value(), Value::I16(1));
769 assert_eq!(1u32.to_value(), Value::U32(1));
770 assert_eq!(1.5f32.to_value(), Value::F32(1.5));
771 assert_eq!("x".to_value(), Value::Text("x".into()));
772 assert_eq!(String::from("x").to_value(), Value::Text("x".into()));
773 assert_eq!(
774 std::borrow::Cow::Borrowed("x").to_value(),
775 Value::Text("x".into())
776 );
777 assert_eq!(vec![1u8, 2].to_value(), Value::Bytes(vec![1, 2]));
778 assert_eq!(9usize.to_value(), Value::U64(9));
779 assert_eq!((-9isize).to_value(), Value::I64(-9));
780 assert_eq!(().to_value(), Value::Null);
781 assert_eq!(Value::I32(1).to_value(), Value::I32(1));
782 }
783
784 #[test]
785 fn from_value_widens_and_rejects_overflow() {
786 assert_eq!(i64::from_value(Value::I32(5)).unwrap(), 5);
787 assert_eq!(u8::from_value(Value::I64(200)).unwrap(), 200);
788 assert!(u8::from_value(Value::I64(300)).is_err());
789 assert!(i32::from_value(Value::Text("3".into())).is_err());
790 assert_eq!(f64::from_value(Value::I32(2)).unwrap(), 2.0);
791 assert!(bool::from_value(Value::Bool(false)).unwrap().eq(&false));
792 assert_eq!(String::from_value(Value::Text("s".into())).unwrap(), "s");
793 assert_eq!(Option::<i32>::from_value(Value::Null).unwrap(), None);
794 assert_eq!(
795 from_value_array::<i32>(Value::array([1i32, 2])).unwrap(),
796 vec![1, 2]
797 );
798 assert!(from_value_array::<i32>(Value::I32(1)).is_err());
799 }
800
801 #[test]
802 fn type_mismatch_explains_both_sides() {
803 let e = i32::from_value(Value::Text("3".into())).unwrap_err();
804 assert_eq!(e.to_string(), "cannot read text as i32");
805 }
806
807 #[cfg(feature = "chrono")]
812 mod chrono_values {
813 use super::*;
814 use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
815
816 fn date() -> NaiveDate {
817 NaiveDate::from_ymd_opt(2026, 7, 30).unwrap()
818 }
819
820 fn time() -> NaiveTime {
821 NaiveTime::from_hms_opt(12, 34, 56).unwrap()
822 }
823
824 #[test]
825 fn to_value_wraps_each_temporal_type() {
826 assert_eq!(date().to_value(), Value::Date(date()));
827 assert_eq!(time().to_value(), Value::Time(time()));
828 let dt = date().and_time(time());
829 assert_eq!(dt.to_value(), Value::DateTime(dt));
830 let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
831 assert_eq!(utc.to_value(), Value::TimestampTz(utc));
832 }
833
834 #[test]
835 fn zoned_datetimes_normalise_to_utc() {
836 let jst: DateTime<FixedOffset> = "2026-07-30T21:34:56+09:00".parse().unwrap();
838 let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
839 assert_eq!(jst.to_value(), Value::TimestampTz(utc));
840 }
841
842 #[test]
843 fn serialises_as_iso_8601_strings() {
844 assert_eq!(json(date().to_value()), serde_json::json!("2026-07-30"));
845 assert_eq!(json(time().to_value()), serde_json::json!("12:34:56"));
846 assert_eq!(
847 json(date().and_time(time()).to_value()),
848 serde_json::json!("2026-07-30T12:34:56")
849 );
850 let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
851 assert_eq!(
852 json(utc.to_value()),
853 serde_json::json!("2026-07-30T12:34:56Z")
854 );
855 }
856
857 #[test]
858 fn fractional_seconds_appear_only_when_non_zero() {
859 let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap();
860 assert_eq!(json(t.to_value()), serde_json::json!("12:34:56.789"));
861 let dt = date().and_time(t);
862 assert_eq!(
863 json(dt.to_value()),
864 serde_json::json!("2026-07-30T12:34:56.789")
865 );
866 let utc = DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc);
867 assert_eq!(
868 json(utc.to_value()),
869 serde_json::json!("2026-07-30T12:34:56.789Z")
870 );
871 }
872
873 #[test]
874 fn round_trips_from_its_own_variant_and_serialised_text() {
875 let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
876 assert_eq!(NaiveDate::from_value(date().to_value()).unwrap(), date());
877 assert_eq!(
878 NaiveDate::from_value(Value::Text("2026-07-30".into())).unwrap(),
879 date()
880 );
881 assert_eq!(
882 NaiveTime::from_value(Value::Text("12:34:56".into())).unwrap(),
883 time()
884 );
885 let dt = date().and_time(time());
886 assert_eq!(
887 NaiveDateTime::from_value(Value::Text("2026-07-30T12:34:56".into())).unwrap(),
888 dt
889 );
890 assert_eq!(
892 NaiveDateTime::from_value(Value::Text("2026-07-30 12:34:56".into())).unwrap(),
893 dt
894 );
895 assert_eq!(DateTime::<Utc>::from_value(utc.to_value()).unwrap(), utc);
896 assert_eq!(
897 DateTime::<Utc>::from_value(Value::Text("2026-07-30T21:34:56+09:00".into()))
898 .unwrap(),
899 utc
900 );
901 assert!(NaiveDate::from_value(Value::I32(1)).is_err());
902 assert!(NaiveDate::from_value(Value::Text("not a date".into())).is_err());
903 }
904
905 #[test]
906 fn type_names_are_reported() {
907 assert_eq!(date().to_value().type_name(), "date");
908 assert_eq!(time().to_value().type_name(), "time");
909 assert_eq!(date().and_time(time()).to_value().type_name(), "datetime");
910 let utc = Utc.with_ymd_and_hms(2026, 7, 30, 0, 0, 0).unwrap();
911 assert_eq!(utc.to_value().type_name(), "timestamptz");
912 }
913 }
914
915 #[cfg(feature = "uuid")]
916 mod uuid_values {
917 use super::*;
918 use uuid::Uuid;
919
920 const HYPHENATED: &str = "550e8400-e29b-41d4-a716-446655440000";
921
922 #[test]
923 fn binds_serialises_and_round_trips() {
924 let u = Uuid::parse_str(HYPHENATED).unwrap();
925 assert_eq!(u.to_value(), Value::Uuid(u));
926 assert_eq!(u.to_value().type_name(), "uuid");
927 assert_eq!(json(u.to_value()), serde_json::json!(HYPHENATED));
928 assert_eq!(Uuid::from_value(u.to_value()).unwrap(), u);
929 assert_eq!(Uuid::from_value(Value::Text(HYPHENATED.into())).unwrap(), u);
930 assert_eq!(
931 Uuid::from_value(Value::Bytes(u.as_bytes().to_vec())).unwrap(),
932 u
933 );
934 assert!(Uuid::from_value(Value::Bytes(vec![1, 2, 3])).is_err());
935 assert!(Uuid::from_value(Value::I32(1)).is_err());
936 }
937 }
938
939 #[cfg(feature = "decimal")]
940 mod decimal_values {
941 use super::*;
942 use rust_decimal::Decimal;
943
944 #[test]
945 fn binds_serialises_and_round_trips() {
946 let d = Decimal::new(1999, 2);
948 assert_eq!(d.to_value(), Value::Decimal(d));
949 assert_eq!(d.to_value().type_name(), "decimal");
950 assert_eq!(json(d.to_value()), serde_json::json!("19.99"));
952 assert_eq!(Decimal::from_value(d.to_value()).unwrap(), d);
953 assert_eq!(Decimal::from_value(Value::Text("19.99".into())).unwrap(), d);
954 assert_eq!(
955 Decimal::from_value(Value::I64(7)).unwrap(),
956 Decimal::from(7)
957 );
958 assert!(Decimal::from_value(Value::F64(19.99)).is_err());
960 }
961
962 #[test]
963 fn trailing_zeros_survive_serialisation() {
964 let d = Decimal::new(110, 2);
966 assert_eq!(json(d.to_value()), serde_json::json!("1.10"));
967 assert_eq!(d.to_value(), Decimal::new(11, 1).to_value());
969 }
970 }
971
972 #[cfg(feature = "json")]
973 mod json_values {
974 use super::*;
975
976 #[test]
977 fn binds_serialises_structurally_and_round_trips() {
978 let doc = serde_json::json!({"a": [1, 2], "b": "x"});
979 assert_eq!(doc.clone().to_value(), Value::Json(doc.clone()));
980 assert_eq!(doc.clone().to_value().type_name(), "json");
981 assert_eq!(json(doc.clone().to_value()), doc);
983 assert_eq!(
984 serde_json::Value::from_value(doc.clone().to_value()).unwrap(),
985 doc
986 );
987 assert_eq!(
988 serde_json::Value::from_value(Value::Text(r#"{"a":[1,2],"b":"x"}"#.into()))
989 .unwrap(),
990 doc
991 );
992 assert!(serde_json::Value::from_value(Value::Text("not json".into())).is_err());
993 assert!(serde_json::Value::from_value(Value::I32(1)).is_err());
994 }
995 }
996}