1use std::collections::BTreeMap;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8macro_rules! string_newtype {
12 ($(#[$meta:meta])* $name:ident, $what:literal) => {
13 $(#[$meta])*
14 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15 #[serde(transparent)]
16 pub struct $name(String);
17
18 impl $name {
19 #[doc = concat!("Create a ", $what, " value from its textual representation.")]
20 pub fn new(value: impl Into<String>) -> Self {
21 Self(value.into())
22 }
23
24 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28
29 pub fn into_inner(self) -> String {
31 self.0
32 }
33 }
34
35 impl From<String> for $name {
36 fn from(value: String) -> Self {
37 Self(value)
38 }
39 }
40
41 impl From<&str> for $name {
42 fn from(value: &str) -> Self {
43 Self(value.to_string())
44 }
45 }
46
47 impl AsRef<str> for $name {
48 fn as_ref(&self) -> &str {
49 self.as_str()
50 }
51 }
52
53 impl std::fmt::Display for $name {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_str(self.as_str())
56 }
57 }
58 };
59}
60
61string_newtype!(
62 Geometry,
64 "geometry"
65);
66
67string_newtype!(
68 Geography,
70 "geography"
71);
72
73#[derive(Debug, Clone, PartialEq)]
81pub enum Value {
82 Null,
84 Bool(bool),
86 I32(i32),
88 I64(i64),
90 F64(f64),
92 Decimal(rust_decimal::Decimal),
94 DateTime(chrono::NaiveDateTime),
96 Uuid(uuid::Uuid),
98 Json(serde_json::Value),
100 Hstore(BTreeMap<String, Option<String>>),
102 Geometry(String),
104 Geography(String),
106 Vector(Vec<f32>),
108 String(String),
110 Bytes(Vec<u8>),
112 Array(Vec<Value>),
114 Array2D(Vec<Vec<Value>>),
116 Enum {
123 value: String,
125 type_name: String,
127 },
128 Composite {
136 type_name: String,
138 fields: Vec<Value>,
140 },
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144#[serde(tag = "type", content = "value", rename_all = "snake_case")]
145enum SerdeValue {
146 Null,
147 Bool(bool),
148 I32(i32),
149 I64(i64),
150 F64(f64),
151 Decimal(String),
152 DateTime(String),
153 Uuid(String),
154 Json(serde_json::Value),
155 Hstore(BTreeMap<String, Option<String>>),
156 Geometry(String),
157 Geography(String),
158 Vector(Vec<f32>),
159 String(String),
160 Bytes(String),
161 Array(Vec<Value>),
162 Array2D(Vec<Vec<Value>>),
163 Enum {
164 value: String,
165 type_name: String,
166 },
167 Composite {
168 type_name: String,
169 fields: Vec<Value>,
170 },
171}
172
173const DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ";
175
176fn format_datetime(value: chrono::NaiveDateTime) -> String {
177 value.format(DATETIME_FORMAT).to_string()
178}
179
180fn parse_datetime_string(raw: &str) -> std::result::Result<chrono::NaiveDateTime, String> {
181 chrono::DateTime::parse_from_rfc3339(raw)
182 .map(|value| value.naive_utc())
183 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f"))
184 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%d %H:%M:%S%.f"))
185 .map_err(|_| format!("invalid datetime '{}'", raw))
186}
187
188impl From<&Value> for SerdeValue {
189 fn from(value: &Value) -> Self {
190 match value {
191 Value::Null => SerdeValue::Null,
192 Value::Bool(v) => SerdeValue::Bool(*v),
193 Value::I32(v) => SerdeValue::I32(*v),
194 Value::I64(v) => SerdeValue::I64(*v),
195 Value::F64(v) => SerdeValue::F64(*v),
196 Value::Decimal(v) => SerdeValue::Decimal(v.to_string()),
197 Value::DateTime(v) => SerdeValue::DateTime(format_datetime(*v)),
198 Value::Uuid(v) => SerdeValue::Uuid(v.to_string()),
199 Value::Json(v) => SerdeValue::Json(v.clone()),
200 Value::Hstore(v) => SerdeValue::Hstore(v.clone()),
201 Value::Geometry(v) => SerdeValue::Geometry(v.clone()),
202 Value::Geography(v) => SerdeValue::Geography(v.clone()),
203 Value::Vector(v) => SerdeValue::Vector(v.clone()),
204 Value::String(v) => SerdeValue::String(v.clone()),
205 Value::Bytes(v) => {
206 use base64::Engine;
207 SerdeValue::Bytes(base64::engine::general_purpose::STANDARD.encode(v))
208 }
209 Value::Array(v) => SerdeValue::Array(v.clone()),
210 Value::Array2D(v) => SerdeValue::Array2D(v.clone()),
211 Value::Enum { value, type_name } => SerdeValue::Enum {
212 value: value.clone(),
213 type_name: type_name.clone(),
214 },
215 Value::Composite { type_name, fields } => SerdeValue::Composite {
216 type_name: type_name.clone(),
217 fields: fields.clone(),
218 },
219 }
220 }
221}
222
223impl TryFrom<SerdeValue> for Value {
224 type Error = String;
225
226 fn try_from(value: SerdeValue) -> std::result::Result<Self, Self::Error> {
227 match value {
228 SerdeValue::Null => Ok(Value::Null),
229 SerdeValue::Bool(v) => Ok(Value::Bool(v)),
230 SerdeValue::I32(v) => Ok(Value::I32(v)),
231 SerdeValue::I64(v) => Ok(Value::I64(v)),
232 SerdeValue::F64(v) => Ok(Value::F64(v)),
233 SerdeValue::Decimal(raw) => rust_decimal::Decimal::from_str(&raw)
234 .map(Value::Decimal)
235 .map_err(|e| format!("invalid decimal '{}': {}", raw, e)),
236 SerdeValue::DateTime(raw) => parse_datetime_string(&raw).map(Value::DateTime),
237 SerdeValue::Uuid(raw) => uuid::Uuid::parse_str(&raw)
238 .map(Value::Uuid)
239 .map_err(|e| format!("invalid uuid '{}': {}", raw, e)),
240 SerdeValue::Json(v) => Ok(Value::Json(v)),
241 SerdeValue::Hstore(v) => Ok(Value::Hstore(v)),
242 SerdeValue::Geometry(v) => Ok(Value::Geometry(v)),
243 SerdeValue::Geography(v) => Ok(Value::Geography(v)),
244 SerdeValue::Vector(v) => Ok(Value::Vector(v)),
245 SerdeValue::String(v) => Ok(Value::String(v)),
246 SerdeValue::Bytes(raw) => {
247 use base64::Engine;
248 base64::engine::general_purpose::STANDARD
249 .decode(raw.as_bytes())
250 .map(Value::Bytes)
251 .map_err(|e| format!("invalid base64 bytes '{}': {}", raw, e))
252 }
253 SerdeValue::Array(v) => Ok(Value::Array(v)),
254 SerdeValue::Array2D(v) => Ok(Value::Array2D(v)),
255 SerdeValue::Enum { value, type_name } => Ok(Value::Enum { value, type_name }),
256 SerdeValue::Composite { type_name, fields } => {
257 Ok(Value::Composite { type_name, fields })
258 }
259 }
260 }
261}
262
263impl From<bool> for Value {
264 fn from(v: bool) -> Self {
265 Value::Bool(v)
266 }
267}
268
269impl From<i32> for Value {
270 fn from(v: i32) -> Self {
271 Value::I32(v)
272 }
273}
274
275impl From<i64> for Value {
276 fn from(v: i64) -> Self {
277 Value::I64(v)
278 }
279}
280
281impl From<f64> for Value {
282 fn from(v: f64) -> Self {
283 Value::F64(v)
284 }
285}
286
287impl From<f32> for Value {
288 fn from(v: f32) -> Self {
289 Value::F64(v as f64)
290 }
291}
292
293impl From<rust_decimal::Decimal> for Value {
294 fn from(v: rust_decimal::Decimal) -> Self {
295 Value::Decimal(v)
296 }
297}
298
299impl From<chrono::NaiveDateTime> for Value {
300 fn from(v: chrono::NaiveDateTime) -> Self {
301 Value::DateTime(v)
302 }
303}
304
305impl From<uuid::Uuid> for Value {
306 fn from(v: uuid::Uuid) -> Self {
307 Value::Uuid(v)
308 }
309}
310
311impl From<serde_json::Value> for Value {
312 fn from(v: serde_json::Value) -> Self {
313 Value::Json(v)
314 }
315}
316
317impl From<BTreeMap<String, Option<String>>> for Value {
318 fn from(v: BTreeMap<String, Option<String>>) -> Self {
319 Value::Hstore(v)
320 }
321}
322
323impl From<Geometry> for Value {
324 fn from(v: Geometry) -> Self {
325 Value::Geometry(v.into_inner())
326 }
327}
328
329impl From<Geography> for Value {
330 fn from(v: Geography) -> Self {
331 Value::Geography(v.into_inner())
332 }
333}
334
335impl From<Vec<f32>> for Value {
336 fn from(v: Vec<f32>) -> Self {
337 Value::Vector(v)
338 }
339}
340
341impl From<String> for Value {
342 fn from(v: String) -> Self {
343 Value::String(v)
344 }
345}
346
347impl From<&str> for Value {
348 fn from(v: &str) -> Self {
349 Value::String(v.to_string())
350 }
351}
352
353impl From<Vec<u8>> for Value {
354 fn from(v: Vec<u8>) -> Self {
355 Value::Bytes(v)
356 }
357}
358
359macro_rules! impl_vec_from {
363 ($($t:ty),* $(,)?) => {
364 $(
365 impl From<Vec<$t>> for Value {
366 fn from(v: Vec<$t>) -> Self {
367 Value::Array(v.into_iter().map(|x| x.into()).collect())
368 }
369 }
370
371 impl From<Vec<Vec<$t>>> for Value {
372 fn from(v: Vec<Vec<$t>>) -> Self {
373 Value::Array2D(
374 v.into_iter()
375 .map(|row| row.into_iter().map(|x| x.into()).collect())
376 .collect(),
377 )
378 }
379 }
380 )*
381 };
382}
383
384impl_vec_from!(
385 i32,
386 i64,
387 f64,
388 bool,
389 String,
390 Geometry,
391 Geography,
392 BTreeMap<String, Option<String>>,
393 rust_decimal::Decimal,
394 uuid::Uuid,
395 chrono::NaiveDateTime,
396 serde_json::Value,
397);
398
399macro_rules! impl_option_from {
403 ($($t:ty),* $(,)?) => {
404 $(
405 impl From<Option<$t>> for Value {
406 fn from(v: Option<$t>) -> Self {
407 v.map(|x| x.into()).unwrap_or(Value::Null)
408 }
409 }
410 )*
411 };
412}
413
414impl_option_from!(
415 bool,
416 i32,
417 i64,
418 f64,
419 String,
420 Vec<f32>,
421 Geometry,
422 Geography,
423 BTreeMap<String, Option<String>>,
424 rust_decimal::Decimal,
425 uuid::Uuid,
426 chrono::NaiveDateTime,
427);
428
429impl From<Option<&str>> for Value {
430 fn from(v: Option<&str>) -> Self {
431 v.map(|s| Value::String(s.to_string()))
432 .unwrap_or(Value::Null)
433 }
434}
435
436impl Value {
437 pub fn to_json_plain(&self) -> serde_json::Value {
443 match self {
444 Value::Null => serde_json::Value::Null,
445 Value::Bool(v) => serde_json::Value::Bool(*v),
446 Value::I32(v) => serde_json::Value::Number((*v).into()),
447 Value::I64(v) => serde_json::Value::Number((*v).into()),
448 Value::F64(v) => serde_json::Number::from_f64(*v)
449 .map(serde_json::Value::Number)
450 .unwrap_or(serde_json::Value::Null),
451 Value::Decimal(v) => serde_json::Value::String(v.to_string()),
452 Value::DateTime(v) => serde_json::Value::String(format_datetime(*v)),
453 Value::Uuid(v) => serde_json::Value::String(v.to_string()),
454 Value::Json(v) => v.clone(),
455 Value::Hstore(v) => serde_json::Value::Object(
456 v.iter()
457 .map(|(key, value)| {
458 (
459 key.clone(),
460 value
461 .as_ref()
462 .map(|item| serde_json::Value::String(item.clone()))
463 .unwrap_or(serde_json::Value::Null),
464 )
465 })
466 .collect(),
467 ),
468 Value::Geometry(v) | Value::Geography(v) => serde_json::Value::String(v.clone()),
469 Value::Vector(v) => serde_json::Value::Array(
470 v.iter()
471 .map(|item| {
472 serde_json::Number::from_f64(*item as f64)
473 .map(serde_json::Value::Number)
474 .unwrap_or(serde_json::Value::Null)
475 })
476 .collect(),
477 ),
478 Value::String(v) => serde_json::Value::String(v.clone()),
479 Value::Bytes(v) => {
480 use base64::Engine;
481 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(v))
482 }
483 Value::Array(v) => {
484 serde_json::Value::Array(v.iter().map(Value::to_json_plain).collect())
485 }
486 Value::Array2D(v) => serde_json::Value::Array(
487 v.iter()
488 .map(|row| {
489 serde_json::Value::Array(row.iter().map(Value::to_json_plain).collect())
490 })
491 .collect(),
492 ),
493 Value::Enum { value, .. } => serde_json::Value::String(value.clone()),
494 Value::Composite { fields, .. } => {
495 serde_json::Value::Array(fields.iter().map(Value::to_json_plain).collect())
496 }
497 }
498 }
499}
500
501struct DisplayString<T>(T);
503
504impl<T: std::fmt::Display> Serialize for DisplayString<T> {
505 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506 where
507 S: Serializer,
508 {
509 serializer.collect_str(&self.0)
510 }
511}
512
513struct DateTimeString(chrono::NaiveDateTime);
516
517impl Serialize for DateTimeString {
518 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
519 where
520 S: Serializer,
521 {
522 serializer.collect_str(&self.0.format(DATETIME_FORMAT))
523 }
524}
525
526struct Base64String<'a>(&'a [u8]);
529
530impl Serialize for Base64String<'_> {
531 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532 where
533 S: Serializer,
534 {
535 serializer.collect_str(&base64::display::Base64Display::new(
536 self.0,
537 &base64::engine::general_purpose::STANDARD,
538 ))
539 }
540}
541
542#[derive(Serialize)]
548#[serde(tag = "type", content = "value", rename_all = "snake_case")]
549enum SerdeValueRef<'a> {
550 Null,
551 Bool(bool),
552 I32(i32),
553 I64(i64),
554 F64(f64),
555 Decimal(DisplayString<&'a rust_decimal::Decimal>),
556 DateTime(DateTimeString),
557 Uuid(DisplayString<&'a uuid::Uuid>),
558 Json(&'a serde_json::Value),
559 Hstore(&'a BTreeMap<String, Option<String>>),
560 Geometry(&'a str),
561 Geography(&'a str),
562 Vector(&'a [f32]),
563 String(&'a str),
564 Bytes(Base64String<'a>),
565 Array(&'a [Value]),
566 Array2D(&'a [Vec<Value>]),
567 Enum {
568 value: &'a str,
569 type_name: &'a str,
570 },
571 Composite {
572 type_name: &'a str,
573 fields: &'a [Value],
574 },
575}
576
577impl<'a> From<&'a Value> for SerdeValueRef<'a> {
578 fn from(value: &'a Value) -> Self {
579 match value {
580 Value::Null => SerdeValueRef::Null,
581 Value::Bool(v) => SerdeValueRef::Bool(*v),
582 Value::I32(v) => SerdeValueRef::I32(*v),
583 Value::I64(v) => SerdeValueRef::I64(*v),
584 Value::F64(v) => SerdeValueRef::F64(*v),
585 Value::Decimal(v) => SerdeValueRef::Decimal(DisplayString(v)),
586 Value::DateTime(v) => SerdeValueRef::DateTime(DateTimeString(*v)),
587 Value::Uuid(v) => SerdeValueRef::Uuid(DisplayString(v)),
588 Value::Json(v) => SerdeValueRef::Json(v),
589 Value::Hstore(v) => SerdeValueRef::Hstore(v),
590 Value::Geometry(v) => SerdeValueRef::Geometry(v),
591 Value::Geography(v) => SerdeValueRef::Geography(v),
592 Value::Vector(v) => SerdeValueRef::Vector(v),
593 Value::String(v) => SerdeValueRef::String(v),
594 Value::Bytes(v) => SerdeValueRef::Bytes(Base64String(v)),
595 Value::Array(v) => SerdeValueRef::Array(v),
596 Value::Array2D(v) => SerdeValueRef::Array2D(v),
597 Value::Enum { value, type_name } => SerdeValueRef::Enum { value, type_name },
598 Value::Composite { type_name, fields } => {
599 SerdeValueRef::Composite { type_name, fields }
600 }
601 }
602 }
603}
604
605pub struct PlainValueRef<'a>(pub &'a Value);
614
615struct PlainF64(f64);
618
619impl Serialize for PlainF64 {
620 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
621 where
622 S: Serializer,
623 {
624 if self.0.is_finite() {
625 serializer.serialize_f64(self.0)
626 } else {
627 serializer.serialize_unit()
628 }
629 }
630}
631
632struct PlainSliceRef<'a>(&'a [Value]);
633
634impl Serialize for PlainSliceRef<'_> {
635 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
636 where
637 S: Serializer,
638 {
639 serializer.collect_seq(self.0.iter().map(PlainValueRef))
640 }
641}
642
643impl Serialize for PlainValueRef<'_> {
644 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
645 where
646 S: Serializer,
647 {
648 match self.0 {
649 Value::Null => serializer.serialize_unit(),
650 Value::Bool(v) => serializer.serialize_bool(*v),
651 Value::I32(v) => serializer.serialize_i32(*v),
652 Value::I64(v) => serializer.serialize_i64(*v),
653 Value::F64(v) => PlainF64(*v).serialize(serializer),
654 Value::Decimal(v) => serializer.collect_str(v),
655 Value::DateTime(v) => DateTimeString(*v).serialize(serializer),
656 Value::Uuid(v) => serializer.collect_str(v),
657 Value::Json(v) => v.serialize(serializer),
658 Value::Hstore(v) => serializer.collect_map(v.iter()),
659 Value::Geometry(v) | Value::Geography(v) => serializer.serialize_str(v),
660 Value::Vector(v) => serializer.collect_seq(v.iter().map(|item| PlainF64(*item as f64))),
661 Value::String(v) => serializer.serialize_str(v),
662 Value::Bytes(v) => Base64String(v).serialize(serializer),
663 Value::Array(v) => serializer.collect_seq(v.iter().map(PlainValueRef)),
664 Value::Array2D(v) => serializer.collect_seq(v.iter().map(|row| PlainSliceRef(row))),
665 Value::Enum { value, .. } => serializer.serialize_str(value),
666 Value::Composite { fields, .. } => {
667 serializer.collect_seq(fields.iter().map(PlainValueRef))
668 }
669 }
670 }
671}
672
673impl Serialize for Value {
674 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
675 where
676 S: Serializer,
677 {
678 SerdeValueRef::from(self).serialize(serializer)
679 }
680}
681
682impl<'de> Deserialize<'de> for Value {
685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686 where
687 D: Deserializer<'de>,
688 {
689 let tagged = SerdeValue::deserialize(deserializer)?;
690 Value::try_from(tagged).map_err(serde::de::Error::custom)
691 }
692}
693
694pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
705 match json {
706 serde_json::Value::Null => Value::Null,
707 serde_json::Value::Bool(b) => Value::Bool(*b),
708 serde_json::Value::Number(n) => {
709 if let Some(i) = n.as_i64() {
710 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
711 Value::I32(i as i32)
712 } else {
713 Value::I64(i)
714 }
715 } else if let Some(f) = n.as_f64() {
716 Value::F64(f)
717 } else {
718 Value::String(n.to_string())
719 }
720 }
721 serde_json::Value::String(s) => Value::String(s.clone()),
722 serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
723 serde_json::Value::Object(_) => Value::Json(json.clone()),
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use core::f64;
730 use std::collections::BTreeMap;
731
732 use super::*;
733
734 #[test]
735 fn test_value_variants() {
736 assert_eq!(Value::Null, Value::Null);
737 assert_eq!(Value::Bool(true), Value::from(true));
738 assert_eq!(Value::I32(42), Value::from(42i32));
739 assert_eq!(Value::I64(42), Value::from(42i64));
740 assert_eq!(Value::F64(2.5), Value::from(2.5f64));
741 assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
742 assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
743
744 use rust_decimal::Decimal;
745 let dec = Decimal::new(12345, 2);
746 assert_eq!(Value::Decimal(dec), Value::from(dec));
747
748 use chrono::NaiveDate;
749 let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
750 .unwrap()
751 .and_hms_opt(12, 0, 0)
752 .unwrap();
753 assert_eq!(Value::DateTime(dt), Value::from(dt));
754
755 use uuid::Uuid;
756 let id = Uuid::nil();
757 assert_eq!(Value::Uuid(id), Value::from(id));
758
759 use serde_json::json;
760 let j = json!({"key": "value"});
761 assert_eq!(Value::Json(j.clone()), Value::from(j));
762
763 let hstore = BTreeMap::from([
764 ("display_name".to_string(), Some("Bob".to_string())),
765 ("nickname".to_string(), None),
766 ]);
767 assert_eq!(Value::Hstore(hstore.clone()), Value::from(hstore));
768
769 assert_eq!(
770 Value::Vector(vec![0.1, 0.2]),
771 Value::from(vec![0.1f32, 0.2])
772 );
773 }
774
775 #[test]
776 fn test_value_to_json_plain_primitives() {
777 assert_eq!(Value::Null.to_json_plain(), serde_json::Value::Null);
778 assert_eq!(
779 Value::Bool(true).to_json_plain(),
780 serde_json::Value::Bool(true)
781 );
782 assert_eq!(Value::I32(42).to_json_plain().as_i64(), Some(42));
783 assert_eq!(
784 Value::I64(9007199254740991).to_json_plain().as_i64(),
785 Some(9007199254740991)
786 );
787 assert_eq!(
788 Value::F64(f64::consts::PI).to_json_plain().as_f64(),
789 Some(f64::consts::PI)
790 );
791 assert_eq!(
792 Value::String("hello world".to_string())
793 .to_json_plain()
794 .as_str(),
795 Some("hello world")
796 );
797 }
798
799 #[test]
800 fn test_value_to_json_plain_special_scalars() {
801 use rust_decimal::Decimal;
802 let dec = Decimal::new(12345, 2);
803 use chrono::NaiveDate;
804 let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
805 .unwrap()
806 .and_hms_opt(10, 30, 45)
807 .unwrap();
808 use uuid::Uuid;
809 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
810 assert_eq!(Value::Decimal(dec).to_json_plain().as_str(), Some("123.45"));
811 assert!(Value::DateTime(dt)
812 .to_json_plain()
813 .as_str()
814 .unwrap()
815 .starts_with("2026-02-18T10:30:45"));
816 assert_eq!(
817 Value::Uuid(id).to_json_plain().as_str(),
818 Some("550e8400-e29b-41d4-a716-446655440000")
819 );
820 assert_eq!(
821 Value::Bytes(vec![72, 101, 108, 108, 111])
822 .to_json_plain()
823 .as_str(),
824 Some("SGVsbG8=")
825 );
826 }
827
828 #[test]
829 fn test_value_to_json_plain_json_and_arrays() {
830 use serde_json::json;
831 let object = json!({"name": "Alice", "age": 30});
832 assert_eq!(Value::Json(object.clone()).to_json_plain(), object);
833
834 let value = Value::Array(vec![
835 Value::String("a".to_string()),
836 Value::String("b".to_string()),
837 Value::String("c".to_string()),
838 ]);
839
840 let json = value.to_json_plain();
841 assert_eq!(json[0].as_str(), Some("a"));
842 assert_eq!(json[1].as_str(), Some("b"));
843 assert_eq!(json[2].as_str(), Some("c"));
844 }
845
846 #[test]
847 fn test_value_to_json_plain_hstore() {
848 let value = Value::Hstore(BTreeMap::from([
849 ("display_name".to_string(), Some("Bob".to_string())),
850 ("nickname".to_string(), None),
851 ]));
852
853 assert_eq!(
854 value.to_json_plain(),
855 serde_json::json!({
856 "display_name": "Bob",
857 "nickname": null
858 })
859 );
860 }
861
862 #[test]
863 fn test_value_to_json_plain_vector() {
864 let json = Value::Vector(vec![1.0, 2.5, 3.25]).to_json_plain();
865 assert_eq!(json, serde_json::json!([1.0, 2.5, 3.25]));
866 }
867
868 #[test]
869 fn test_value_plain_json_array2d_roundtrip_stays_untyped_without_schema() {
870 let value = Value::Array2D(vec![
871 vec![Value::I32(1), Value::I32(2)],
872 vec![Value::I32(3), Value::I32(4)],
873 ]);
874
875 let json = value.to_json_plain();
876 assert_eq!(json[0][0].as_i64(), Some(1));
877 assert_eq!(json[0][1].as_i64(), Some(2));
878 assert_eq!(json[1][0].as_i64(), Some(3));
879 assert_eq!(json[1][1].as_i64(), Some(4));
880
881 let expected = Value::Array(vec![
886 Value::Array(vec![Value::I32(1), Value::I32(2)]),
887 Value::Array(vec![Value::I32(3), Value::I32(4)]),
888 ]);
889 assert_eq!(json_to_value_ref(&json), expected);
890 }
891
892 #[test]
893 fn test_tagged_serde_shape_is_explicit() {
894 let value = Value::Decimal(rust_decimal::Decimal::new(12345, 2));
895 let json = serde_json::to_value(&value).unwrap();
896
897 assert_eq!(
898 json,
899 serde_json::json!({
900 "type": "decimal",
901 "value": "123.45"
902 })
903 );
904 }
905
906 #[test]
907 fn test_tagged_serde_round_trip_preserves_typed_variants() {
908 use chrono::NaiveDate;
909 use serde_json::json;
910 use uuid::Uuid;
911
912 let values = vec![
913 Value::Null,
914 Value::Bool(false),
915 Value::I32(-42),
916 Value::I64(9007199254740991), Value::F64(f64::consts::E),
918 Value::Decimal(rust_decimal::Decimal::new(314, 2)),
919 Value::DateTime(
920 NaiveDate::from_ymd_opt(2026, 2, 18)
921 .unwrap()
922 .and_hms_opt(10, 30, 45)
923 .unwrap(),
924 ),
925 Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
926 Value::Bytes(vec![1, 2, 3, 4]),
927 Value::Json(json!({"ok": true})),
928 Value::Hstore(BTreeMap::from([
929 ("display_name".to_string(), Some("Bob".to_string())),
930 ("nickname".to_string(), None),
931 ])),
932 Value::Vector(vec![1.0, 2.0, 3.5]),
933 Value::String("test".to_string()),
934 Value::Array(vec![Value::I32(1), Value::I32(2)]),
935 Value::Array2D(vec![vec![Value::I32(1), Value::I32(2)]]),
936 Value::Enum {
937 value: "ADMIN".to_string(),
938 type_name: "role".to_string(),
939 },
940 ];
941
942 for value in values {
943 let json = serde_json::to_value(&value).unwrap();
944 let deserialized: Value = serde_json::from_value(json).unwrap();
945 assert_eq!(deserialized, value);
946 }
947 }
948
949 fn plain_equivalence_samples() -> Vec<Value> {
953 use chrono::NaiveDate;
954 use serde_json::json;
955 use uuid::Uuid;
956
957 vec![
958 Value::Null,
959 Value::Bool(true),
960 Value::I32(-42),
961 Value::I64(9007199254740991),
962 Value::F64(f64::consts::PI),
963 Value::F64(f64::NAN),
964 Value::F64(f64::INFINITY),
965 Value::Decimal(rust_decimal::Decimal::new(-12345, 2)),
966 Value::DateTime(
967 NaiveDate::from_ymd_opt(2026, 2, 18)
968 .unwrap()
969 .and_hms_micro_opt(10, 30, 45, 123456)
970 .unwrap(),
971 ),
972 Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
973 Value::Json(json!({"nested": {"ok": true}, "list": [1, "two", null]})),
974 Value::Hstore(BTreeMap::from([
975 ("display_name".to_string(), Some("Bob".to_string())),
976 ("nickname".to_string(), None),
977 ])),
978 Value::Geometry("POINT(1 2)".to_string()),
979 Value::Geography("SRID=4326;POINT(9 45)".to_string()),
980 Value::Vector(vec![1.0, -2.5, f32::NAN]),
981 Value::String("hello \"quoted\" world".to_string()),
982 Value::Bytes(vec![72, 101, 108, 108, 111]),
983 Value::Array(vec![
984 Value::I32(1),
985 Value::String("two".to_string()),
986 Value::Null,
987 Value::Array(vec![Value::Bool(false)]),
988 ]),
989 Value::Array2D(vec![
990 vec![Value::I32(1), Value::I32(2)],
991 vec![Value::I32(3), Value::I32(4)],
992 ]),
993 Value::Enum {
994 value: "ADMIN".to_string(),
995 type_name: "role".to_string(),
996 },
997 Value::Composite {
998 type_name: "championstats".to_string(),
999 fields: vec![Value::I32(0), Value::String("x".to_string()), Value::Null],
1000 },
1001 ]
1002 }
1003
1004 #[test]
1005 fn test_plain_value_ref_matches_to_json_plain_tree() {
1006 for value in plain_equivalence_samples() {
1007 let via_ref = serde_json::to_value(PlainValueRef(&value)).unwrap();
1008 assert_eq!(via_ref, value.to_json_plain(), "variant: {:?}", value);
1009 }
1010 }
1011
1012 #[test]
1013 fn test_plain_value_ref_matches_to_json_plain_string() {
1014 for value in plain_equivalence_samples() {
1015 let via_ref = serde_json::to_string(&PlainValueRef(&value)).unwrap();
1016 let via_tree = serde_json::to_string(&value.to_json_plain()).unwrap();
1017 assert_eq!(via_ref, via_tree, "variant: {:?}", value);
1018 }
1019 }
1020
1021 #[test]
1022 fn test_tagged_serialize_borrowed_matches_owned_serde_value() {
1023 for value in plain_equivalence_samples() {
1024 let via_ref = serde_json::to_string(&value).unwrap();
1025 let via_owned = serde_json::to_string(&SerdeValue::from(&value)).unwrap();
1026 assert_eq!(via_ref, via_owned, "variant: {:?}", value);
1027 }
1028 }
1029}