1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::datetime::{NdbDateTime, NdbDuration};
11use crate::geometry::Geometry;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
22#[serde(untagged)]
23pub enum Value {
24 #[default]
25 Null,
27 Bool(bool),
29 Integer(i64),
31 Float(f64),
33 String(String),
35 Bytes(Vec<u8>),
37 Array(Vec<Value>),
39 Object(HashMap<String, Value>),
41 Uuid(String),
43 Ulid(String),
45 DateTime(NdbDateTime),
47 Duration(NdbDuration),
49 Decimal(rust_decimal::Decimal),
51 Geometry(Geometry),
53 Set(Vec<Value>),
55 Regex(String),
57 Range {
59 start: Option<Box<Value>>,
61 end: Option<Box<Value>>,
63 inclusive: bool,
65 },
66 Record {
68 table: String,
70 id: String,
72 },
73}
74
75impl Value {
76 pub fn is_null(&self) -> bool {
78 matches!(self, Value::Null)
79 }
80
81 pub fn as_str(&self) -> Option<&str> {
83 match self {
84 Value::String(s) => Some(s),
85 _ => None,
86 }
87 }
88
89 pub fn as_i64(&self) -> Option<i64> {
91 match self {
92 Value::Integer(i) => Some(*i),
93 _ => None,
94 }
95 }
96
97 pub fn as_f64(&self) -> Option<f64> {
99 match self {
100 Value::Float(f) => Some(*f),
101 Value::Integer(i) => Some(*i as f64),
102 _ => None,
103 }
104 }
105
106 pub fn as_bool(&self) -> Option<bool> {
108 match self {
109 Value::Bool(b) => Some(*b),
110 _ => None,
111 }
112 }
113
114 pub fn as_bytes(&self) -> Option<&[u8]> {
116 match self {
117 Value::Bytes(b) => Some(b),
118 _ => None,
119 }
120 }
121
122 pub fn as_uuid(&self) -> Option<&str> {
124 match self {
125 Value::Uuid(s) => Some(s),
126 _ => None,
127 }
128 }
129
130 pub fn as_ulid(&self) -> Option<&str> {
132 match self {
133 Value::Ulid(s) => Some(s),
134 _ => None,
135 }
136 }
137
138 pub fn as_datetime(&self) -> Option<&NdbDateTime> {
140 match self {
141 Value::DateTime(dt) => Some(dt),
142 _ => None,
143 }
144 }
145
146 pub fn as_duration(&self) -> Option<&NdbDuration> {
148 match self {
149 Value::Duration(d) => Some(d),
150 _ => None,
151 }
152 }
153
154 pub fn as_decimal(&self) -> Option<&rust_decimal::Decimal> {
156 match self {
157 Value::Decimal(d) => Some(d),
158 _ => None,
159 }
160 }
161
162 pub fn as_geometry(&self) -> Option<&Geometry> {
164 match self {
165 Value::Geometry(g) => Some(g),
166 _ => None,
167 }
168 }
169
170 pub fn as_set(&self) -> Option<&[Value]> {
172 match self {
173 Value::Set(s) => Some(s),
174 _ => None,
175 }
176 }
177
178 pub fn as_regex(&self) -> Option<&str> {
180 match self {
181 Value::Regex(r) => Some(r),
182 _ => None,
183 }
184 }
185
186 pub fn as_record(&self) -> Option<(&str, &str)> {
188 match self {
189 Value::Record { table, id } => Some((table, id)),
190 _ => None,
191 }
192 }
193
194 pub fn type_name(&self) -> &'static str {
196 match self {
197 Value::Null => "null",
198 Value::Bool(_) => "bool",
199 Value::Integer(_) => "int",
200 Value::Float(_) => "float",
201 Value::String(_) => "string",
202 Value::Bytes(_) => "bytes",
203 Value::Array(_) => "array",
204 Value::Object(_) => "object",
205 Value::Uuid(_) => "uuid",
206 Value::Ulid(_) => "ulid",
207 Value::DateTime(_) => "datetime",
208 Value::Duration(_) => "duration",
209 Value::Decimal(_) => "decimal",
210 Value::Geometry(_) => "geometry",
211 Value::Set(_) => "set",
212 Value::Regex(_) => "regex",
213 Value::Range { .. } => "range",
214 Value::Record { .. } => "record",
215 }
216 }
217}
218
219impl Value {
221 pub fn get(&self, field: &str) -> Option<&Value> {
223 match self {
224 Value::Object(map) => map.get(field),
225 _ => None,
226 }
227 }
228
229 pub fn get_mut(&mut self, field: &str) -> Option<&mut Value> {
231 match self {
232 Value::Object(map) => map.get_mut(field),
233 _ => None,
234 }
235 }
236
237 pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
239 match self {
240 Value::Object(map) => Some(map),
241 _ => None,
242 }
243 }
244
245 pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, Value>> {
247 match self {
248 Value::Object(map) => Some(map),
249 _ => None,
250 }
251 }
252
253 pub fn as_array(&self) -> Option<&[Value]> {
255 match self {
256 Value::Array(arr) => Some(arr),
257 Value::Set(arr) => Some(arr),
258 _ => None,
259 }
260 }
261}
262
263impl Value {
264 pub fn eq_coerced(&self, other: &Value) -> bool {
269 match (self, other) {
270 (Value::Null, Value::Null) => true,
271 (Value::Bool(a), Value::Bool(b)) => a == b,
272 (Value::Integer(a), Value::Integer(b)) => a == b,
273 (Value::Integer(a), Value::Float(b)) => *a as f64 == *b,
274 (Value::Float(a), Value::Integer(b)) => *a == *b as f64,
275 (Value::Float(a), Value::Float(b)) => a == b,
276 (Value::String(a), Value::String(b)) => a == b,
277 (Value::Integer(a), Value::String(s)) => {
279 s.parse::<i64>().is_ok_and(|n| *a == n)
280 || s.parse::<f64>().is_ok_and(|n| *a as f64 == n)
281 }
282 (Value::String(s), Value::Integer(b)) => {
283 s.parse::<i64>().is_ok_and(|n| n == *b)
284 || s.parse::<f64>().is_ok_and(|n| n == *b as f64)
285 }
286 (Value::Float(a), Value::String(s)) => s.parse::<f64>().is_ok_and(|n| *a == n),
287 (Value::String(s), Value::Float(b)) => s.parse::<f64>().is_ok_and(|n| n == *b),
288 _ => false,
289 }
290 }
291
292 pub fn cmp_coerced(&self, other: &Value) -> std::cmp::Ordering {
296 use std::cmp::Ordering;
297 let self_f64 = match self {
298 Value::Integer(i) => Some(*i as f64),
299 Value::Float(f) => Some(*f),
300 Value::String(s) => s.parse::<f64>().ok(),
301 _ => None,
302 };
303 let other_f64 = match other {
304 Value::Integer(i) => Some(*i as f64),
305 Value::Float(f) => Some(*f),
306 Value::String(s) => s.parse::<f64>().ok(),
307 _ => None,
308 };
309 if let (Some(a), Some(b)) = (self_f64, other_f64) {
310 return a.partial_cmp(&b).unwrap_or(Ordering::Equal);
311 }
312 let a_str = match self {
313 Value::String(s) => s.as_str(),
314 _ => return Ordering::Equal,
315 };
316 let b_str = match other {
317 Value::String(s) => s.as_str(),
318 _ => return Ordering::Equal,
319 };
320 a_str.cmp(b_str)
321 }
322
323 pub fn as_array_iter(&self) -> Option<impl Iterator<Item = &Value>> {
325 match self {
326 Value::Array(arr) | Value::Set(arr) => Some(arr.iter()),
327 _ => None,
328 }
329 }
330}
331
332impl From<&str> for Value {
334 fn from(s: &str) -> Self {
335 Value::String(s.to_owned())
336 }
337}
338
339impl From<String> for Value {
340 fn from(s: String) -> Self {
341 Value::String(s)
342 }
343}
344
345impl From<i64> for Value {
346 fn from(i: i64) -> Self {
347 Value::Integer(i)
348 }
349}
350
351impl From<f64> for Value {
352 fn from(f: f64) -> Self {
353 Value::Float(f)
354 }
355}
356
357impl From<bool> for Value {
358 fn from(b: bool) -> Self {
359 Value::Bool(b)
360 }
361}
362
363impl From<Vec<u8>> for Value {
364 fn from(b: Vec<u8>) -> Self {
365 Value::Bytes(b)
366 }
367}
368
369impl From<NdbDateTime> for Value {
370 fn from(dt: NdbDateTime) -> Self {
371 Value::DateTime(dt)
372 }
373}
374
375impl From<NdbDuration> for Value {
376 fn from(d: NdbDuration) -> Self {
377 Value::Duration(d)
378 }
379}
380
381impl From<rust_decimal::Decimal> for Value {
382 fn from(d: rust_decimal::Decimal) -> Self {
383 Value::Decimal(d)
384 }
385}
386
387impl From<Geometry> for Value {
388 fn from(g: Geometry) -> Self {
389 Value::Geometry(g)
390 }
391}
392
393impl From<Value> for serde_json::Value {
394 fn from(v: Value) -> Self {
395 match v {
396 Value::Null => serde_json::Value::Null,
397 Value::Bool(b) => serde_json::Value::Bool(b),
398 Value::Integer(i) => serde_json::json!(i),
399 Value::Float(f) => serde_json::json!(f),
400 Value::String(s) | Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => {
401 serde_json::Value::String(s)
402 }
403 Value::Bytes(b) => {
404 let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
405 serde_json::Value::String(hex)
406 }
407 Value::Array(arr) | Value::Set(arr) => {
408 serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
409 }
410 Value::Object(map) => serde_json::Value::Object(
411 map.into_iter()
412 .map(|(k, v)| (k, serde_json::Value::from(v)))
413 .collect(),
414 ),
415 Value::DateTime(dt) => serde_json::Value::String(dt.to_string()),
416 Value::Duration(d) => serde_json::Value::String(d.to_string()),
417 Value::Decimal(d) => serde_json::Value::String(d.to_string()),
418 Value::Geometry(g) => serde_json::to_value(g).unwrap_or(serde_json::Value::Null),
419 Value::Range { .. } | Value::Record { .. } => serde_json::Value::Null,
420 }
421 }
422}
423
424impl From<serde_json::Value> for Value {
425 fn from(v: serde_json::Value) -> Self {
426 match v {
427 serde_json::Value::Null => Value::Null,
428 serde_json::Value::Bool(b) => Value::Bool(b),
429 serde_json::Value::Number(n) => {
430 if let Some(i) = n.as_i64() {
431 Value::Integer(i)
432 } else if let Some(u) = n.as_u64() {
433 Value::Integer(u as i64)
434 } else if let Some(f) = n.as_f64() {
435 Value::Float(f)
436 } else {
437 Value::Null
438 }
439 }
440 serde_json::Value::String(s) => Value::String(s),
441 serde_json::Value::Array(arr) => {
442 Value::Array(arr.into_iter().map(Value::from).collect())
443 }
444 serde_json::Value::Object(map) => {
445 Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
446 }
447 }
448 }
449}
450
451impl Value {
452 pub fn to_sql_literal(&self) -> String {
454 match self {
455 Value::Null => "NULL".into(),
456 Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.into(),
457 Value::Integer(i) => i.to_string(),
458 Value::Float(f) => f.to_string(),
459 Value::String(s) => format!("'{}'", s.replace('\'', "''")),
460 Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => {
461 format!("'{}'", s.replace('\'', "''"))
462 }
463 Value::Bytes(b) => {
464 let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
465 format!("'\\x{hex}'")
466 }
467 Value::Array(arr) | Value::Set(arr) => {
468 let elements: Vec<String> = arr.iter().map(|v| v.to_sql_literal()).collect();
469 format!("ARRAY[{}]", elements.join(", "))
470 }
471 Value::Object(map) => {
472 let json_str = serde_json::to_string(&serde_json::Value::Object(
473 map.iter()
474 .map(|(k, v)| (k.clone(), value_to_json(v)))
475 .collect(),
476 ))
477 .unwrap_or_default();
478 format!("'{}'", json_str.replace('\'', "''"))
479 }
480 Value::DateTime(dt) => format!("'{dt}'"),
481 Value::Duration(d) => format!("'{d}'"),
482 Value::Decimal(d) => d.to_string(),
483 Value::Geometry(g) => format!("'{}'", serde_json::to_string(g).unwrap_or_default()),
484 Value::Range { .. } | Value::Record { .. } => "NULL".into(),
485 }
486 }
487}
488
489fn value_to_json(v: &Value) -> serde_json::Value {
491 match v {
492 Value::Null => serde_json::Value::Null,
493 Value::Bool(b) => serde_json::Value::Bool(*b),
494 Value::Integer(i) => serde_json::json!(*i),
495 Value::Float(f) => serde_json::json!(*f),
496 Value::String(s) => serde_json::Value::String(s.clone()),
497 Value::Array(arr) | Value::Set(arr) => {
498 serde_json::Value::Array(arr.iter().map(value_to_json).collect())
499 }
500 Value::Object(map) => serde_json::Value::Object(
501 map.iter()
502 .map(|(k, v)| (k.clone(), value_to_json(v)))
503 .collect(),
504 ),
505 other => serde_json::Value::String(other.to_sql_literal()),
506 }
507}
508
509impl zerompk::ToMessagePack for Value {
515 fn write<W: zerompk::Write>(&self, writer: &mut W) -> zerompk::Result<()> {
516 match self {
517 Value::Null => {
518 writer.write_array_len(1)?;
519 writer.write_u8(0)
520 }
521 Value::Bool(b) => {
522 writer.write_array_len(2)?;
523 writer.write_u8(1)?;
524 writer.write_boolean(*b)
525 }
526 Value::Integer(i) => {
527 writer.write_array_len(2)?;
528 writer.write_u8(2)?;
529 writer.write_i64(*i)
530 }
531 Value::Float(f) => {
532 writer.write_array_len(2)?;
533 writer.write_u8(3)?;
534 writer.write_f64(*f)
535 }
536 Value::String(s) => {
537 writer.write_array_len(2)?;
538 writer.write_u8(4)?;
539 writer.write_string(s)
540 }
541 Value::Bytes(b) => {
542 writer.write_array_len(2)?;
543 writer.write_u8(5)?;
544 writer.write_binary(b)
545 }
546 Value::Array(arr) => {
547 writer.write_array_len(2)?;
548 writer.write_u8(6)?;
549 arr.write(writer)
550 }
551 Value::Object(map) => {
552 writer.write_array_len(2)?;
553 writer.write_u8(7)?;
554 map.write(writer)
555 }
556 Value::Uuid(s) => {
557 writer.write_array_len(2)?;
558 writer.write_u8(8)?;
559 writer.write_string(s)
560 }
561 Value::Ulid(s) => {
562 writer.write_array_len(2)?;
563 writer.write_u8(9)?;
564 writer.write_string(s)
565 }
566 Value::DateTime(dt) => {
567 writer.write_array_len(2)?;
568 writer.write_u8(10)?;
569 dt.write(writer)
570 }
571 Value::Duration(d) => {
572 writer.write_array_len(2)?;
573 writer.write_u8(11)?;
574 d.write(writer)
575 }
576 Value::Decimal(d) => {
577 writer.write_array_len(2)?;
578 writer.write_u8(12)?;
579 writer.write_binary(&d.serialize())
580 }
581 Value::Geometry(g) => {
582 writer.write_array_len(2)?;
583 writer.write_u8(13)?;
584 g.write(writer)
585 }
586 Value::Set(s) => {
587 writer.write_array_len(2)?;
588 writer.write_u8(14)?;
589 s.write(writer)
590 }
591 Value::Regex(r) => {
592 writer.write_array_len(2)?;
593 writer.write_u8(15)?;
594 writer.write_string(r)
595 }
596 Value::Range {
597 start,
598 end,
599 inclusive,
600 } => {
601 writer.write_array_len(4)?;
602 writer.write_u8(16)?;
603 start.write(writer)?;
604 end.write(writer)?;
605 writer.write_boolean(*inclusive)
606 }
607 Value::Record { table, id } => {
608 writer.write_array_len(3)?;
609 writer.write_u8(17)?;
610 writer.write_string(table)?;
611 writer.write_string(id)
612 }
613 }
614 }
615}
616
617impl<'a> zerompk::FromMessagePack<'a> for Value {
618 fn read<R: zerompk::Read<'a>>(reader: &mut R) -> zerompk::Result<Self> {
619 let len = reader.read_array_len()?;
620 if len == 0 {
621 return Err(zerompk::Error::ArrayLengthMismatch {
622 expected: 1,
623 actual: 0,
624 });
625 }
626 let tag = reader.read_u8()?;
627 match tag {
628 0 => Ok(Value::Null),
629 1 => Ok(Value::Bool(reader.read_boolean()?)),
630 2 => Ok(Value::Integer(reader.read_i64()?)),
631 3 => Ok(Value::Float(reader.read_f64()?)),
632 4 => Ok(Value::String(reader.read_string()?.into_owned())),
633 5 => Ok(Value::Bytes(reader.read_binary()?.into_owned())),
634 6 => Ok(Value::Array(Vec::<Value>::read(reader)?)),
635 7 => Ok(Value::Object(HashMap::<String, Value>::read(reader)?)),
636 8 => Ok(Value::Uuid(reader.read_string()?.into_owned())),
637 9 => Ok(Value::Ulid(reader.read_string()?.into_owned())),
638 10 => Ok(Value::DateTime(NdbDateTime::read(reader)?)),
639 11 => Ok(Value::Duration(NdbDuration::read(reader)?)),
640 12 => {
641 let cow = reader.read_binary()?;
642 if cow.len() != 16 {
643 return Err(zerompk::Error::BufferTooSmall);
644 }
645 let mut buf = [0u8; 16];
646 buf.copy_from_slice(&cow);
647 Ok(Value::Decimal(rust_decimal::Decimal::deserialize(buf)))
648 }
649 13 => Ok(Value::Geometry(Geometry::read(reader)?)),
650 14 => Ok(Value::Set(Vec::<Value>::read(reader)?)),
651 15 => Ok(Value::Regex(reader.read_string()?.into_owned())),
652 16 => {
653 let start = Option::<Box<Value>>::read(reader)?;
654 let end = Option::<Box<Value>>::read(reader)?;
655 let inclusive = reader.read_boolean()?;
656 Ok(Value::Range {
657 start,
658 end,
659 inclusive,
660 })
661 }
662 17 => {
663 let table = reader.read_string()?.into_owned();
664 let id = reader.read_string()?.into_owned();
665 Ok(Value::Record { table, id })
666 }
667 _ => Err(zerompk::Error::InvalidMarker(tag)),
668 }
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[test]
677 fn value_type_checks() {
678 assert!(Value::Null.is_null());
679 assert!(!Value::Bool(true).is_null());
680
681 assert_eq!(Value::String("hi".into()).as_str(), Some("hi"));
682 assert_eq!(Value::Integer(42).as_i64(), Some(42));
683 assert_eq!(Value::Float(2.78).as_f64(), Some(2.78));
684 assert_eq!(Value::Integer(10).as_f64(), Some(10.0));
685 assert_eq!(Value::Bool(true).as_bool(), Some(true));
686 assert_eq!(Value::Bytes(vec![1, 2]).as_bytes(), Some(&[1, 2][..]));
687 }
688
689 #[test]
690 fn from_conversions() {
691 let s: Value = "hello".into();
692 assert_eq!(s.as_str(), Some("hello"));
693
694 let i: Value = 42i64.into();
695 assert_eq!(i.as_i64(), Some(42));
696
697 let f: Value = 2.78f64.into();
698 assert_eq!(f.as_f64(), Some(2.78));
699 }
700
701 #[test]
702 fn nested_value() {
703 let nested = Value::Object({
704 let mut m = HashMap::new();
705 m.insert(
706 "inner".into(),
707 Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
708 );
709 m
710 });
711 assert!(!nested.is_null());
712 }
713
714 #[test]
717 fn eq_coerced_same_type() {
718 assert!(Value::Null.eq_coerced(&Value::Null));
719 assert!(Value::Bool(true).eq_coerced(&Value::Bool(true)));
720 assert!(!Value::Bool(true).eq_coerced(&Value::Bool(false)));
721 assert!(Value::Integer(42).eq_coerced(&Value::Integer(42)));
722 assert!(Value::Float(2.78).eq_coerced(&Value::Float(2.78)));
723 assert!(Value::String("hello".into()).eq_coerced(&Value::String("hello".into())));
724 }
725
726 #[test]
727 fn eq_coerced_int_float() {
728 assert!(Value::Integer(5).eq_coerced(&Value::Float(5.0)));
729 assert!(Value::Float(5.0).eq_coerced(&Value::Integer(5)));
730 assert!(!Value::Integer(5).eq_coerced(&Value::Float(5.1)));
731 }
732
733 #[test]
734 fn eq_coerced_string_number() {
735 assert!(Value::String("5".into()).eq_coerced(&Value::Integer(5)));
737 assert!(Value::Integer(5).eq_coerced(&Value::String("5".into())));
738 assert!(Value::String("2.78".into()).eq_coerced(&Value::Float(2.78)));
740 assert!(Value::Float(2.78).eq_coerced(&Value::String("2.78".into())));
741 assert!(!Value::String("abc".into()).eq_coerced(&Value::Integer(5)));
743 assert!(!Value::Integer(5).eq_coerced(&Value::String("abc".into())));
744 }
745
746 #[test]
747 fn eq_coerced_cross_type_false() {
748 assert!(!Value::Bool(true).eq_coerced(&Value::Integer(1)));
750 assert!(!Value::Null.eq_coerced(&Value::Integer(0)));
752 assert!(!Value::Null.eq_coerced(&Value::String("".into())));
753 }
754
755 #[test]
756 fn cmp_coerced_numeric() {
757 use std::cmp::Ordering;
758 assert_eq!(
759 Value::Integer(5).cmp_coerced(&Value::Integer(10)),
760 Ordering::Less
761 );
762 assert_eq!(
763 Value::Integer(10).cmp_coerced(&Value::Float(5.0)),
764 Ordering::Greater
765 );
766 assert_eq!(
767 Value::String("90".into()).cmp_coerced(&Value::Integer(80)),
768 Ordering::Greater
769 );
770 assert_eq!(
771 Value::Float(2.78).cmp_coerced(&Value::String("2.78".into())),
772 Ordering::Equal
773 );
774 }
775
776 #[test]
777 fn cmp_coerced_string_fallback() {
778 use std::cmp::Ordering;
779 assert_eq!(
780 Value::String("abc".into()).cmp_coerced(&Value::String("def".into())),
781 Ordering::Less
782 );
783 assert_eq!(
784 Value::String("z".into()).cmp_coerced(&Value::String("a".into())),
785 Ordering::Greater
786 );
787 }
788
789 #[test]
790 fn eq_coerced_symmetry() {
791 let cases = [
793 (Value::Integer(42), Value::String("42".into())),
794 (Value::Float(2.78), Value::String("2.78".into())),
795 (Value::Integer(5), Value::Float(5.0)),
796 ];
797 for (a, b) in &cases {
798 assert_eq!(
799 a.eq_coerced(b),
800 b.eq_coerced(a),
801 "symmetry violated for {a:?} vs {b:?}"
802 );
803 }
804 }
805}