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}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[serde(tag = "type", content = "value", rename_all = "snake_case")]
159enum SerdeValue {
160 Null,
161 Bool(bool),
162 I32(i32),
163 I64(i64),
164 F64(f64),
165 Decimal(String),
166 DateTime(String),
167 Uuid(String),
168 Json(serde_json::Value),
169 Hstore(BTreeMap<String, Option<String>>),
170 Geometry(String),
171 Geography(String),
172 Vector(Vec<f32>),
173 String(String),
174 Bytes(String),
175 Array(Vec<Value>),
176 Array2D(Vec<Vec<Value>>),
177 Enum { value: String, type_name: String },
178}
179
180fn format_datetime(value: chrono::NaiveDateTime) -> String {
181 value.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string()
182}
183
184fn parse_datetime_string(raw: &str) -> std::result::Result<chrono::NaiveDateTime, String> {
185 chrono::DateTime::parse_from_rfc3339(raw)
186 .map(|value| value.naive_utc())
187 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f"))
188 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%d %H:%M:%S%.f"))
189 .map_err(|_| format!("invalid datetime '{}'", raw))
190}
191
192impl From<&Value> for SerdeValue {
193 fn from(value: &Value) -> Self {
194 match value {
195 Value::Null => SerdeValue::Null,
196 Value::Bool(v) => SerdeValue::Bool(*v),
197 Value::I32(v) => SerdeValue::I32(*v),
198 Value::I64(v) => SerdeValue::I64(*v),
199 Value::F64(v) => SerdeValue::F64(*v),
200 Value::Decimal(v) => SerdeValue::Decimal(v.to_string()),
201 Value::DateTime(v) => SerdeValue::DateTime(format_datetime(*v)),
202 Value::Uuid(v) => SerdeValue::Uuid(v.to_string()),
203 Value::Json(v) => SerdeValue::Json(v.clone()),
204 Value::Hstore(v) => SerdeValue::Hstore(v.clone()),
205 Value::Geometry(v) => SerdeValue::Geometry(v.clone()),
206 Value::Geography(v) => SerdeValue::Geography(v.clone()),
207 Value::Vector(v) => SerdeValue::Vector(v.clone()),
208 Value::String(v) => SerdeValue::String(v.clone()),
209 Value::Bytes(v) => {
210 use base64::Engine;
211 SerdeValue::Bytes(base64::engine::general_purpose::STANDARD.encode(v))
212 }
213 Value::Array(v) => SerdeValue::Array(v.clone()),
214 Value::Array2D(v) => SerdeValue::Array2D(v.clone()),
215 Value::Enum { value, type_name } => SerdeValue::Enum {
216 value: value.clone(),
217 type_name: type_name.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 }
257 }
258}
259
260impl From<bool> for Value {
261 fn from(v: bool) -> Self {
262 Value::Bool(v)
263 }
264}
265
266impl From<i32> for Value {
267 fn from(v: i32) -> Self {
268 Value::I32(v)
269 }
270}
271
272impl From<i64> for Value {
273 fn from(v: i64) -> Self {
274 Value::I64(v)
275 }
276}
277
278impl From<f64> for Value {
279 fn from(v: f64) -> Self {
280 Value::F64(v)
281 }
282}
283
284impl From<f32> for Value {
285 fn from(v: f32) -> Self {
286 Value::F64(v as f64)
287 }
288}
289
290impl From<rust_decimal::Decimal> for Value {
291 fn from(v: rust_decimal::Decimal) -> Self {
292 Value::Decimal(v)
293 }
294}
295
296impl From<chrono::NaiveDateTime> for Value {
297 fn from(v: chrono::NaiveDateTime) -> Self {
298 Value::DateTime(v)
299 }
300}
301
302impl From<uuid::Uuid> for Value {
303 fn from(v: uuid::Uuid) -> Self {
304 Value::Uuid(v)
305 }
306}
307
308impl From<serde_json::Value> for Value {
309 fn from(v: serde_json::Value) -> Self {
310 Value::Json(v)
311 }
312}
313
314impl From<BTreeMap<String, Option<String>>> for Value {
315 fn from(v: BTreeMap<String, Option<String>>) -> Self {
316 Value::Hstore(v)
317 }
318}
319
320impl From<Geometry> for Value {
321 fn from(v: Geometry) -> Self {
322 Value::Geometry(v.into_inner())
323 }
324}
325
326impl From<Geography> for Value {
327 fn from(v: Geography) -> Self {
328 Value::Geography(v.into_inner())
329 }
330}
331
332impl From<Vec<f32>> for Value {
333 fn from(v: Vec<f32>) -> Self {
334 Value::Vector(v)
335 }
336}
337
338impl From<String> for Value {
339 fn from(v: String) -> Self {
340 Value::String(v)
341 }
342}
343
344impl From<&str> for Value {
345 fn from(v: &str) -> Self {
346 Value::String(v.to_string())
347 }
348}
349
350impl From<Vec<u8>> for Value {
351 fn from(v: Vec<u8>) -> Self {
352 Value::Bytes(v)
353 }
354}
355
356macro_rules! impl_vec_from {
360 ($($t:ty),* $(,)?) => {
361 $(
362 impl From<Vec<$t>> for Value {
363 fn from(v: Vec<$t>) -> Self {
364 Value::Array(v.into_iter().map(|x| x.into()).collect())
365 }
366 }
367
368 impl From<Vec<Vec<$t>>> for Value {
369 fn from(v: Vec<Vec<$t>>) -> Self {
370 Value::Array2D(
371 v.into_iter()
372 .map(|row| row.into_iter().map(|x| x.into()).collect())
373 .collect(),
374 )
375 }
376 }
377 )*
378 };
379}
380
381impl_vec_from!(
382 i32,
383 i64,
384 f64,
385 bool,
386 String,
387 Geometry,
388 Geography,
389 BTreeMap<String, Option<String>>,
390 rust_decimal::Decimal,
391 uuid::Uuid,
392 chrono::NaiveDateTime,
393 serde_json::Value,
394);
395
396macro_rules! impl_option_from {
400 ($($t:ty),* $(,)?) => {
401 $(
402 impl From<Option<$t>> for Value {
403 fn from(v: Option<$t>) -> Self {
404 v.map(|x| x.into()).unwrap_or(Value::Null)
405 }
406 }
407 )*
408 };
409}
410
411impl_option_from!(
412 bool,
413 i32,
414 i64,
415 f64,
416 String,
417 Vec<f32>,
418 Geometry,
419 Geography,
420 BTreeMap<String, Option<String>>,
421 rust_decimal::Decimal,
422 uuid::Uuid,
423 chrono::NaiveDateTime,
424);
425
426impl From<Option<&str>> for Value {
427 fn from(v: Option<&str>) -> Self {
428 v.map(|s| Value::String(s.to_string()))
429 .unwrap_or(Value::Null)
430 }
431}
432
433impl Value {
434 pub fn to_json_plain(&self) -> serde_json::Value {
440 match self {
441 Value::Null => serde_json::Value::Null,
442 Value::Bool(v) => serde_json::Value::Bool(*v),
443 Value::I32(v) => serde_json::Value::Number((*v).into()),
444 Value::I64(v) => serde_json::Value::Number((*v).into()),
445 Value::F64(v) => serde_json::Number::from_f64(*v)
446 .map(serde_json::Value::Number)
447 .unwrap_or(serde_json::Value::Null),
448 Value::Decimal(v) => serde_json::Value::String(v.to_string()),
449 Value::DateTime(v) => serde_json::Value::String(format_datetime(*v)),
450 Value::Uuid(v) => serde_json::Value::String(v.to_string()),
451 Value::Json(v) => v.clone(),
452 Value::Hstore(v) => serde_json::Value::Object(
453 v.iter()
454 .map(|(key, value)| {
455 (
456 key.clone(),
457 value
458 .as_ref()
459 .map(|item| serde_json::Value::String(item.clone()))
460 .unwrap_or(serde_json::Value::Null),
461 )
462 })
463 .collect(),
464 ),
465 Value::Geometry(v) | Value::Geography(v) => serde_json::Value::String(v.clone()),
466 Value::Vector(v) => serde_json::Value::Array(
467 v.iter()
468 .map(|item| {
469 serde_json::Number::from_f64(*item as f64)
470 .map(serde_json::Value::Number)
471 .unwrap_or(serde_json::Value::Null)
472 })
473 .collect(),
474 ),
475 Value::String(v) => serde_json::Value::String(v.clone()),
476 Value::Bytes(v) => {
477 use base64::Engine;
478 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(v))
479 }
480 Value::Array(v) => {
481 serde_json::Value::Array(v.iter().map(Value::to_json_plain).collect())
482 }
483 Value::Array2D(v) => serde_json::Value::Array(
484 v.iter()
485 .map(|row| {
486 serde_json::Value::Array(row.iter().map(Value::to_json_plain).collect())
487 })
488 .collect(),
489 ),
490 Value::Enum { value, .. } => serde_json::Value::String(value.clone()),
491 }
492 }
493}
494
495impl Serialize for Value {
496 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
497 where
498 S: Serializer,
499 {
500 SerdeValue::from(self).serialize(serializer)
501 }
502}
503
504impl<'de> Deserialize<'de> for Value {
507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508 where
509 D: Deserializer<'de>,
510 {
511 let tagged = SerdeValue::deserialize(deserializer)?;
512 Value::try_from(tagged).map_err(serde::de::Error::custom)
513 }
514}
515
516pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
527 match json {
528 serde_json::Value::Null => Value::Null,
529 serde_json::Value::Bool(b) => Value::Bool(*b),
530 serde_json::Value::Number(n) => {
531 if let Some(i) = n.as_i64() {
532 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
533 Value::I32(i as i32)
534 } else {
535 Value::I64(i)
536 }
537 } else if let Some(f) = n.as_f64() {
538 Value::F64(f)
539 } else {
540 Value::String(n.to_string())
541 }
542 }
543 serde_json::Value::String(s) => Value::String(s.clone()),
544 serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
545 serde_json::Value::Object(_) => Value::Json(json.clone()),
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use core::f64;
552 use std::collections::BTreeMap;
553
554 use super::*;
555
556 #[test]
557 fn test_value_variants() {
558 assert_eq!(Value::Null, Value::Null);
559 assert_eq!(Value::Bool(true), Value::from(true));
560 assert_eq!(Value::I32(42), Value::from(42i32));
561 assert_eq!(Value::I64(42), Value::from(42i64));
562 assert_eq!(Value::F64(2.5), Value::from(2.5f64));
563 assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
564 assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
565
566 use rust_decimal::Decimal;
567 let dec = Decimal::new(12345, 2);
568 assert_eq!(Value::Decimal(dec), Value::from(dec));
569
570 use chrono::NaiveDate;
571 let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
572 .unwrap()
573 .and_hms_opt(12, 0, 0)
574 .unwrap();
575 assert_eq!(Value::DateTime(dt), Value::from(dt));
576
577 use uuid::Uuid;
578 let id = Uuid::nil();
579 assert_eq!(Value::Uuid(id), Value::from(id));
580
581 use serde_json::json;
582 let j = json!({"key": "value"});
583 assert_eq!(Value::Json(j.clone()), Value::from(j));
584
585 let hstore = BTreeMap::from([
586 ("display_name".to_string(), Some("Bob".to_string())),
587 ("nickname".to_string(), None),
588 ]);
589 assert_eq!(Value::Hstore(hstore.clone()), Value::from(hstore));
590
591 assert_eq!(
592 Value::Vector(vec![0.1, 0.2]),
593 Value::from(vec![0.1f32, 0.2])
594 );
595 }
596
597 #[test]
598 fn test_value_to_json_plain_primitives() {
599 assert_eq!(Value::Null.to_json_plain(), serde_json::Value::Null);
600 assert_eq!(
601 Value::Bool(true).to_json_plain(),
602 serde_json::Value::Bool(true)
603 );
604 assert_eq!(Value::I32(42).to_json_plain().as_i64(), Some(42));
605 assert_eq!(
606 Value::I64(9007199254740991).to_json_plain().as_i64(),
607 Some(9007199254740991)
608 );
609 assert_eq!(
610 Value::F64(f64::consts::PI).to_json_plain().as_f64(),
611 Some(f64::consts::PI)
612 );
613 assert_eq!(
614 Value::String("hello world".to_string())
615 .to_json_plain()
616 .as_str(),
617 Some("hello world")
618 );
619 }
620
621 #[test]
622 fn test_value_to_json_plain_special_scalars() {
623 use rust_decimal::Decimal;
624 let dec = Decimal::new(12345, 2);
625 use chrono::NaiveDate;
626 let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
627 .unwrap()
628 .and_hms_opt(10, 30, 45)
629 .unwrap();
630 use uuid::Uuid;
631 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
632 assert_eq!(Value::Decimal(dec).to_json_plain().as_str(), Some("123.45"));
633 assert!(Value::DateTime(dt)
634 .to_json_plain()
635 .as_str()
636 .unwrap()
637 .starts_with("2026-02-18T10:30:45"));
638 assert_eq!(
639 Value::Uuid(id).to_json_plain().as_str(),
640 Some("550e8400-e29b-41d4-a716-446655440000")
641 );
642 assert_eq!(
643 Value::Bytes(vec![72, 101, 108, 108, 111])
644 .to_json_plain()
645 .as_str(),
646 Some("SGVsbG8=")
647 );
648 }
649
650 #[test]
651 fn test_value_to_json_plain_json_and_arrays() {
652 use serde_json::json;
653 let object = json!({"name": "Alice", "age": 30});
654 assert_eq!(Value::Json(object.clone()).to_json_plain(), object);
655
656 let value = Value::Array(vec![
657 Value::String("a".to_string()),
658 Value::String("b".to_string()),
659 Value::String("c".to_string()),
660 ]);
661
662 let json = value.to_json_plain();
663 assert_eq!(json[0].as_str(), Some("a"));
664 assert_eq!(json[1].as_str(), Some("b"));
665 assert_eq!(json[2].as_str(), Some("c"));
666 }
667
668 #[test]
669 fn test_value_to_json_plain_hstore() {
670 let value = Value::Hstore(BTreeMap::from([
671 ("display_name".to_string(), Some("Bob".to_string())),
672 ("nickname".to_string(), None),
673 ]));
674
675 assert_eq!(
676 value.to_json_plain(),
677 serde_json::json!({
678 "display_name": "Bob",
679 "nickname": null
680 })
681 );
682 }
683
684 #[test]
685 fn test_value_to_json_plain_vector() {
686 let json = Value::Vector(vec![1.0, 2.5, 3.25]).to_json_plain();
687 assert_eq!(json, serde_json::json!([1.0, 2.5, 3.25]));
688 }
689
690 #[test]
691 fn test_value_plain_json_array2d_roundtrip_stays_untyped_without_schema() {
692 let value = Value::Array2D(vec![
693 vec![Value::I32(1), Value::I32(2)],
694 vec![Value::I32(3), Value::I32(4)],
695 ]);
696
697 let json = value.to_json_plain();
698 assert_eq!(json[0][0].as_i64(), Some(1));
699 assert_eq!(json[0][1].as_i64(), Some(2));
700 assert_eq!(json[1][0].as_i64(), Some(3));
701 assert_eq!(json[1][1].as_i64(), Some(4));
702
703 let expected = Value::Array(vec![
708 Value::Array(vec![Value::I32(1), Value::I32(2)]),
709 Value::Array(vec![Value::I32(3), Value::I32(4)]),
710 ]);
711 assert_eq!(json_to_value_ref(&json), expected);
712 }
713
714 #[test]
715 fn test_tagged_serde_shape_is_explicit() {
716 let value = Value::Decimal(rust_decimal::Decimal::new(12345, 2));
717 let json = serde_json::to_value(&value).unwrap();
718
719 assert_eq!(
720 json,
721 serde_json::json!({
722 "type": "decimal",
723 "value": "123.45"
724 })
725 );
726 }
727
728 #[test]
729 fn test_tagged_serde_round_trip_preserves_typed_variants() {
730 use chrono::NaiveDate;
731 use serde_json::json;
732 use uuid::Uuid;
733
734 let values = vec![
735 Value::Null,
736 Value::Bool(false),
737 Value::I32(-42),
738 Value::I64(9007199254740991), Value::F64(f64::consts::E),
740 Value::Decimal(rust_decimal::Decimal::new(314, 2)),
741 Value::DateTime(
742 NaiveDate::from_ymd_opt(2026, 2, 18)
743 .unwrap()
744 .and_hms_opt(10, 30, 45)
745 .unwrap(),
746 ),
747 Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
748 Value::Bytes(vec![1, 2, 3, 4]),
749 Value::Json(json!({"ok": true})),
750 Value::Hstore(BTreeMap::from([
751 ("display_name".to_string(), Some("Bob".to_string())),
752 ("nickname".to_string(), None),
753 ])),
754 Value::Vector(vec![1.0, 2.0, 3.5]),
755 Value::String("test".to_string()),
756 Value::Array(vec![Value::I32(1), Value::I32(2)]),
757 Value::Array2D(vec![vec![Value::I32(1), Value::I32(2)]]),
758 Value::Enum {
759 value: "ADMIN".to_string(),
760 type_name: "role".to_string(),
761 },
762 ];
763
764 for value in values {
765 let json = serde_json::to_value(&value).unwrap();
766 let deserialized: Value = serde_json::from_value(json).unwrap();
767 assert_eq!(deserialized, value);
768 }
769 }
770}