1use std::collections::BTreeMap;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct Geometry(String);
12
13impl Geometry {
14 pub fn new(value: impl Into<String>) -> Self {
16 Self(value.into())
17 }
18
19 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23
24 pub fn into_inner(self) -> String {
26 self.0
27 }
28}
29
30impl From<String> for Geometry {
31 fn from(value: String) -> Self {
32 Self(value)
33 }
34}
35
36impl From<&str> for Geometry {
37 fn from(value: &str) -> Self {
38 Self(value.to_string())
39 }
40}
41
42impl AsRef<str> for Geometry {
43 fn as_ref(&self) -> &str {
44 self.as_str()
45 }
46}
47
48impl std::fmt::Display for Geometry {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.write_str(self.as_str())
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
56#[serde(transparent)]
57pub struct Geography(String);
58
59impl Geography {
60 pub fn new(value: impl Into<String>) -> Self {
62 Self(value.into())
63 }
64
65 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69
70 pub fn into_inner(self) -> String {
72 self.0
73 }
74}
75
76impl From<String> for Geography {
77 fn from(value: String) -> Self {
78 Self(value)
79 }
80}
81
82impl From<&str> for Geography {
83 fn from(value: &str) -> Self {
84 Self(value.to_string())
85 }
86}
87
88impl AsRef<str> for Geography {
89 fn as_ref(&self) -> &str {
90 self.as_str()
91 }
92}
93
94impl std::fmt::Display for Geography {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.write_str(self.as_str())
97 }
98}
99
100#[derive(Debug, Clone, PartialEq)]
108pub enum Value {
109 Null,
111 Bool(bool),
113 I32(i32),
115 I64(i64),
117 F64(f64),
119 Decimal(rust_decimal::Decimal),
121 DateTime(chrono::NaiveDateTime),
123 Uuid(uuid::Uuid),
125 Json(serde_json::Value),
127 Hstore(BTreeMap<String, Option<String>>),
129 Geometry(String),
131 Geography(String),
133 Vector(Vec<f32>),
135 String(String),
137 Bytes(Vec<u8>),
139 Array(Vec<Value>),
141 Array2D(Vec<Vec<Value>>),
143 Enum {
150 value: String,
152 type_name: String,
154 },
155 Composite {
163 type_name: String,
165 fields: Vec<Value>,
167 },
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171#[serde(tag = "type", content = "value", rename_all = "snake_case")]
172enum SerdeValue {
173 Null,
174 Bool(bool),
175 I32(i32),
176 I64(i64),
177 F64(f64),
178 Decimal(String),
179 DateTime(String),
180 Uuid(String),
181 Json(serde_json::Value),
182 Hstore(BTreeMap<String, Option<String>>),
183 Geometry(String),
184 Geography(String),
185 Vector(Vec<f32>),
186 String(String),
187 Bytes(String),
188 Array(Vec<Value>),
189 Array2D(Vec<Vec<Value>>),
190 Enum {
191 value: String,
192 type_name: String,
193 },
194 Composite {
195 type_name: String,
196 fields: Vec<Value>,
197 },
198}
199
200fn format_datetime(value: chrono::NaiveDateTime) -> String {
201 value.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string()
202}
203
204fn parse_datetime_string(raw: &str) -> std::result::Result<chrono::NaiveDateTime, String> {
205 chrono::DateTime::parse_from_rfc3339(raw)
206 .map(|value| value.naive_utc())
207 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f"))
208 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%d %H:%M:%S%.f"))
209 .map_err(|_| format!("invalid datetime '{}'", raw))
210}
211
212impl From<&Value> for SerdeValue {
213 fn from(value: &Value) -> Self {
214 match value {
215 Value::Null => SerdeValue::Null,
216 Value::Bool(v) => SerdeValue::Bool(*v),
217 Value::I32(v) => SerdeValue::I32(*v),
218 Value::I64(v) => SerdeValue::I64(*v),
219 Value::F64(v) => SerdeValue::F64(*v),
220 Value::Decimal(v) => SerdeValue::Decimal(v.to_string()),
221 Value::DateTime(v) => SerdeValue::DateTime(format_datetime(*v)),
222 Value::Uuid(v) => SerdeValue::Uuid(v.to_string()),
223 Value::Json(v) => SerdeValue::Json(v.clone()),
224 Value::Hstore(v) => SerdeValue::Hstore(v.clone()),
225 Value::Geometry(v) => SerdeValue::Geometry(v.clone()),
226 Value::Geography(v) => SerdeValue::Geography(v.clone()),
227 Value::Vector(v) => SerdeValue::Vector(v.clone()),
228 Value::String(v) => SerdeValue::String(v.clone()),
229 Value::Bytes(v) => {
230 use base64::Engine;
231 SerdeValue::Bytes(base64::engine::general_purpose::STANDARD.encode(v))
232 }
233 Value::Array(v) => SerdeValue::Array(v.clone()),
234 Value::Array2D(v) => SerdeValue::Array2D(v.clone()),
235 Value::Enum { value, type_name } => SerdeValue::Enum {
236 value: value.clone(),
237 type_name: type_name.clone(),
238 },
239 Value::Composite { type_name, fields } => SerdeValue::Composite {
240 type_name: type_name.clone(),
241 fields: fields.clone(),
242 },
243 }
244 }
245}
246
247impl TryFrom<SerdeValue> for Value {
248 type Error = String;
249
250 fn try_from(value: SerdeValue) -> std::result::Result<Self, Self::Error> {
251 match value {
252 SerdeValue::Null => Ok(Value::Null),
253 SerdeValue::Bool(v) => Ok(Value::Bool(v)),
254 SerdeValue::I32(v) => Ok(Value::I32(v)),
255 SerdeValue::I64(v) => Ok(Value::I64(v)),
256 SerdeValue::F64(v) => Ok(Value::F64(v)),
257 SerdeValue::Decimal(raw) => rust_decimal::Decimal::from_str(&raw)
258 .map(Value::Decimal)
259 .map_err(|e| format!("invalid decimal '{}': {}", raw, e)),
260 SerdeValue::DateTime(raw) => parse_datetime_string(&raw).map(Value::DateTime),
261 SerdeValue::Uuid(raw) => uuid::Uuid::parse_str(&raw)
262 .map(Value::Uuid)
263 .map_err(|e| format!("invalid uuid '{}': {}", raw, e)),
264 SerdeValue::Json(v) => Ok(Value::Json(v)),
265 SerdeValue::Hstore(v) => Ok(Value::Hstore(v)),
266 SerdeValue::Geometry(v) => Ok(Value::Geometry(v)),
267 SerdeValue::Geography(v) => Ok(Value::Geography(v)),
268 SerdeValue::Vector(v) => Ok(Value::Vector(v)),
269 SerdeValue::String(v) => Ok(Value::String(v)),
270 SerdeValue::Bytes(raw) => {
271 use base64::Engine;
272 base64::engine::general_purpose::STANDARD
273 .decode(raw.as_bytes())
274 .map(Value::Bytes)
275 .map_err(|e| format!("invalid base64 bytes '{}': {}", raw, e))
276 }
277 SerdeValue::Array(v) => Ok(Value::Array(v)),
278 SerdeValue::Array2D(v) => Ok(Value::Array2D(v)),
279 SerdeValue::Enum { value, type_name } => Ok(Value::Enum { value, type_name }),
280 SerdeValue::Composite { type_name, fields } => {
281 Ok(Value::Composite { type_name, fields })
282 }
283 }
284 }
285}
286
287impl From<bool> for Value {
288 fn from(v: bool) -> Self {
289 Value::Bool(v)
290 }
291}
292
293impl From<i32> for Value {
294 fn from(v: i32) -> Self {
295 Value::I32(v)
296 }
297}
298
299impl From<i64> for Value {
300 fn from(v: i64) -> Self {
301 Value::I64(v)
302 }
303}
304
305impl From<f64> for Value {
306 fn from(v: f64) -> Self {
307 Value::F64(v)
308 }
309}
310
311impl From<f32> for Value {
312 fn from(v: f32) -> Self {
313 Value::F64(v as f64)
314 }
315}
316
317impl From<rust_decimal::Decimal> for Value {
318 fn from(v: rust_decimal::Decimal) -> Self {
319 Value::Decimal(v)
320 }
321}
322
323impl From<chrono::NaiveDateTime> for Value {
324 fn from(v: chrono::NaiveDateTime) -> Self {
325 Value::DateTime(v)
326 }
327}
328
329impl From<uuid::Uuid> for Value {
330 fn from(v: uuid::Uuid) -> Self {
331 Value::Uuid(v)
332 }
333}
334
335impl From<serde_json::Value> for Value {
336 fn from(v: serde_json::Value) -> Self {
337 Value::Json(v)
338 }
339}
340
341impl From<BTreeMap<String, Option<String>>> for Value {
342 fn from(v: BTreeMap<String, Option<String>>) -> Self {
343 Value::Hstore(v)
344 }
345}
346
347impl From<Geometry> for Value {
348 fn from(v: Geometry) -> Self {
349 Value::Geometry(v.into_inner())
350 }
351}
352
353impl From<Geography> for Value {
354 fn from(v: Geography) -> Self {
355 Value::Geography(v.into_inner())
356 }
357}
358
359impl From<Vec<f32>> for Value {
360 fn from(v: Vec<f32>) -> Self {
361 Value::Vector(v)
362 }
363}
364
365impl From<String> for Value {
366 fn from(v: String) -> Self {
367 Value::String(v)
368 }
369}
370
371impl From<&str> for Value {
372 fn from(v: &str) -> Self {
373 Value::String(v.to_string())
374 }
375}
376
377impl From<Vec<u8>> for Value {
378 fn from(v: Vec<u8>) -> Self {
379 Value::Bytes(v)
380 }
381}
382
383macro_rules! impl_vec_from {
387 ($($t:ty),* $(,)?) => {
388 $(
389 impl From<Vec<$t>> for Value {
390 fn from(v: Vec<$t>) -> Self {
391 Value::Array(v.into_iter().map(|x| x.into()).collect())
392 }
393 }
394
395 impl From<Vec<Vec<$t>>> for Value {
396 fn from(v: Vec<Vec<$t>>) -> Self {
397 Value::Array2D(
398 v.into_iter()
399 .map(|row| row.into_iter().map(|x| x.into()).collect())
400 .collect(),
401 )
402 }
403 }
404 )*
405 };
406}
407
408impl_vec_from!(
409 i32,
410 i64,
411 f64,
412 bool,
413 String,
414 Geometry,
415 Geography,
416 BTreeMap<String, Option<String>>,
417 rust_decimal::Decimal,
418 uuid::Uuid,
419 chrono::NaiveDateTime,
420 serde_json::Value,
421);
422
423macro_rules! impl_option_from {
427 ($($t:ty),* $(,)?) => {
428 $(
429 impl From<Option<$t>> for Value {
430 fn from(v: Option<$t>) -> Self {
431 v.map(|x| x.into()).unwrap_or(Value::Null)
432 }
433 }
434 )*
435 };
436}
437
438impl_option_from!(
439 bool,
440 i32,
441 i64,
442 f64,
443 String,
444 Vec<f32>,
445 Geometry,
446 Geography,
447 BTreeMap<String, Option<String>>,
448 rust_decimal::Decimal,
449 uuid::Uuid,
450 chrono::NaiveDateTime,
451);
452
453impl From<Option<&str>> for Value {
454 fn from(v: Option<&str>) -> Self {
455 v.map(|s| Value::String(s.to_string()))
456 .unwrap_or(Value::Null)
457 }
458}
459
460impl Value {
461 pub fn to_json_plain(&self) -> serde_json::Value {
467 match self {
468 Value::Null => serde_json::Value::Null,
469 Value::Bool(v) => serde_json::Value::Bool(*v),
470 Value::I32(v) => serde_json::Value::Number((*v).into()),
471 Value::I64(v) => serde_json::Value::Number((*v).into()),
472 Value::F64(v) => serde_json::Number::from_f64(*v)
473 .map(serde_json::Value::Number)
474 .unwrap_or(serde_json::Value::Null),
475 Value::Decimal(v) => serde_json::Value::String(v.to_string()),
476 Value::DateTime(v) => serde_json::Value::String(format_datetime(*v)),
477 Value::Uuid(v) => serde_json::Value::String(v.to_string()),
478 Value::Json(v) => v.clone(),
479 Value::Hstore(v) => serde_json::Value::Object(
480 v.iter()
481 .map(|(key, value)| {
482 (
483 key.clone(),
484 value
485 .as_ref()
486 .map(|item| serde_json::Value::String(item.clone()))
487 .unwrap_or(serde_json::Value::Null),
488 )
489 })
490 .collect(),
491 ),
492 Value::Geometry(v) | Value::Geography(v) => serde_json::Value::String(v.clone()),
493 Value::Vector(v) => serde_json::Value::Array(
494 v.iter()
495 .map(|item| {
496 serde_json::Number::from_f64(*item as f64)
497 .map(serde_json::Value::Number)
498 .unwrap_or(serde_json::Value::Null)
499 })
500 .collect(),
501 ),
502 Value::String(v) => serde_json::Value::String(v.clone()),
503 Value::Bytes(v) => {
504 use base64::Engine;
505 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(v))
506 }
507 Value::Array(v) => {
508 serde_json::Value::Array(v.iter().map(Value::to_json_plain).collect())
509 }
510 Value::Array2D(v) => serde_json::Value::Array(
511 v.iter()
512 .map(|row| {
513 serde_json::Value::Array(row.iter().map(Value::to_json_plain).collect())
514 })
515 .collect(),
516 ),
517 Value::Enum { value, .. } => serde_json::Value::String(value.clone()),
518 Value::Composite { fields, .. } => {
519 serde_json::Value::Array(fields.iter().map(Value::to_json_plain).collect())
520 }
521 }
522 }
523}
524
525impl Serialize for Value {
526 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
527 where
528 S: Serializer,
529 {
530 SerdeValue::from(self).serialize(serializer)
531 }
532}
533
534impl<'de> Deserialize<'de> for Value {
537 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
538 where
539 D: Deserializer<'de>,
540 {
541 let tagged = SerdeValue::deserialize(deserializer)?;
542 Value::try_from(tagged).map_err(serde::de::Error::custom)
543 }
544}
545
546pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
557 match json {
558 serde_json::Value::Null => Value::Null,
559 serde_json::Value::Bool(b) => Value::Bool(*b),
560 serde_json::Value::Number(n) => {
561 if let Some(i) = n.as_i64() {
562 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
563 Value::I32(i as i32)
564 } else {
565 Value::I64(i)
566 }
567 } else if let Some(f) = n.as_f64() {
568 Value::F64(f)
569 } else {
570 Value::String(n.to_string())
571 }
572 }
573 serde_json::Value::String(s) => Value::String(s.clone()),
574 serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
575 serde_json::Value::Object(_) => Value::Json(json.clone()),
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use core::f64;
582 use std::collections::BTreeMap;
583
584 use super::*;
585
586 #[test]
587 fn test_value_variants() {
588 assert_eq!(Value::Null, Value::Null);
589 assert_eq!(Value::Bool(true), Value::from(true));
590 assert_eq!(Value::I32(42), Value::from(42i32));
591 assert_eq!(Value::I64(42), Value::from(42i64));
592 assert_eq!(Value::F64(2.5), Value::from(2.5f64));
593 assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
594 assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
595
596 use rust_decimal::Decimal;
597 let dec = Decimal::new(12345, 2);
598 assert_eq!(Value::Decimal(dec), Value::from(dec));
599
600 use chrono::NaiveDate;
601 let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
602 .unwrap()
603 .and_hms_opt(12, 0, 0)
604 .unwrap();
605 assert_eq!(Value::DateTime(dt), Value::from(dt));
606
607 use uuid::Uuid;
608 let id = Uuid::nil();
609 assert_eq!(Value::Uuid(id), Value::from(id));
610
611 use serde_json::json;
612 let j = json!({"key": "value"});
613 assert_eq!(Value::Json(j.clone()), Value::from(j));
614
615 let hstore = BTreeMap::from([
616 ("display_name".to_string(), Some("Bob".to_string())),
617 ("nickname".to_string(), None),
618 ]);
619 assert_eq!(Value::Hstore(hstore.clone()), Value::from(hstore));
620
621 assert_eq!(
622 Value::Vector(vec![0.1, 0.2]),
623 Value::from(vec![0.1f32, 0.2])
624 );
625 }
626
627 #[test]
628 fn test_value_to_json_plain_primitives() {
629 assert_eq!(Value::Null.to_json_plain(), serde_json::Value::Null);
630 assert_eq!(
631 Value::Bool(true).to_json_plain(),
632 serde_json::Value::Bool(true)
633 );
634 assert_eq!(Value::I32(42).to_json_plain().as_i64(), Some(42));
635 assert_eq!(
636 Value::I64(9007199254740991).to_json_plain().as_i64(),
637 Some(9007199254740991)
638 );
639 assert_eq!(
640 Value::F64(f64::consts::PI).to_json_plain().as_f64(),
641 Some(f64::consts::PI)
642 );
643 assert_eq!(
644 Value::String("hello world".to_string())
645 .to_json_plain()
646 .as_str(),
647 Some("hello world")
648 );
649 }
650
651 #[test]
652 fn test_value_to_json_plain_special_scalars() {
653 use rust_decimal::Decimal;
654 let dec = Decimal::new(12345, 2);
655 use chrono::NaiveDate;
656 let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
657 .unwrap()
658 .and_hms_opt(10, 30, 45)
659 .unwrap();
660 use uuid::Uuid;
661 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
662 assert_eq!(Value::Decimal(dec).to_json_plain().as_str(), Some("123.45"));
663 assert!(Value::DateTime(dt)
664 .to_json_plain()
665 .as_str()
666 .unwrap()
667 .starts_with("2026-02-18T10:30:45"));
668 assert_eq!(
669 Value::Uuid(id).to_json_plain().as_str(),
670 Some("550e8400-e29b-41d4-a716-446655440000")
671 );
672 assert_eq!(
673 Value::Bytes(vec![72, 101, 108, 108, 111])
674 .to_json_plain()
675 .as_str(),
676 Some("SGVsbG8=")
677 );
678 }
679
680 #[test]
681 fn test_value_to_json_plain_json_and_arrays() {
682 use serde_json::json;
683 let object = json!({"name": "Alice", "age": 30});
684 assert_eq!(Value::Json(object.clone()).to_json_plain(), object);
685
686 let value = Value::Array(vec![
687 Value::String("a".to_string()),
688 Value::String("b".to_string()),
689 Value::String("c".to_string()),
690 ]);
691
692 let json = value.to_json_plain();
693 assert_eq!(json[0].as_str(), Some("a"));
694 assert_eq!(json[1].as_str(), Some("b"));
695 assert_eq!(json[2].as_str(), Some("c"));
696 }
697
698 #[test]
699 fn test_value_to_json_plain_hstore() {
700 let value = Value::Hstore(BTreeMap::from([
701 ("display_name".to_string(), Some("Bob".to_string())),
702 ("nickname".to_string(), None),
703 ]));
704
705 assert_eq!(
706 value.to_json_plain(),
707 serde_json::json!({
708 "display_name": "Bob",
709 "nickname": null
710 })
711 );
712 }
713
714 #[test]
715 fn test_value_to_json_plain_vector() {
716 let json = Value::Vector(vec![1.0, 2.5, 3.25]).to_json_plain();
717 assert_eq!(json, serde_json::json!([1.0, 2.5, 3.25]));
718 }
719
720 #[test]
721 fn test_value_plain_json_array2d_roundtrip_stays_untyped_without_schema() {
722 let value = Value::Array2D(vec![
723 vec![Value::I32(1), Value::I32(2)],
724 vec![Value::I32(3), Value::I32(4)],
725 ]);
726
727 let json = value.to_json_plain();
728 assert_eq!(json[0][0].as_i64(), Some(1));
729 assert_eq!(json[0][1].as_i64(), Some(2));
730 assert_eq!(json[1][0].as_i64(), Some(3));
731 assert_eq!(json[1][1].as_i64(), Some(4));
732
733 let expected = Value::Array(vec![
738 Value::Array(vec![Value::I32(1), Value::I32(2)]),
739 Value::Array(vec![Value::I32(3), Value::I32(4)]),
740 ]);
741 assert_eq!(json_to_value_ref(&json), expected);
742 }
743
744 #[test]
745 fn test_tagged_serde_shape_is_explicit() {
746 let value = Value::Decimal(rust_decimal::Decimal::new(12345, 2));
747 let json = serde_json::to_value(&value).unwrap();
748
749 assert_eq!(
750 json,
751 serde_json::json!({
752 "type": "decimal",
753 "value": "123.45"
754 })
755 );
756 }
757
758 #[test]
759 fn test_tagged_serde_round_trip_preserves_typed_variants() {
760 use chrono::NaiveDate;
761 use serde_json::json;
762 use uuid::Uuid;
763
764 let values = vec![
765 Value::Null,
766 Value::Bool(false),
767 Value::I32(-42),
768 Value::I64(9007199254740991), Value::F64(f64::consts::E),
770 Value::Decimal(rust_decimal::Decimal::new(314, 2)),
771 Value::DateTime(
772 NaiveDate::from_ymd_opt(2026, 2, 18)
773 .unwrap()
774 .and_hms_opt(10, 30, 45)
775 .unwrap(),
776 ),
777 Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
778 Value::Bytes(vec![1, 2, 3, 4]),
779 Value::Json(json!({"ok": true})),
780 Value::Hstore(BTreeMap::from([
781 ("display_name".to_string(), Some("Bob".to_string())),
782 ("nickname".to_string(), None),
783 ])),
784 Value::Vector(vec![1.0, 2.0, 3.5]),
785 Value::String("test".to_string()),
786 Value::Array(vec![Value::I32(1), Value::I32(2)]),
787 Value::Array2D(vec![vec![Value::I32(1), Value::I32(2)]]),
788 Value::Enum {
789 value: "ADMIN".to_string(),
790 type_name: "role".to_string(),
791 },
792 ];
793
794 for value in values {
795 let json = serde_json::to_value(&value).unwrap();
796 let deserialized: Value = serde_json::from_value(json).unwrap();
797 assert_eq!(deserialized, value);
798 }
799 }
800}