1use crate::api::error::UniError;
15use crate::core::id::{Eid, Vid};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::fmt;
19use std::hash::{Hash, Hasher};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum TemporalType {
28 Date,
29 LocalTime,
30 Time,
31 LocalDateTime,
32 DateTime,
33 Duration,
34 Btic,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub enum TemporalValue {
44 Date { days_since_epoch: i32 },
46 LocalTime { nanos_since_midnight: i64 },
48 Time {
50 nanos_since_midnight: i64,
51 offset_seconds: i32,
52 },
53 LocalDateTime { nanos_since_epoch: i64 },
55 DateTime {
58 nanos_since_epoch: i64,
59 offset_seconds: i32,
60 timezone_name: Option<String>,
61 },
62 Duration { months: i64, days: i64, nanos: i64 },
65 Btic { lo: i64, hi: i64, meta: u64 },
68}
69
70impl Eq for TemporalValue {}
71
72impl Hash for TemporalValue {
73 fn hash<H: Hasher>(&self, state: &mut H) {
74 std::mem::discriminant(self).hash(state);
75 match self {
76 TemporalValue::Date { days_since_epoch } => days_since_epoch.hash(state),
77 TemporalValue::LocalTime {
78 nanos_since_midnight,
79 } => nanos_since_midnight.hash(state),
80 TemporalValue::Time {
81 nanos_since_midnight,
82 offset_seconds,
83 } => {
84 nanos_since_midnight.hash(state);
85 offset_seconds.hash(state);
86 }
87 TemporalValue::LocalDateTime { nanos_since_epoch } => nanos_since_epoch.hash(state),
88 TemporalValue::DateTime {
89 nanos_since_epoch,
90 offset_seconds,
91 timezone_name,
92 } => {
93 nanos_since_epoch.hash(state);
94 offset_seconds.hash(state);
95 timezone_name.hash(state);
96 }
97 TemporalValue::Duration {
98 months,
99 days,
100 nanos,
101 } => {
102 months.hash(state);
103 days.hash(state);
104 nanos.hash(state);
105 }
106 TemporalValue::Btic { lo, hi, meta } => {
107 lo.hash(state);
108 hi.hash(state);
109 meta.hash(state);
110 }
111 }
112 }
113}
114
115impl TemporalValue {
116 pub fn temporal_type(&self) -> TemporalType {
118 match self {
119 TemporalValue::Date { .. } => TemporalType::Date,
120 TemporalValue::LocalTime { .. } => TemporalType::LocalTime,
121 TemporalValue::Time { .. } => TemporalType::Time,
122 TemporalValue::LocalDateTime { .. } => TemporalType::LocalDateTime,
123 TemporalValue::DateTime { .. } => TemporalType::DateTime,
124 TemporalValue::Duration { .. } => TemporalType::Duration,
125 TemporalValue::Btic { .. } => TemporalType::Btic,
126 }
127 }
128
129 pub fn year(&self) -> Option<i64> {
135 self.to_date().map(|d| d.year() as i64)
136 }
137
138 pub fn month(&self) -> Option<i64> {
140 self.to_date().map(|d| d.month() as i64)
141 }
142
143 pub fn day(&self) -> Option<i64> {
145 self.to_date().map(|d| d.day() as i64)
146 }
147
148 pub fn hour(&self) -> Option<i64> {
150 self.to_time().map(|t| t.hour() as i64)
151 }
152
153 pub fn minute(&self) -> Option<i64> {
155 self.to_time().map(|t| t.minute() as i64)
156 }
157
158 pub fn second(&self) -> Option<i64> {
160 self.to_time().map(|t| t.second() as i64)
161 }
162
163 pub fn to_date(&self) -> Option<chrono::NaiveDate> {
169 let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1)?;
170 match self {
171 TemporalValue::Date { days_since_epoch } => {
172 epoch.checked_add_signed(chrono::Duration::days(*days_since_epoch as i64))
173 }
174 TemporalValue::LocalDateTime { nanos_since_epoch } => {
175 let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
176 Some(dt.date_naive())
177 }
178 TemporalValue::DateTime {
179 nanos_since_epoch,
180 offset_seconds,
181 ..
182 } => {
183 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
185 let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
186 Some(dt.date_naive())
187 }
188 _ => None,
189 }
190 }
191
192 pub fn to_time(&self) -> Option<chrono::NaiveTime> {
194 match self {
195 TemporalValue::LocalTime {
196 nanos_since_midnight,
197 }
198 | TemporalValue::Time {
199 nanos_since_midnight,
200 ..
201 } => nanos_to_time(*nanos_since_midnight),
202 TemporalValue::LocalDateTime { nanos_since_epoch } => {
203 let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
204 Some(dt.naive_utc().time())
205 }
206 TemporalValue::DateTime {
207 nanos_since_epoch,
208 offset_seconds,
209 ..
210 } => {
211 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
212 let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
213 Some(dt.naive_utc().time())
214 }
215 _ => None,
216 }
217 }
218}
219
220fn nanos_to_time(nanos: i64) -> Option<chrono::NaiveTime> {
222 let total_secs = nanos / 1_000_000_000;
223 let h = (total_secs / 3600) as u32;
224 let m = ((total_secs % 3600) / 60) as u32;
225 let s = (total_secs % 60) as u32;
226 let ns = (nanos % 1_000_000_000) as u32;
227 chrono::NaiveTime::from_hms_nano_opt(h, m, s, ns)
228}
229
230fn format_offset(offset_seconds: i32) -> String {
232 if offset_seconds == 0 {
233 return "Z".to_string();
234 }
235 format_offset_numeric(offset_seconds)
236}
237
238fn format_offset_numeric(offset_seconds: i32) -> String {
240 let sign = if offset_seconds >= 0 { '+' } else { '-' };
241 let abs = offset_seconds.unsigned_abs();
242 let h = abs / 3600;
243 let m = (abs % 3600) / 60;
244 let s = abs % 60;
245 if s != 0 {
246 format!("{}{:02}:{:02}:{:02}", sign, h, m, s)
247 } else {
248 format!("{}{:02}:{:02}", sign, h, m)
249 }
250}
251
252fn format_fractional(nanos: u32) -> String {
254 if nanos == 0 {
255 return String::new();
256 }
257 let s = format!("{:09}", nanos);
258 let trimmed = s.trim_end_matches('0');
259 format!(".{}", trimmed)
260}
261
262fn format_time_component(hour: u32, minute: u32, second: u32, nanos: u32) -> String {
264 if second == 0 && nanos == 0 {
265 format!("{:02}:{:02}", hour, minute)
266 } else {
267 let frac = format_fractional(nanos);
268 format!("{:02}:{:02}:{:02}{}", hour, minute, second, frac)
269 }
270}
271
272fn format_naive_time(t: &chrono::NaiveTime) -> String {
274 format_time_component(t.hour(), t.minute(), t.second(), t.nanosecond())
275}
276
277fn nanos_to_time_or_midnight(nanos: i64) -> chrono::NaiveTime {
279 nanos_to_time(nanos).unwrap_or_else(|| chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
280}
281
282impl fmt::Display for TemporalValue {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 match self {
285 TemporalValue::Date { days_since_epoch } => {
286 let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
287 let date = epoch
292 .checked_add_signed(chrono::Duration::days(*days_since_epoch as i64))
293 .unwrap_or(if *days_since_epoch >= 0 {
294 chrono::NaiveDate::MAX
295 } else {
296 chrono::NaiveDate::MIN
297 });
298 write!(f, "{}", date.format("%Y-%m-%d"))
299 }
300 TemporalValue::LocalTime {
301 nanos_since_midnight,
302 } => {
303 let time = nanos_to_time_or_midnight(*nanos_since_midnight);
304 write!(f, "{}", format_naive_time(&time))
305 }
306 TemporalValue::Time {
307 nanos_since_midnight,
308 offset_seconds,
309 } => {
310 let time = nanos_to_time_or_midnight(*nanos_since_midnight);
311 write!(
312 f,
313 "{}{}",
314 format_naive_time(&time),
315 format_offset(*offset_seconds)
316 )
317 }
318 TemporalValue::LocalDateTime { nanos_since_epoch } => {
319 let ndt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch).naive_utc();
320 write!(
321 f,
322 "{}T{}",
323 ndt.date().format("%Y-%m-%d"),
324 format_naive_time(&ndt.time())
325 )
326 }
327 TemporalValue::DateTime {
328 nanos_since_epoch,
329 offset_seconds,
330 timezone_name,
331 } => {
332 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
334 let ndt = chrono::DateTime::from_timestamp_nanos(local_nanos).naive_utc();
335 let tz = format_offset(*offset_seconds);
336 write!(
337 f,
338 "{}T{}{}",
339 ndt.date().format("%Y-%m-%d"),
340 format_naive_time(&ndt.time()),
341 tz
342 )?;
343 if let Some(name) = timezone_name {
344 write!(f, "[{}]", name)?;
345 }
346 Ok(())
347 }
348 TemporalValue::Duration {
349 months,
350 days,
351 nanos,
352 } => {
353 write!(f, "P")?;
354 let years = months / 12;
355 let rem_months = months % 12;
356 if years != 0 {
357 write!(f, "{}Y", years)?;
358 }
359 if rem_months != 0 {
360 write!(f, "{}M", rem_months)?;
361 }
362 if *days != 0 {
363 write!(f, "{}D", days)?;
364 }
365 let abs_nanos = nanos.unsigned_abs() as i128;
367 let nanos_sign = if *nanos < 0 { -1i64 } else { 1 };
368 let total_secs = (abs_nanos / 1_000_000_000) as i64;
369 let frac_nanos = (abs_nanos % 1_000_000_000) as u32;
370 let hours = total_secs / 3600;
371 let mins = (total_secs % 3600) / 60;
372 let secs = total_secs % 60;
373
374 if hours != 0 || mins != 0 || secs != 0 || frac_nanos != 0 {
375 write!(f, "T")?;
376 if hours != 0 {
377 write!(f, "{}H", hours * nanos_sign)?;
378 }
379 if mins != 0 {
380 write!(f, "{}M", mins * nanos_sign)?;
381 }
382 if secs != 0 || frac_nanos != 0 {
383 let frac = format_fractional(frac_nanos);
384 if nanos_sign < 0 && (secs != 0 || frac_nanos != 0) {
385 write!(f, "-{}{}", secs, frac)?;
386 } else {
387 write!(f, "{}{}", secs, frac)?;
388 }
389 write!(f, "S")?;
390 }
391 } else if years == 0 && rem_months == 0 && *days == 0 {
392 write!(f, "T0S")?;
394 }
395 Ok(())
396 }
397 TemporalValue::Btic { lo, hi, meta } => match uni_btic::Btic::new(*lo, *hi, *meta) {
398 Ok(btic) => write!(f, "{btic}"),
399 Err(_) => write!(f, "Btic[lo={lo}, hi={hi}, meta={meta:#x}]"),
400 },
401 }
402 }
403}
404
405use chrono::Datelike as _;
407use chrono::Timelike as _;
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
422#[serde(untagged)]
423#[non_exhaustive]
424pub enum Value {
425 Null,
427 Bool(bool),
429 Int(i64),
431 Float(f64),
433 String(String),
435 Bytes(Vec<u8>),
437 List(Vec<Value>),
439 Map(HashMap<String, Value>),
441
442 Node(Node),
445 Edge(Edge),
447 Path(Path),
449
450 Vector(Vec<f32>),
453
454 SparseVector {
461 indices: Vec<u32>,
463 values: Vec<f32>,
465 },
466
467 BinaryVector(Vec<u8>),
472
473 Temporal(TemporalValue),
476}
477
478impl Value {
483 pub fn is_null(&self) -> bool {
485 matches!(self, Value::Null)
486 }
487
488 pub fn as_bool(&self) -> Option<bool> {
490 match self {
491 Value::Bool(b) => Some(*b),
492 _ => None,
493 }
494 }
495
496 pub fn as_i64(&self) -> Option<i64> {
498 match self {
499 Value::Int(i) => Some(*i),
500 _ => None,
501 }
502 }
503
504 pub fn as_u64(&self) -> Option<u64> {
506 match self {
507 Value::Int(i) if *i >= 0 => Some(*i as u64),
508 _ => None,
509 }
510 }
511
512 pub fn as_f64(&self) -> Option<f64> {
516 match self {
517 Value::Float(f) => Some(*f),
518 Value::Int(i) => Some(*i as f64),
519 _ => None,
520 }
521 }
522
523 pub fn as_str(&self) -> Option<&str> {
525 match self {
526 Value::String(s) => Some(s),
527 _ => None,
528 }
529 }
530
531 pub fn is_i64(&self) -> bool {
533 matches!(self, Value::Int(_))
534 }
535
536 pub fn is_f64(&self) -> bool {
538 matches!(self, Value::Float(_))
539 }
540
541 pub fn is_string(&self) -> bool {
543 matches!(self, Value::String(_))
544 }
545
546 pub fn is_number(&self) -> bool {
548 matches!(self, Value::Int(_) | Value::Float(_))
549 }
550
551 pub fn as_array(&self) -> Option<&Vec<Value>> {
553 match self {
554 Value::List(l) => Some(l),
555 _ => None,
556 }
557 }
558
559 pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
561 match self {
562 Value::Map(m) => Some(m),
563 _ => None,
564 }
565 }
566
567 pub fn is_bool(&self) -> bool {
569 matches!(self, Value::Bool(_))
570 }
571
572 pub fn is_list(&self) -> bool {
574 matches!(self, Value::List(_))
575 }
576
577 pub fn is_map(&self) -> bool {
579 matches!(self, Value::Map(_))
580 }
581
582 pub fn get(&self, key: &str) -> Option<&Value> {
586 match self {
587 Value::Map(m) => m.get(key),
588 _ => None,
589 }
590 }
591
592 pub fn is_temporal(&self) -> bool {
594 matches!(self, Value::Temporal(_))
595 }
596
597 pub fn as_temporal(&self) -> Option<&TemporalValue> {
599 match self {
600 Value::Temporal(t) => Some(t),
601 _ => None,
602 }
603 }
604}
605
606impl fmt::Display for Value {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 match self {
609 Value::Null => write!(f, "null"),
610 Value::Bool(b) => write!(f, "{b}"),
611 Value::Int(i) => write!(f, "{i}"),
612 Value::Float(v) => {
613 if v.fract() == 0.0 && v.is_finite() {
614 write!(f, "{v:.1}")
615 } else {
616 write!(f, "{v}")
617 }
618 }
619 Value::String(s) => write!(f, "{s}"),
620 Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
621 Value::List(l) => {
622 write!(f, "[")?;
623 for (i, item) in l.iter().enumerate() {
624 if i > 0 {
625 write!(f, ", ")?;
626 }
627 write!(f, "{item}")?;
628 }
629 write!(f, "]")
630 }
631 Value::Map(m) => {
632 write!(f, "{{")?;
633 for (i, (k, v)) in m.iter().enumerate() {
634 if i > 0 {
635 write!(f, ", ")?;
636 }
637 write!(f, "{k}: {v}")?;
638 }
639 write!(f, "}}")
640 }
641 Value::Node(n) => write!(f, "(:{} {{vid: {}}})", n.labels.join(":"), n.vid),
642 Value::Edge(e) => write!(f, "-[:{}]-", e.edge_type),
643 Value::Path(p) => write!(
644 f,
645 "<path: {} nodes, {} edges>",
646 p.nodes.len(),
647 p.edges.len()
648 ),
649 Value::Vector(v) => write!(f, "<vector: {} dims>", v.len()),
650 Value::SparseVector { indices, .. } => {
651 write!(f, "<sparse vector: {} nnz>", indices.len())
652 }
653 Value::BinaryVector(bytes) => write!(f, "<binary vector: {} lanes>", bytes.len()),
654 Value::Temporal(t) => write!(f, "{t}"),
655 }
656 }
657}
658
659pub fn cmp_i64_f64(i: i64, f: f64) -> std::cmp::Ordering {
682 use std::cmp::Ordering;
683 if f.is_infinite() {
684 return if f > 0.0 {
685 Ordering::Less
686 } else {
687 Ordering::Greater
688 };
689 }
690 let ff = f.floor();
691 if ff >= 9_223_372_036_854_775_808.0 {
693 return Ordering::Less;
694 }
695 if ff < -9_223_372_036_854_775_808.0 {
697 return Ordering::Greater;
698 }
699 let fi = ff as i64;
701 match i.cmp(&fi) {
702 Ordering::Equal if f > ff => Ordering::Less,
704 Ordering::Equal => Ordering::Equal,
705 other => other,
706 }
707}
708
709fn float_eq_normalized(a: f64, b: f64) -> bool {
716 a.total_cmp(&b) == std::cmp::Ordering::Equal
717 || (a == 0.0 && b == 0.0)
718 || (a.is_nan() && b.is_nan())
719}
720
721fn float_eq_normalized_f32(a: f32, b: f32) -> bool {
730 a.total_cmp(&b) == std::cmp::Ordering::Equal
731 || (a == 0.0 && b == 0.0)
732 || (a.is_nan() && b.is_nan())
733}
734
735fn slice_eq_normalized_f32(a: &[f32], b: &[f32]) -> bool {
740 a.len() == b.len()
741 && a.iter()
742 .zip(b)
743 .all(|(x, y)| float_eq_normalized_f32(*x, *y))
744}
745
746impl PartialEq for Value {
747 fn eq(&self, other: &Self) -> bool {
754 match (self, other) {
755 (Value::Float(a), Value::Float(b)) => float_eq_normalized(*a, *b),
757 (Value::Null, Value::Null) => true,
759 (Value::Bool(a), Value::Bool(b)) => a == b,
760 (Value::Int(a), Value::Int(b)) => a == b,
761 (Value::String(a), Value::String(b)) => a == b,
762 (Value::Bytes(a), Value::Bytes(b)) => a == b,
763 (Value::List(a), Value::List(b)) => a == b,
764 (Value::Map(a), Value::Map(b)) => a == b,
765 (Value::Node(a), Value::Node(b)) => a == b,
766 (Value::Edge(a), Value::Edge(b)) => a == b,
767 (Value::Path(a), Value::Path(b)) => a == b,
768 (Value::Vector(a), Value::Vector(b)) => slice_eq_normalized_f32(a, b),
772 (
773 Value::SparseVector {
774 indices: i1,
775 values: v1,
776 },
777 Value::SparseVector {
778 indices: i2,
779 values: v2,
780 },
781 ) => i1 == i2 && slice_eq_normalized_f32(v1, v2),
782 (Value::BinaryVector(a), Value::BinaryVector(b)) => a == b,
785 (Value::Temporal(a), Value::Temporal(b)) => a == b,
786 _ => false,
788 }
789 }
790}
791
792impl Eq for Value {}
793
794fn hash_f64_normalized<H: Hasher>(f: f64, state: &mut H) {
799 let bits = if f == 0.0 {
800 0.0f64.to_bits()
801 } else if f.is_nan() {
802 f64::NAN.to_bits()
803 } else {
804 f.to_bits()
805 };
806 bits.hash(state);
807}
808
809fn hash_f32_normalized<H: Hasher>(f: f32, state: &mut H) {
816 let bits = if f == 0.0 {
817 0.0f32.to_bits()
818 } else if f.is_nan() {
819 f32::NAN.to_bits()
820 } else {
821 f.to_bits()
822 };
823 bits.hash(state);
824}
825
826impl Hash for Value {
827 fn hash<H: Hasher>(&self, state: &mut H) {
828 std::mem::discriminant(self).hash(state);
830 match self {
831 Value::Null => {}
832 Value::Bool(b) => b.hash(state),
833 Value::Int(i) => i.hash(state),
834 Value::Float(f) => hash_f64_normalized(*f, state),
838 Value::String(s) => s.hash(state),
839 Value::Bytes(b) => b.hash(state),
840 Value::List(l) => l.hash(state),
841 Value::Map(m) => hash_map(m, state),
842 Value::Node(n) => n.hash(state),
843 Value::Edge(e) => e.hash(state),
844 Value::Path(p) => p.hash(state),
845 Value::Vector(v) => {
846 v.len().hash(state);
849 for f in v {
850 hash_f32_normalized(*f, state);
851 }
852 }
853 Value::SparseVector { indices, values } => {
854 indices.hash(state);
858 values.len().hash(state);
859 for f in values {
860 hash_f32_normalized(*f, state);
861 }
862 }
863 Value::BinaryVector(b) => b.hash(state),
866 Value::Temporal(t) => t.hash(state),
867 }
868 }
869}
870
871fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
877 let mut pairs: Vec<_> = m.iter().collect();
878 pairs.sort_by_key(|(k, _)| *k);
879 pairs.len().hash(state);
880 for (k, v) in pairs {
881 k.hash(state);
882 v.hash(state);
883 }
884}
885
886#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888pub struct Node {
889 pub vid: Vid,
891 pub labels: Vec<String>,
893 pub properties: HashMap<String, Value>,
895}
896
897impl Hash for Node {
898 fn hash<H: Hasher>(&self, state: &mut H) {
899 self.vid.hash(state);
900 let mut sorted_labels = self.labels.clone();
901 sorted_labels.sort();
902 sorted_labels.hash(state);
903 hash_map(&self.properties, state);
904 }
905}
906
907impl Node {
908 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
915 let val = self
916 .properties
917 .get(property)
918 .ok_or_else(|| UniError::Query {
919 message: format!("Property '{}' not found on node {}", property, self.vid),
920 query: None,
921 })?;
922 T::from_value(val)
923 }
924
925 pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
927 self.properties
928 .get(property)
929 .and_then(|v| T::from_value(v).ok())
930 }
931}
932
933#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
935pub struct Edge {
936 pub eid: Eid,
938 pub edge_type: String,
940 pub src: Vid,
942 pub dst: Vid,
944 pub properties: HashMap<String, Value>,
946}
947
948impl Hash for Edge {
949 fn hash<H: Hasher>(&self, state: &mut H) {
950 self.eid.hash(state);
951 self.edge_type.hash(state);
952 self.src.hash(state);
953 self.dst.hash(state);
954 hash_map(&self.properties, state);
955 }
956}
957
958impl Edge {
959 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
966 let val = self
967 .properties
968 .get(property)
969 .ok_or_else(|| UniError::Query {
970 message: format!("Property '{}' not found on edge {}", property, self.eid),
971 query: None,
972 })?;
973 T::from_value(val)
974 }
975}
976
977#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
979pub struct Path {
980 pub nodes: Vec<Node>,
982 #[serde(rename = "relationships")]
984 pub edges: Vec<Edge>,
985}
986
987impl Path {
988 pub fn nodes(&self) -> &[Node] {
990 &self.nodes
991 }
992
993 pub fn edges(&self) -> &[Edge] {
995 &self.edges
996 }
997
998 pub fn len(&self) -> usize {
1000 self.edges.len()
1001 }
1002
1003 pub fn is_empty(&self) -> bool {
1005 self.edges.is_empty()
1006 }
1007
1008 pub fn start(&self) -> Option<&Node> {
1010 self.nodes.first()
1011 }
1012
1013 pub fn end(&self) -> Option<&Node> {
1015 self.nodes.last()
1016 }
1017}
1018
1019pub trait FromValue: Sized {
1025 fn from_value(value: &Value) -> crate::Result<Self>;
1031}
1032
1033impl<T> FromValue for T
1035where
1036 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1037{
1038 fn from_value(value: &Value) -> crate::Result<Self> {
1039 Self::try_from(value)
1040 }
1041}
1042
1043macro_rules! impl_try_from_value_owned {
1048 ($($t:ty),+ $(,)?) => {
1049 $(
1050 impl TryFrom<Value> for $t {
1051 type Error = UniError;
1052 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1053 Self::try_from(&value)
1054 }
1055 }
1056 )+
1057 };
1058}
1059
1060impl_try_from_value_owned!(
1061 String,
1062 i64,
1063 i32,
1064 f64,
1065 bool,
1066 Vid,
1067 Eid,
1068 Vec<f32>,
1069 Path,
1070 Node,
1071 Edge
1072);
1073
1074fn type_error(expected: &str, value: &Value) -> UniError {
1080 UniError::Type {
1081 expected: expected.to_string(),
1082 actual: format!("{:?}", value),
1083 }
1084}
1085
1086impl TryFrom<&Value> for String {
1087 type Error = UniError;
1088
1089 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1090 match value {
1091 Value::String(s) => Ok(s.clone()),
1092 Value::Int(i) => Ok(i.to_string()),
1093 Value::Float(f) => Ok(f.to_string()),
1094 Value::Bool(b) => Ok(b.to_string()),
1095 Value::Temporal(t) => Ok(t.to_string()),
1096 _ => Err(type_error("String", value)),
1097 }
1098 }
1099}
1100
1101impl TryFrom<&Value> for i64 {
1102 type Error = UniError;
1103
1104 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1111 match value {
1112 Value::Int(i) => Ok(*i),
1113 Value::Float(f) => Ok(*f as i64),
1114 _ => Err(type_error("Int", value)),
1115 }
1116 }
1117}
1118
1119impl TryFrom<&Value> for i32 {
1120 type Error = UniError;
1121
1122 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1127 match value {
1128 Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
1129 expected: "i32".to_string(),
1130 actual: format!("Integer {} out of range", i),
1131 }),
1132 Value::Float(f) => {
1133 if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1134 return Err(UniError::Type {
1135 expected: "i32".to_string(),
1136 actual: format!("Float {} out of range", f),
1137 });
1138 }
1139 if f.fract() != 0.0 {
1140 return Err(UniError::Type {
1141 expected: "i32".to_string(),
1142 actual: format!("Float {} has fractional part", f),
1143 });
1144 }
1145 Ok(*f as i32)
1146 }
1147 _ => Err(type_error("Int", value)),
1148 }
1149 }
1150}
1151
1152impl TryFrom<&Value> for f64 {
1153 type Error = UniError;
1154
1155 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1156 match value {
1157 Value::Float(f) => Ok(*f),
1158 Value::Int(i) => Ok(*i as f64),
1159 _ => Err(type_error("Float", value)),
1160 }
1161 }
1162}
1163
1164impl TryFrom<&Value> for bool {
1165 type Error = UniError;
1166
1167 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1168 match value {
1169 Value::Bool(b) => Ok(*b),
1170 _ => Err(type_error("Bool", value)),
1171 }
1172 }
1173}
1174
1175impl TryFrom<&Value> for Vid {
1176 type Error = UniError;
1177
1178 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1179 match value {
1180 Value::Node(n) => Ok(n.vid),
1181 Value::String(s) => {
1182 if let Ok(id) = s.parse::<u64>() {
1183 return Ok(Vid::new(id));
1184 }
1185 Err(UniError::Type {
1186 expected: "Vid".into(),
1187 actual: s.clone(),
1188 })
1189 }
1190 Value::Int(i) => Ok(Vid::new(*i as u64)),
1191 _ => Err(type_error("Vid", value)),
1192 }
1193 }
1194}
1195
1196impl TryFrom<&Value> for Eid {
1197 type Error = UniError;
1198
1199 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1200 match value {
1201 Value::Edge(e) => Ok(e.eid),
1202 Value::String(s) => {
1203 if let Ok(id) = s.parse::<u64>() {
1204 return Ok(Eid::new(id));
1205 }
1206 Err(UniError::Type {
1207 expected: "Eid".into(),
1208 actual: s.clone(),
1209 })
1210 }
1211 Value::Int(i) => Ok(Eid::new(*i as u64)),
1212 _ => Err(type_error("Eid", value)),
1213 }
1214 }
1215}
1216
1217impl TryFrom<&Value> for Vec<f32> {
1218 type Error = UniError;
1219
1220 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1221 match value {
1222 Value::Vector(v) => Ok(v.clone()),
1223 Value::List(l) => {
1224 let mut vec = Vec::with_capacity(l.len());
1225 for item in l {
1226 match item {
1227 Value::Float(f) => vec.push(*f as f32),
1228 Value::Int(i) => vec.push(*i as f32),
1229 _ => return Err(type_error("Float", item)),
1230 }
1231 }
1232 Ok(vec)
1233 }
1234 _ => Err(type_error("Vector", value)),
1235 }
1236 }
1237}
1238
1239impl<T> TryFrom<&Value> for Option<T>
1240where
1241 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1242{
1243 type Error = UniError;
1244
1245 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1246 match value {
1247 Value::Null => Ok(None),
1248 _ => T::try_from(value).map(Some),
1249 }
1250 }
1251}
1252
1253impl<T> TryFrom<Value> for Option<T>
1254where
1255 T: TryFrom<Value, Error = UniError>,
1256{
1257 type Error = UniError;
1258 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1259 match value {
1260 Value::Null => Ok(None),
1261 _ => T::try_from(value).map(Some),
1262 }
1263 }
1264}
1265
1266impl<T> TryFrom<&Value> for Vec<T>
1267where
1268 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1269{
1270 type Error = UniError;
1271
1272 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1273 match value {
1274 Value::List(l) => {
1275 let mut vec = Vec::with_capacity(l.len());
1276 for item in l {
1277 vec.push(T::try_from(item)?);
1278 }
1279 Ok(vec)
1280 }
1281 _ => Err(type_error("List", value)),
1282 }
1283 }
1284}
1285
1286impl<T> TryFrom<Value> for Vec<T>
1287where
1288 T: TryFrom<Value, Error = UniError>,
1289{
1290 type Error = UniError;
1291 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1292 match value {
1293 Value::List(l) => {
1294 let mut vec = Vec::with_capacity(l.len());
1295 for item in l {
1296 vec.push(T::try_from(item)?);
1297 }
1298 Ok(vec)
1299 }
1300 other => Err(type_error("List", &other)),
1301 }
1302 }
1303}
1304
1305fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1311 keys.iter().find_map(|k| map.get(*k))
1312}
1313
1314fn extract_properties(value: &Value) -> HashMap<String, Value> {
1316 match value {
1317 Value::Map(m) => m.clone(),
1318 _ => HashMap::new(),
1319 }
1320}
1321
1322impl TryFrom<&Value> for Node {
1323 type Error = UniError;
1324
1325 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1326 match value {
1327 Value::Node(n) => Ok(n.clone()),
1328 Value::Map(m) => {
1329 let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1330 let props_val = m.get("properties");
1331
1332 let (Some(v), Some(p)) = (vid_val, props_val) else {
1333 return Err(type_error("Node Map", value));
1334 };
1335
1336 let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1338 label_list
1339 .iter()
1340 .filter_map(|v| {
1341 if let Value::String(s) = v {
1342 Some(s.clone())
1343 } else {
1344 None
1345 }
1346 })
1347 .collect()
1348 } else {
1349 Vec::new()
1350 };
1351
1352 Ok(Node {
1353 vid: Vid::try_from(v)?,
1354 labels,
1355 properties: extract_properties(p),
1356 })
1357 }
1358 _ => Err(type_error("Node", value)),
1359 }
1360 }
1361}
1362
1363impl TryFrom<&Value> for Edge {
1364 type Error = UniError;
1365
1366 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1367 match value {
1368 Value::Edge(e) => Ok(e.clone()),
1369 Value::Map(m) => {
1370 let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1371 let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1372 let src_val = get_with_fallback(m, &["_src", "src"]);
1373 let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1374 let props_val = m.get("properties");
1375
1376 let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1377 (eid_val, type_val, src_val, dst_val, props_val)
1378 else {
1379 return Err(type_error("Edge Map", value));
1380 };
1381
1382 Ok(Edge {
1383 eid: Eid::try_from(id)?,
1384 edge_type: String::try_from(t)?,
1385 src: Vid::try_from(s)?,
1386 dst: Vid::try_from(d)?,
1387 properties: extract_properties(p),
1388 })
1389 }
1390 _ => Err(type_error("Edge", value)),
1391 }
1392 }
1393}
1394
1395impl TryFrom<&Value> for Path {
1396 type Error = UniError;
1397
1398 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1399 match value {
1400 Value::Path(p) => Ok(p.clone()),
1401 Value::Map(m) => {
1402 let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1403 (m.get("nodes"), m.get("relationships"))
1404 else {
1405 return Err(type_error("Path (Map with nodes/relationships)", value));
1406 };
1407
1408 let nodes = nodes_list
1409 .iter()
1410 .map(Node::try_from)
1411 .collect::<std::result::Result<Vec<_>, _>>()?;
1412
1413 let edges = rels_list
1414 .iter()
1415 .map(Edge::try_from)
1416 .collect::<std::result::Result<Vec<_>, _>>()?;
1417
1418 Ok(Path { nodes, edges })
1419 }
1420 _ => Err(type_error("Path", value)),
1421 }
1422 }
1423}
1424
1425impl From<String> for Value {
1430 fn from(v: String) -> Self {
1431 Value::String(v)
1432 }
1433}
1434
1435impl From<&str> for Value {
1436 fn from(v: &str) -> Self {
1437 Value::String(v.to_string())
1438 }
1439}
1440
1441impl From<i64> for Value {
1442 fn from(v: i64) -> Self {
1443 Value::Int(v)
1444 }
1445}
1446
1447impl From<i32> for Value {
1448 fn from(v: i32) -> Self {
1449 Value::Int(v as i64)
1450 }
1451}
1452
1453impl From<f64> for Value {
1454 fn from(v: f64) -> Self {
1455 Value::Float(v)
1456 }
1457}
1458
1459impl From<bool> for Value {
1460 fn from(v: bool) -> Self {
1461 Value::Bool(v)
1462 }
1463}
1464
1465impl From<Vec<f32>> for Value {
1466 fn from(v: Vec<f32>) -> Self {
1467 Value::Vector(v)
1468 }
1469}
1470
1471impl From<serde_json::Value> for Value {
1476 fn from(v: serde_json::Value) -> Self {
1477 match v {
1478 serde_json::Value::Null => Value::Null,
1479 serde_json::Value::Bool(b) => Value::Bool(b),
1480 serde_json::Value::Number(n) => {
1481 if let Some(i) = n.as_i64() {
1482 Value::Int(i)
1483 } else if let Some(f) = n.as_f64() {
1484 Value::Float(f)
1485 } else {
1486 Value::Null
1487 }
1488 }
1489 serde_json::Value::String(s) => Value::String(s),
1490 serde_json::Value::Array(arr) => {
1491 Value::List(arr.into_iter().map(Value::from).collect())
1492 }
1493 serde_json::Value::Object(obj) => {
1494 Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1495 }
1496 }
1497 }
1498}
1499
1500impl From<Value> for serde_json::Value {
1501 fn from(v: Value) -> Self {
1502 match v {
1503 Value::Null => serde_json::Value::Null,
1504 Value::Bool(b) => serde_json::Value::Bool(b),
1505 Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1506 Value::Float(f) => serde_json::Number::from_f64(f)
1507 .map(serde_json::Value::Number)
1508 .unwrap_or(serde_json::Value::Null), Value::String(s) => serde_json::Value::String(s),
1510 Value::Bytes(b) => {
1511 use base64::Engine;
1512 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1513 }
1514 Value::List(l) => {
1515 serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1516 }
1517 Value::Map(m) => {
1518 let mut map = serde_json::Map::new();
1519 for (k, v) in m {
1520 map.insert(k, v.into());
1521 }
1522 serde_json::Value::Object(map)
1523 }
1524 Value::Node(n) => {
1525 let mut map = serde_json::Map::new();
1526 map.insert(
1527 "_id".to_string(),
1528 serde_json::Value::String(n.vid.to_string()),
1529 );
1530 map.insert(
1531 "_labels".to_string(),
1532 serde_json::Value::Array(
1533 n.labels
1534 .into_iter()
1535 .map(serde_json::Value::String)
1536 .collect(),
1537 ),
1538 );
1539 let props: serde_json::Value = Value::Map(n.properties).into();
1540 map.insert("properties".to_string(), props);
1541 serde_json::Value::Object(map)
1542 }
1543 Value::Edge(e) => {
1544 let mut map = serde_json::Map::new();
1545 map.insert(
1546 "_id".to_string(),
1547 serde_json::Value::String(e.eid.to_string()),
1548 );
1549 map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1550 map.insert(
1551 "_src".to_string(),
1552 serde_json::Value::String(e.src.to_string()),
1553 );
1554 map.insert(
1555 "_dst".to_string(),
1556 serde_json::Value::String(e.dst.to_string()),
1557 );
1558 let props: serde_json::Value = Value::Map(e.properties).into();
1559 map.insert("properties".to_string(), props);
1560 serde_json::Value::Object(map)
1561 }
1562 Value::Path(p) => {
1563 let mut map = serde_json::Map::new();
1564 map.insert(
1565 "nodes".to_string(),
1566 Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1567 );
1568 map.insert(
1569 "relationships".to_string(),
1570 Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1571 );
1572 serde_json::Value::Object(map)
1573 }
1574 Value::Vector(v) => serde_json::Value::Array(
1575 v.into_iter()
1576 .map(|f| {
1577 serde_json::Number::from_f64(f as f64)
1578 .map(serde_json::Value::Number)
1579 .unwrap_or(serde_json::Value::Null)
1580 })
1581 .collect(),
1582 ),
1583 Value::SparseVector { indices, values } => {
1584 let idx = serde_json::Value::Array(
1585 indices
1586 .into_iter()
1587 .map(|i| serde_json::Value::Number(serde_json::Number::from(i)))
1588 .collect(),
1589 );
1590 let vals = serde_json::Value::Array(
1591 values
1592 .into_iter()
1593 .map(|f| {
1594 serde_json::Number::from_f64(f as f64)
1595 .map(serde_json::Value::Number)
1596 .unwrap_or(serde_json::Value::Null)
1597 })
1598 .collect(),
1599 );
1600 let mut map = serde_json::Map::new();
1601 map.insert("indices".to_string(), idx);
1602 map.insert("values".to_string(), vals);
1603 serde_json::Value::Object(map)
1604 }
1605 Value::BinaryVector(bytes) => serde_json::Value::Array(
1608 bytes
1609 .into_iter()
1610 .map(|b| serde_json::Value::Number(serde_json::Number::from(b)))
1611 .collect(),
1612 ),
1613 Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1614 }
1615 }
1616}
1617
1618#[macro_export]
1640macro_rules! unival {
1641 (null) => {
1643 $crate::Value::Null
1644 };
1645
1646 (true) => {
1648 $crate::Value::Bool(true)
1649 };
1650 (false) => {
1651 $crate::Value::Bool(false)
1652 };
1653
1654 ([ $($elem:tt),* $(,)? ]) => {
1656 $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1657 };
1658
1659 ({ $($key:tt : $val:tt),* $(,)? }) => {
1661 $crate::Value::Map({
1662 #[allow(unused_mut)]
1663 let mut map = ::std::collections::HashMap::new();
1664 $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1665 map
1666 })
1667 };
1668
1669 ($e:expr) => {
1671 $crate::Value::from($e)
1672 };
1673}
1674
1675impl From<usize> for Value {
1680 fn from(v: usize) -> Self {
1681 Value::Int(v as i64)
1682 }
1683}
1684
1685impl From<u64> for Value {
1686 fn from(v: u64) -> Self {
1687 Value::Int(v as i64)
1688 }
1689}
1690
1691impl From<f32> for Value {
1692 fn from(v: f32) -> Self {
1693 Value::Float(v as f64)
1694 }
1695}
1696
1697#[cfg(test)]
1702mod tests {
1703 use super::*;
1704 use std::cmp::Ordering;
1705
1706 #[test]
1707 fn cmp_i64_f64_exact_above_2p53() {
1708 let two_p53 = 9_007_199_254_740_992.0_f64;
1710 assert_eq!(cmp_i64_f64(9_007_199_254_740_992, two_p53), Ordering::Equal);
1711 assert_eq!(
1712 cmp_i64_f64(9_007_199_254_740_993, two_p53),
1713 Ordering::Greater
1714 );
1715 assert_eq!(cmp_i64_f64(9_007_199_254_740_991, two_p53), Ordering::Less);
1716 }
1717
1718 #[test]
1719 fn cmp_i64_f64_small_and_fractional() {
1720 assert_eq!(cmp_i64_f64(2, 2.0), Ordering::Equal);
1721 assert_eq!(cmp_i64_f64(1, 1.5), Ordering::Less);
1722 assert_eq!(cmp_i64_f64(2, 1.5), Ordering::Greater);
1723 assert_eq!(cmp_i64_f64(-3, -2.5), Ordering::Less);
1724 assert_eq!(cmp_i64_f64(-2, -2.5), Ordering::Greater);
1725 assert_eq!(cmp_i64_f64(0, -0.0), Ordering::Equal);
1726 }
1727
1728 #[test]
1729 fn cmp_i64_f64_extremes_and_infinities() {
1730 assert_eq!(cmp_i64_f64(i64::MAX, f64::INFINITY), Ordering::Less);
1731 assert_eq!(cmp_i64_f64(i64::MIN, f64::NEG_INFINITY), Ordering::Greater);
1732 assert_eq!(
1734 cmp_i64_f64(i64::MAX, 9_223_372_036_854_775_808.0),
1735 Ordering::Less
1736 );
1737 assert_eq!(
1739 cmp_i64_f64(i64::MIN, -9_223_372_036_854_775_808.0),
1740 Ordering::Equal
1741 );
1742 assert_eq!(cmp_i64_f64(i64::MIN, -1e300), Ordering::Greater);
1744 assert_eq!(cmp_i64_f64(i64::MAX, 1e300), Ordering::Less);
1746 }
1747
1748 #[test]
1749 fn test_accessor_methods() {
1750 assert!(Value::Null.is_null());
1751 assert!(!Value::Int(1).is_null());
1752
1753 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1754 assert_eq!(Value::Int(42).as_bool(), None);
1755
1756 assert_eq!(Value::Int(42).as_i64(), Some(42));
1757 assert_eq!(Value::Float(2.5).as_i64(), None);
1758
1759 assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1761 assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1762 assert_eq!(Value::String("x".into()).as_f64(), None);
1763
1764 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1765 assert_eq!(Value::Int(1).as_str(), None);
1766
1767 assert!(Value::Int(1).is_i64());
1768 assert!(!Value::Float(1.0).is_i64());
1769
1770 assert!(Value::Float(1.0).is_f64());
1771 assert!(!Value::Int(1).is_f64());
1772
1773 assert!(Value::Int(1).is_number());
1774 assert!(Value::Float(1.0).is_number());
1775 assert!(!Value::String("x".into()).is_number());
1776 }
1777
1778 #[test]
1779 fn test_serde_json_roundtrip() {
1780 let val = Value::Int(42);
1781 let json: serde_json::Value = val.clone().into();
1782 let back: Value = json.into();
1783 assert_eq!(val, back);
1784
1785 let val = Value::Float(2.5);
1786 let json: serde_json::Value = val.clone().into();
1787 let back: Value = json.into();
1788 assert_eq!(val, back);
1789
1790 let val = Value::String("hello".into());
1791 let json: serde_json::Value = val.clone().into();
1792 let back: Value = json.into();
1793 assert_eq!(val, back);
1794
1795 let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1796 let json: serde_json::Value = val.clone().into();
1797 let back: Value = json.into();
1798 assert_eq!(val, back);
1799 }
1800
1801 #[test]
1802 fn test_unival_macro() {
1803 assert_eq!(unival!(null), Value::Null);
1804 assert_eq!(unival!(true), Value::Bool(true));
1805 assert_eq!(unival!(false), Value::Bool(false));
1806 assert_eq!(unival!(42_i64), Value::Int(42));
1807 assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1808 assert_eq!(unival!("hello"), Value::String("hello".into()));
1809
1810 let list = unival!([1_i64, 2_i64]);
1812 assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1813
1814 let map = unival!({"key": "val", "num": 42_i64});
1816 if let Value::Map(m) = &map {
1817 assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1818 assert_eq!(m.get("num"), Some(&Value::Int(42)));
1819 } else {
1820 panic!("Expected Map");
1821 }
1822
1823 let x: i64 = 99;
1825 assert_eq!(unival!(x), Value::Int(99));
1826 }
1827
1828 #[test]
1829 fn test_int_float_distinction_preserved() {
1830 let int_val = Value::Int(42);
1832 let float_val = Value::Float(42.0);
1833
1834 assert!(int_val.is_i64());
1835 assert!(!int_val.is_f64());
1836
1837 assert!(float_val.is_f64());
1838 assert!(!float_val.is_i64());
1839
1840 assert_ne!(int_val, float_val);
1842 }
1843
1844 #[test]
1845 fn test_temporal_display_zero_seconds_omitted() {
1846 let lt = TemporalValue::LocalTime {
1848 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1849 };
1850 assert_eq!(lt.to_string(), "12:00");
1851
1852 let lt2 = TemporalValue::LocalTime {
1854 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1855 };
1856 assert_eq!(lt2.to_string(), "12:31:14");
1857
1858 let lt3 = TemporalValue::LocalTime {
1860 nanos_since_midnight: 500_000_000,
1861 };
1862 assert_eq!(lt3.to_string(), "00:00:00.5");
1863
1864 let t = TemporalValue::Time {
1866 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1867 offset_seconds: 0,
1868 };
1869 assert_eq!(t.to_string(), "12:00Z");
1870
1871 let t2 = TemporalValue::Time {
1873 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1874 offset_seconds: 3600,
1875 };
1876 assert_eq!(t2.to_string(), "12:31:14+01:00");
1877
1878 let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1880 .unwrap()
1881 .and_hms_opt(12, 31, 0)
1882 .unwrap()
1883 .and_utc()
1884 .timestamp_nanos_opt()
1885 .unwrap();
1886 let ldt = TemporalValue::LocalDateTime {
1887 nanos_since_epoch: epoch_nanos,
1888 };
1889 assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1890
1891 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1893 .unwrap()
1894 .and_hms_opt(11, 31, 0)
1895 .unwrap()
1896 .and_utc()
1897 .timestamp_nanos_opt()
1898 .unwrap();
1899 let dt = TemporalValue::DateTime {
1900 nanos_since_epoch: utc_nanos,
1901 offset_seconds: 3600,
1902 timezone_name: None,
1903 };
1904 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1905
1906 let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1908 .unwrap()
1909 .and_hms_nano_opt(20, 40, 32, 142_000_000)
1910 .unwrap()
1911 .and_utc()
1912 .timestamp_nanos_opt()
1913 .unwrap();
1914 let dt2 = TemporalValue::DateTime {
1915 nanos_since_epoch: utc_nanos2,
1916 offset_seconds: 3600,
1917 timezone_name: None,
1918 };
1919 assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1920
1921 let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1923 .unwrap()
1924 .and_hms_opt(12, 31, 0)
1925 .unwrap()
1926 .and_utc()
1927 .timestamp_nanos_opt()
1928 .unwrap();
1929 let dt3 = TemporalValue::DateTime {
1930 nanos_since_epoch: utc_nanos3,
1931 offset_seconds: 0,
1932 timezone_name: None,
1933 };
1934 assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1935 }
1936
1937 #[test]
1938 fn test_temporal_display_fractional_trailing_zeros_stripped() {
1939 let d = TemporalValue::Duration {
1941 months: 0,
1942 days: 0,
1943 nanos: 900_000_000,
1944 };
1945 assert_eq!(d.to_string(), "PT0.9S");
1946
1947 let d2 = TemporalValue::Duration {
1949 months: 0,
1950 days: 0,
1951 nanos: 400_000_000,
1952 };
1953 assert_eq!(d2.to_string(), "PT0.4S");
1954
1955 let d3 = TemporalValue::Duration {
1957 months: 0,
1958 days: 0,
1959 nanos: 142_000_000,
1960 };
1961 assert_eq!(d3.to_string(), "PT0.142S");
1962
1963 let d4 = TemporalValue::Duration {
1965 months: 0,
1966 days: 0,
1967 nanos: 1,
1968 };
1969 assert_eq!(d4.to_string(), "PT0.000000001S");
1970 }
1971
1972 #[test]
1973 fn test_temporal_display_offset_second_precision() {
1974 let t = TemporalValue::Time {
1976 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1977 offset_seconds: 2 * 3600 + 5 * 60 + 59,
1978 };
1979 assert_eq!(t.to_string(), "12:00+02:05:59");
1980
1981 let t2 = TemporalValue::Time {
1983 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1984 offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1985 };
1986 assert_eq!(t2.to_string(), "12:00-02:05:07");
1987 }
1988
1989 #[test]
1990 fn test_temporal_display_datetime_with_timezone_name() {
1991 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1992 .unwrap()
1993 .and_hms_opt(11, 31, 0)
1994 .unwrap()
1995 .and_utc()
1996 .timestamp_nanos_opt()
1997 .unwrap();
1998 let dt = TemporalValue::DateTime {
1999 nanos_since_epoch: utc_nanos,
2000 offset_seconds: 3600,
2001 timezone_name: Some("Europe/Stockholm".to_string()),
2002 };
2003 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
2004 }
2005
2006 #[test]
2013 fn value_hash_eq_contract_float_signed_zero() {
2014 use std::collections::hash_map::DefaultHasher;
2015 use std::hash::{Hash, Hasher};
2016
2017 fn h(v: &Value) -> u64 {
2018 let mut s = DefaultHasher::new();
2019 v.hash(&mut s);
2020 s.finish()
2021 }
2022
2023 let pos = Value::Float(0.0);
2024 let neg = Value::Float(-0.0);
2025 assert_eq!(pos, neg, "0.0 and -0.0 compare equal");
2026 assert_eq!(
2027 h(&pos),
2028 h(&neg),
2029 "equal Values must hash equally (Hash/Eq contract)"
2030 );
2031 }
2032}