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