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 millisecond(&self) -> Option<i64> {
165 self.to_time().map(|t| (t.nanosecond() / 1_000_000) as i64)
166 }
167
168 pub fn microsecond(&self) -> Option<i64> {
170 self.to_time().map(|t| (t.nanosecond() / 1_000) as i64)
171 }
172
173 pub fn nanosecond(&self) -> Option<i64> {
175 self.to_time().map(|t| t.nanosecond() as i64)
176 }
177
178 pub fn quarter(&self) -> Option<i64> {
180 self.to_date().map(|d| ((d.month() - 1) / 3 + 1) as i64)
181 }
182
183 pub fn week(&self) -> Option<i64> {
185 self.to_date().map(|d| d.iso_week().week() as i64)
186 }
187
188 pub fn week_year(&self) -> Option<i64> {
190 self.to_date().map(|d| d.iso_week().year() as i64)
191 }
192
193 pub fn ordinal_day(&self) -> Option<i64> {
195 self.to_date().map(|d| d.ordinal() as i64)
196 }
197
198 pub fn day_of_week(&self) -> Option<i64> {
200 self.to_date()
201 .map(|d| (d.weekday().num_days_from_monday() + 1) as i64)
202 }
203
204 pub fn day_of_quarter(&self) -> Option<i64> {
206 self.to_date().map(|d| {
207 let quarter_start_month = ((d.month() - 1) / 3) * 3 + 1;
208 let quarter_start =
209 chrono::NaiveDate::from_ymd_opt(d.year(), quarter_start_month, 1).unwrap();
210 d.signed_duration_since(quarter_start).num_days() + 1
211 })
212 }
213
214 pub fn timezone(&self) -> Option<&str> {
216 match self {
217 TemporalValue::DateTime {
218 timezone_name: Some(name),
219 ..
220 } => Some(name.as_str()),
221 _ => None,
222 }
223 }
224
225 fn raw_offset_seconds(&self) -> Option<i32> {
227 match self {
228 TemporalValue::Time { offset_seconds, .. }
229 | TemporalValue::DateTime { offset_seconds, .. } => Some(*offset_seconds),
230 _ => None,
231 }
232 }
233
234 pub fn offset(&self) -> Option<String> {
236 self.raw_offset_seconds().map(format_offset)
237 }
238
239 pub fn offset_minutes(&self) -> Option<i64> {
241 self.raw_offset_seconds().map(|s| s as i64 / 60)
242 }
243
244 pub fn offset_seconds_value(&self) -> Option<i64> {
246 self.raw_offset_seconds().map(|s| s as i64)
247 }
248
249 fn raw_epoch_nanos(&self) -> Option<i64> {
251 match self {
252 TemporalValue::DateTime {
253 nanos_since_epoch, ..
254 }
255 | TemporalValue::LocalDateTime {
256 nanos_since_epoch, ..
257 } => Some(*nanos_since_epoch),
258 _ => None,
259 }
260 }
261
262 pub fn epoch_seconds(&self) -> Option<i64> {
264 self.raw_epoch_nanos().map(|n| n / 1_000_000_000)
265 }
266
267 pub fn epoch_millis(&self) -> Option<i64> {
269 self.raw_epoch_nanos().map(|n| n / 1_000_000)
270 }
271
272 pub fn to_date(&self) -> Option<chrono::NaiveDate> {
278 let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1)?;
279 match self {
280 TemporalValue::Date { days_since_epoch } => {
281 epoch.checked_add_signed(chrono::Duration::days(*days_since_epoch as i64))
282 }
283 TemporalValue::LocalDateTime { nanos_since_epoch } => {
284 let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
285 Some(dt.date_naive())
286 }
287 TemporalValue::DateTime {
288 nanos_since_epoch,
289 offset_seconds,
290 ..
291 } => {
292 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
294 let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
295 Some(dt.date_naive())
296 }
297 _ => None,
298 }
299 }
300
301 pub fn to_time(&self) -> Option<chrono::NaiveTime> {
303 match self {
304 TemporalValue::LocalTime {
305 nanos_since_midnight,
306 }
307 | TemporalValue::Time {
308 nanos_since_midnight,
309 ..
310 } => nanos_to_time(*nanos_since_midnight),
311 TemporalValue::LocalDateTime { nanos_since_epoch } => {
312 let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
313 Some(dt.naive_utc().time())
314 }
315 TemporalValue::DateTime {
316 nanos_since_epoch,
317 offset_seconds,
318 ..
319 } => {
320 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
321 let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
322 Some(dt.naive_utc().time())
323 }
324 _ => None,
325 }
326 }
327}
328
329fn nanos_to_time(nanos: i64) -> Option<chrono::NaiveTime> {
331 let total_secs = nanos / 1_000_000_000;
332 let h = (total_secs / 3600) as u32;
333 let m = ((total_secs % 3600) / 60) as u32;
334 let s = (total_secs % 60) as u32;
335 let ns = (nanos % 1_000_000_000) as u32;
336 chrono::NaiveTime::from_hms_nano_opt(h, m, s, ns)
337}
338
339fn format_offset(offset_seconds: i32) -> String {
341 if offset_seconds == 0 {
342 return "Z".to_string();
343 }
344 format_offset_numeric(offset_seconds)
345}
346
347fn format_offset_numeric(offset_seconds: i32) -> String {
349 let sign = if offset_seconds >= 0 { '+' } else { '-' };
350 let abs = offset_seconds.unsigned_abs();
351 let h = abs / 3600;
352 let m = (abs % 3600) / 60;
353 let s = abs % 60;
354 if s != 0 {
355 format!("{}{:02}:{:02}:{:02}", sign, h, m, s)
356 } else {
357 format!("{}{:02}:{:02}", sign, h, m)
358 }
359}
360
361fn format_fractional(nanos: u32) -> String {
363 if nanos == 0 {
364 return String::new();
365 }
366 let s = format!("{:09}", nanos);
367 let trimmed = s.trim_end_matches('0');
368 format!(".{}", trimmed)
369}
370
371fn format_time_component(hour: u32, minute: u32, second: u32, nanos: u32) -> String {
373 if second == 0 && nanos == 0 {
374 format!("{:02}:{:02}", hour, minute)
375 } else {
376 let frac = format_fractional(nanos);
377 format!("{:02}:{:02}:{:02}{}", hour, minute, second, frac)
378 }
379}
380
381fn format_naive_time(t: &chrono::NaiveTime) -> String {
383 format_time_component(t.hour(), t.minute(), t.second(), t.nanosecond())
384}
385
386fn nanos_to_time_or_midnight(nanos: i64) -> chrono::NaiveTime {
388 nanos_to_time(nanos).unwrap_or_else(|| chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
389}
390
391impl fmt::Display for TemporalValue {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 match self {
394 TemporalValue::Date { days_since_epoch } => {
395 let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
396 let date = epoch + chrono::Duration::days(*days_since_epoch as i64);
397 write!(f, "{}", date.format("%Y-%m-%d"))
398 }
399 TemporalValue::LocalTime {
400 nanos_since_midnight,
401 } => {
402 let time = nanos_to_time_or_midnight(*nanos_since_midnight);
403 write!(f, "{}", format_naive_time(&time))
404 }
405 TemporalValue::Time {
406 nanos_since_midnight,
407 offset_seconds,
408 } => {
409 let time = nanos_to_time_or_midnight(*nanos_since_midnight);
410 write!(
411 f,
412 "{}{}",
413 format_naive_time(&time),
414 format_offset(*offset_seconds)
415 )
416 }
417 TemporalValue::LocalDateTime { nanos_since_epoch } => {
418 let ndt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch).naive_utc();
419 write!(
420 f,
421 "{}T{}",
422 ndt.date().format("%Y-%m-%d"),
423 format_naive_time(&ndt.time())
424 )
425 }
426 TemporalValue::DateTime {
427 nanos_since_epoch,
428 offset_seconds,
429 timezone_name,
430 } => {
431 let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
433 let ndt = chrono::DateTime::from_timestamp_nanos(local_nanos).naive_utc();
434 let tz = format_offset(*offset_seconds);
435 write!(
436 f,
437 "{}T{}{}",
438 ndt.date().format("%Y-%m-%d"),
439 format_naive_time(&ndt.time()),
440 tz
441 )?;
442 if let Some(name) = timezone_name {
443 write!(f, "[{}]", name)?;
444 }
445 Ok(())
446 }
447 TemporalValue::Duration {
448 months,
449 days,
450 nanos,
451 } => {
452 write!(f, "P")?;
453 let years = months / 12;
454 let rem_months = months % 12;
455 if years != 0 {
456 write!(f, "{}Y", years)?;
457 }
458 if rem_months != 0 {
459 write!(f, "{}M", rem_months)?;
460 }
461 if *days != 0 {
462 write!(f, "{}D", days)?;
463 }
464 let abs_nanos = nanos.unsigned_abs() as i128;
466 let nanos_sign = if *nanos < 0 { -1i64 } else { 1 };
467 let total_secs = (abs_nanos / 1_000_000_000) as i64;
468 let frac_nanos = (abs_nanos % 1_000_000_000) as u32;
469 let hours = total_secs / 3600;
470 let mins = (total_secs % 3600) / 60;
471 let secs = total_secs % 60;
472
473 if hours != 0 || mins != 0 || secs != 0 || frac_nanos != 0 {
474 write!(f, "T")?;
475 if hours != 0 {
476 write!(f, "{}H", hours * nanos_sign)?;
477 }
478 if mins != 0 {
479 write!(f, "{}M", mins * nanos_sign)?;
480 }
481 if secs != 0 || frac_nanos != 0 {
482 let frac = format_fractional(frac_nanos);
483 if nanos_sign < 0 && (secs != 0 || frac_nanos != 0) {
484 write!(f, "-{}{}", secs, frac)?;
485 } else {
486 write!(f, "{}{}", secs, frac)?;
487 }
488 write!(f, "S")?;
489 }
490 } else if years == 0 && rem_months == 0 && *days == 0 {
491 write!(f, "T0S")?;
493 }
494 Ok(())
495 }
496 TemporalValue::Btic { lo, hi, meta } => match uni_btic::Btic::new(*lo, *hi, *meta) {
497 Ok(btic) => write!(f, "{btic}"),
498 Err(_) => write!(f, "Btic[lo={lo}, hi={hi}, meta={meta:#x}]"),
499 },
500 }
501 }
502}
503
504use chrono::Datelike as _;
506use chrono::Timelike as _;
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
521#[serde(untagged)]
522#[non_exhaustive]
523pub enum Value {
524 Null,
526 Bool(bool),
528 Int(i64),
530 Float(f64),
532 String(String),
534 Bytes(Vec<u8>),
536 List(Vec<Value>),
538 Map(HashMap<String, Value>),
540
541 Node(Node),
544 Edge(Edge),
546 Path(Path),
548
549 Vector(Vec<f32>),
552
553 SparseVector {
560 indices: Vec<u32>,
562 values: Vec<f32>,
564 },
565
566 Temporal(TemporalValue),
569}
570
571impl Value {
576 pub fn is_null(&self) -> bool {
578 matches!(self, Value::Null)
579 }
580
581 pub fn as_bool(&self) -> Option<bool> {
583 match self {
584 Value::Bool(b) => Some(*b),
585 _ => None,
586 }
587 }
588
589 pub fn as_i64(&self) -> Option<i64> {
591 match self {
592 Value::Int(i) => Some(*i),
593 _ => None,
594 }
595 }
596
597 pub fn as_u64(&self) -> Option<u64> {
599 match self {
600 Value::Int(i) if *i >= 0 => Some(*i as u64),
601 _ => None,
602 }
603 }
604
605 pub fn as_f64(&self) -> Option<f64> {
609 match self {
610 Value::Float(f) => Some(*f),
611 Value::Int(i) => Some(*i as f64),
612 _ => None,
613 }
614 }
615
616 pub fn as_str(&self) -> Option<&str> {
618 match self {
619 Value::String(s) => Some(s),
620 _ => None,
621 }
622 }
623
624 pub fn is_i64(&self) -> bool {
626 matches!(self, Value::Int(_))
627 }
628
629 pub fn is_f64(&self) -> bool {
631 matches!(self, Value::Float(_))
632 }
633
634 pub fn is_string(&self) -> bool {
636 matches!(self, Value::String(_))
637 }
638
639 pub fn is_number(&self) -> bool {
641 matches!(self, Value::Int(_) | Value::Float(_))
642 }
643
644 pub fn as_array(&self) -> Option<&Vec<Value>> {
646 match self {
647 Value::List(l) => Some(l),
648 _ => None,
649 }
650 }
651
652 pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
654 match self {
655 Value::Map(m) => Some(m),
656 _ => None,
657 }
658 }
659
660 pub fn is_bool(&self) -> bool {
662 matches!(self, Value::Bool(_))
663 }
664
665 pub fn is_list(&self) -> bool {
667 matches!(self, Value::List(_))
668 }
669
670 pub fn is_map(&self) -> bool {
672 matches!(self, Value::Map(_))
673 }
674
675 pub fn get(&self, key: &str) -> Option<&Value> {
679 match self {
680 Value::Map(m) => m.get(key),
681 _ => None,
682 }
683 }
684
685 pub fn is_temporal(&self) -> bool {
687 matches!(self, Value::Temporal(_))
688 }
689
690 pub fn as_temporal(&self) -> Option<&TemporalValue> {
692 match self {
693 Value::Temporal(t) => Some(t),
694 _ => None,
695 }
696 }
697}
698
699impl fmt::Display for Value {
700 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701 match self {
702 Value::Null => write!(f, "null"),
703 Value::Bool(b) => write!(f, "{b}"),
704 Value::Int(i) => write!(f, "{i}"),
705 Value::Float(v) => {
706 if v.fract() == 0.0 && v.is_finite() {
707 write!(f, "{v:.1}")
708 } else {
709 write!(f, "{v}")
710 }
711 }
712 Value::String(s) => write!(f, "{s}"),
713 Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
714 Value::List(l) => {
715 write!(f, "[")?;
716 for (i, item) in l.iter().enumerate() {
717 if i > 0 {
718 write!(f, ", ")?;
719 }
720 write!(f, "{item}")?;
721 }
722 write!(f, "]")
723 }
724 Value::Map(m) => {
725 write!(f, "{{")?;
726 for (i, (k, v)) in m.iter().enumerate() {
727 if i > 0 {
728 write!(f, ", ")?;
729 }
730 write!(f, "{k}: {v}")?;
731 }
732 write!(f, "}}")
733 }
734 Value::Node(n) => write!(f, "(:{} {{vid: {}}})", n.labels.join(":"), n.vid),
735 Value::Edge(e) => write!(f, "-[:{}]-", e.edge_type),
736 Value::Path(p) => write!(
737 f,
738 "<path: {} nodes, {} edges>",
739 p.nodes.len(),
740 p.edges.len()
741 ),
742 Value::Vector(v) => write!(f, "<vector: {} dims>", v.len()),
743 Value::SparseVector { indices, .. } => {
744 write!(f, "<sparse vector: {} nnz>", indices.len())
745 }
746 Value::Temporal(t) => write!(f, "{t}"),
747 }
748 }
749}
750
751fn float_eq_normalized(a: f64, b: f64) -> bool {
762 a.total_cmp(&b) == std::cmp::Ordering::Equal
763 || (a == 0.0 && b == 0.0)
764 || (a.is_nan() && b.is_nan())
765}
766
767impl PartialEq for Value {
768 fn eq(&self, other: &Self) -> bool {
775 match (self, other) {
776 (Value::Float(a), Value::Float(b)) => float_eq_normalized(*a, *b),
778 (Value::Null, Value::Null) => true,
780 (Value::Bool(a), Value::Bool(b)) => a == b,
781 (Value::Int(a), Value::Int(b)) => a == b,
782 (Value::String(a), Value::String(b)) => a == b,
783 (Value::Bytes(a), Value::Bytes(b)) => a == b,
784 (Value::List(a), Value::List(b)) => a == b,
785 (Value::Map(a), Value::Map(b)) => a == b,
786 (Value::Node(a), Value::Node(b)) => a == b,
787 (Value::Edge(a), Value::Edge(b)) => a == b,
788 (Value::Path(a), Value::Path(b)) => a == b,
789 (Value::Vector(a), Value::Vector(b)) => a == b,
790 (
791 Value::SparseVector {
792 indices: i1,
793 values: v1,
794 },
795 Value::SparseVector {
796 indices: i2,
797 values: v2,
798 },
799 ) => i1 == i2 && v1 == v2,
800 (Value::Temporal(a), Value::Temporal(b)) => a == b,
801 _ => false,
803 }
804 }
805}
806
807impl Eq for Value {}
808
809fn hash_f64_normalized<H: Hasher>(f: f64, state: &mut H) {
814 let bits = if f == 0.0 {
815 0.0f64.to_bits()
816 } else if f.is_nan() {
817 f64::NAN.to_bits()
818 } else {
819 f.to_bits()
820 };
821 bits.hash(state);
822}
823
824impl Hash for Value {
825 fn hash<H: Hasher>(&self, state: &mut H) {
826 std::mem::discriminant(self).hash(state);
828 match self {
829 Value::Null => {}
830 Value::Bool(b) => b.hash(state),
831 Value::Int(i) => i.hash(state),
832 Value::Float(f) => hash_f64_normalized(*f, state),
836 Value::String(s) => s.hash(state),
837 Value::Bytes(b) => b.hash(state),
838 Value::List(l) => l.hash(state),
839 Value::Map(m) => hash_map(m, state),
840 Value::Node(n) => n.hash(state),
841 Value::Edge(e) => e.hash(state),
842 Value::Path(p) => p.hash(state),
843 Value::Vector(v) => {
844 v.len().hash(state);
847 for f in v {
848 let bits = if *f == 0.0f32 {
849 0.0f32.to_bits()
850 } else if f.is_nan() {
851 f32::NAN.to_bits()
852 } else {
853 f.to_bits()
854 };
855 bits.hash(state);
856 }
857 }
858 Value::SparseVector { indices, values } => {
859 indices.hash(state);
863 values.len().hash(state);
864 for f in values {
865 let bits = if *f == 0.0f32 {
866 0.0f32.to_bits()
867 } else if f.is_nan() {
868 f32::NAN.to_bits()
869 } else {
870 f.to_bits()
871 };
872 bits.hash(state);
873 }
874 }
875 Value::Temporal(t) => t.hash(state),
876 }
877 }
878}
879
880fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
886 let mut pairs: Vec<_> = m.iter().collect();
887 pairs.sort_by_key(|(k, _)| *k);
888 pairs.len().hash(state);
889 for (k, v) in pairs {
890 k.hash(state);
891 v.hash(state);
892 }
893}
894
895#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
897pub struct Node {
898 pub vid: Vid,
900 pub labels: Vec<String>,
902 pub properties: HashMap<String, Value>,
904}
905
906impl Hash for Node {
907 fn hash<H: Hasher>(&self, state: &mut H) {
908 self.vid.hash(state);
909 let mut sorted_labels = self.labels.clone();
910 sorted_labels.sort();
911 sorted_labels.hash(state);
912 hash_map(&self.properties, state);
913 }
914}
915
916impl Node {
917 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
924 let val = self
925 .properties
926 .get(property)
927 .ok_or_else(|| UniError::Query {
928 message: format!("Property '{}' not found on node {}", property, self.vid),
929 query: None,
930 })?;
931 T::from_value(val)
932 }
933
934 pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
936 self.properties
937 .get(property)
938 .and_then(|v| T::from_value(v).ok())
939 }
940}
941
942#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
944pub struct Edge {
945 pub eid: Eid,
947 pub edge_type: String,
949 pub src: Vid,
951 pub dst: Vid,
953 pub properties: HashMap<String, Value>,
955}
956
957impl Hash for Edge {
958 fn hash<H: Hasher>(&self, state: &mut H) {
959 self.eid.hash(state);
960 self.edge_type.hash(state);
961 self.src.hash(state);
962 self.dst.hash(state);
963 hash_map(&self.properties, state);
964 }
965}
966
967impl Edge {
968 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
975 let val = self
976 .properties
977 .get(property)
978 .ok_or_else(|| UniError::Query {
979 message: format!("Property '{}' not found on edge {}", property, self.eid),
980 query: None,
981 })?;
982 T::from_value(val)
983 }
984}
985
986#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
988pub struct Path {
989 pub nodes: Vec<Node>,
991 #[serde(rename = "relationships")]
993 pub edges: Vec<Edge>,
994}
995
996impl Path {
997 pub fn nodes(&self) -> &[Node] {
999 &self.nodes
1000 }
1001
1002 pub fn edges(&self) -> &[Edge] {
1004 &self.edges
1005 }
1006
1007 pub fn len(&self) -> usize {
1009 self.edges.len()
1010 }
1011
1012 pub fn is_empty(&self) -> bool {
1014 self.edges.is_empty()
1015 }
1016
1017 pub fn start(&self) -> Option<&Node> {
1019 self.nodes.first()
1020 }
1021
1022 pub fn end(&self) -> Option<&Node> {
1024 self.nodes.last()
1025 }
1026}
1027
1028pub trait FromValue: Sized {
1034 fn from_value(value: &Value) -> crate::Result<Self>;
1040}
1041
1042impl<T> FromValue for T
1044where
1045 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1046{
1047 fn from_value(value: &Value) -> crate::Result<Self> {
1048 Self::try_from(value)
1049 }
1050}
1051
1052macro_rules! impl_try_from_value_owned {
1057 ($($t:ty),+ $(,)?) => {
1058 $(
1059 impl TryFrom<Value> for $t {
1060 type Error = UniError;
1061 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1062 Self::try_from(&value)
1063 }
1064 }
1065 )+
1066 };
1067}
1068
1069impl_try_from_value_owned!(
1070 String,
1071 i64,
1072 i32,
1073 f64,
1074 bool,
1075 Vid,
1076 Eid,
1077 Vec<f32>,
1078 Path,
1079 Node,
1080 Edge
1081);
1082
1083fn type_error(expected: &str, value: &Value) -> UniError {
1089 UniError::Type {
1090 expected: expected.to_string(),
1091 actual: format!("{:?}", value),
1092 }
1093}
1094
1095impl TryFrom<&Value> for String {
1096 type Error = UniError;
1097
1098 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1099 match value {
1100 Value::String(s) => Ok(s.clone()),
1101 Value::Int(i) => Ok(i.to_string()),
1102 Value::Float(f) => Ok(f.to_string()),
1103 Value::Bool(b) => Ok(b.to_string()),
1104 Value::Temporal(t) => Ok(t.to_string()),
1105 _ => Err(type_error("String", value)),
1106 }
1107 }
1108}
1109
1110impl TryFrom<&Value> for i64 {
1111 type Error = UniError;
1112
1113 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1120 match value {
1121 Value::Int(i) => Ok(*i),
1122 Value::Float(f) => Ok(*f as i64),
1123 _ => Err(type_error("Int", value)),
1124 }
1125 }
1126}
1127
1128impl TryFrom<&Value> for i32 {
1129 type Error = UniError;
1130
1131 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1136 match value {
1137 Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
1138 expected: "i32".to_string(),
1139 actual: format!("Integer {} out of range", i),
1140 }),
1141 Value::Float(f) => {
1142 if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1143 return Err(UniError::Type {
1144 expected: "i32".to_string(),
1145 actual: format!("Float {} out of range", f),
1146 });
1147 }
1148 if f.fract() != 0.0 {
1149 return Err(UniError::Type {
1150 expected: "i32".to_string(),
1151 actual: format!("Float {} has fractional part", f),
1152 });
1153 }
1154 Ok(*f as i32)
1155 }
1156 _ => Err(type_error("Int", value)),
1157 }
1158 }
1159}
1160
1161impl TryFrom<&Value> for f64 {
1162 type Error = UniError;
1163
1164 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1165 match value {
1166 Value::Float(f) => Ok(*f),
1167 Value::Int(i) => Ok(*i as f64),
1168 _ => Err(type_error("Float", value)),
1169 }
1170 }
1171}
1172
1173impl TryFrom<&Value> for bool {
1174 type Error = UniError;
1175
1176 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1177 match value {
1178 Value::Bool(b) => Ok(*b),
1179 _ => Err(type_error("Bool", value)),
1180 }
1181 }
1182}
1183
1184impl TryFrom<&Value> for Vid {
1185 type Error = UniError;
1186
1187 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1188 match value {
1189 Value::Node(n) => Ok(n.vid),
1190 Value::String(s) => {
1191 if let Ok(id) = s.parse::<u64>() {
1192 return Ok(Vid::new(id));
1193 }
1194 Err(UniError::Type {
1195 expected: "Vid".into(),
1196 actual: s.clone(),
1197 })
1198 }
1199 Value::Int(i) => Ok(Vid::new(*i as u64)),
1200 _ => Err(type_error("Vid", value)),
1201 }
1202 }
1203}
1204
1205impl TryFrom<&Value> for Eid {
1206 type Error = UniError;
1207
1208 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1209 match value {
1210 Value::Edge(e) => Ok(e.eid),
1211 Value::String(s) => {
1212 if let Ok(id) = s.parse::<u64>() {
1213 return Ok(Eid::new(id));
1214 }
1215 Err(UniError::Type {
1216 expected: "Eid".into(),
1217 actual: s.clone(),
1218 })
1219 }
1220 Value::Int(i) => Ok(Eid::new(*i as u64)),
1221 _ => Err(type_error("Eid", value)),
1222 }
1223 }
1224}
1225
1226impl TryFrom<&Value> for Vec<f32> {
1227 type Error = UniError;
1228
1229 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1230 match value {
1231 Value::Vector(v) => Ok(v.clone()),
1232 Value::List(l) => {
1233 let mut vec = Vec::with_capacity(l.len());
1234 for item in l {
1235 match item {
1236 Value::Float(f) => vec.push(*f as f32),
1237 Value::Int(i) => vec.push(*i as f32),
1238 _ => return Err(type_error("Float", item)),
1239 }
1240 }
1241 Ok(vec)
1242 }
1243 _ => Err(type_error("Vector", value)),
1244 }
1245 }
1246}
1247
1248impl<T> TryFrom<&Value> for Option<T>
1249where
1250 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1251{
1252 type Error = UniError;
1253
1254 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1255 match value {
1256 Value::Null => Ok(None),
1257 _ => T::try_from(value).map(Some),
1258 }
1259 }
1260}
1261
1262impl<T> TryFrom<Value> for Option<T>
1263where
1264 T: TryFrom<Value, Error = UniError>,
1265{
1266 type Error = UniError;
1267 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1268 match value {
1269 Value::Null => Ok(None),
1270 _ => T::try_from(value).map(Some),
1271 }
1272 }
1273}
1274
1275impl<T> TryFrom<&Value> for Vec<T>
1276where
1277 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1278{
1279 type Error = UniError;
1280
1281 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1282 match value {
1283 Value::List(l) => {
1284 let mut vec = Vec::with_capacity(l.len());
1285 for item in l {
1286 vec.push(T::try_from(item)?);
1287 }
1288 Ok(vec)
1289 }
1290 _ => Err(type_error("List", value)),
1291 }
1292 }
1293}
1294
1295impl<T> TryFrom<Value> for Vec<T>
1296where
1297 T: TryFrom<Value, Error = UniError>,
1298{
1299 type Error = UniError;
1300 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1301 match value {
1302 Value::List(l) => {
1303 let mut vec = Vec::with_capacity(l.len());
1304 for item in l {
1305 vec.push(T::try_from(item)?);
1306 }
1307 Ok(vec)
1308 }
1309 other => Err(type_error("List", &other)),
1310 }
1311 }
1312}
1313
1314fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1320 keys.iter().find_map(|k| map.get(*k))
1321}
1322
1323fn extract_properties(value: &Value) -> HashMap<String, Value> {
1325 match value {
1326 Value::Map(m) => m.clone(),
1327 _ => HashMap::new(),
1328 }
1329}
1330
1331impl TryFrom<&Value> for Node {
1332 type Error = UniError;
1333
1334 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1335 match value {
1336 Value::Node(n) => Ok(n.clone()),
1337 Value::Map(m) => {
1338 let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1339 let props_val = m.get("properties");
1340
1341 let (Some(v), Some(p)) = (vid_val, props_val) else {
1342 return Err(type_error("Node Map", value));
1343 };
1344
1345 let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1347 label_list
1348 .iter()
1349 .filter_map(|v| {
1350 if let Value::String(s) = v {
1351 Some(s.clone())
1352 } else {
1353 None
1354 }
1355 })
1356 .collect()
1357 } else {
1358 Vec::new()
1359 };
1360
1361 Ok(Node {
1362 vid: Vid::try_from(v)?,
1363 labels,
1364 properties: extract_properties(p),
1365 })
1366 }
1367 _ => Err(type_error("Node", value)),
1368 }
1369 }
1370}
1371
1372impl TryFrom<&Value> for Edge {
1373 type Error = UniError;
1374
1375 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1376 match value {
1377 Value::Edge(e) => Ok(e.clone()),
1378 Value::Map(m) => {
1379 let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1380 let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1381 let src_val = get_with_fallback(m, &["_src", "src"]);
1382 let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1383 let props_val = m.get("properties");
1384
1385 let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1386 (eid_val, type_val, src_val, dst_val, props_val)
1387 else {
1388 return Err(type_error("Edge Map", value));
1389 };
1390
1391 Ok(Edge {
1392 eid: Eid::try_from(id)?,
1393 edge_type: String::try_from(t)?,
1394 src: Vid::try_from(s)?,
1395 dst: Vid::try_from(d)?,
1396 properties: extract_properties(p),
1397 })
1398 }
1399 _ => Err(type_error("Edge", value)),
1400 }
1401 }
1402}
1403
1404impl TryFrom<&Value> for Path {
1405 type Error = UniError;
1406
1407 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1408 match value {
1409 Value::Path(p) => Ok(p.clone()),
1410 Value::Map(m) => {
1411 let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1412 (m.get("nodes"), m.get("relationships"))
1413 else {
1414 return Err(type_error("Path (Map with nodes/relationships)", value));
1415 };
1416
1417 let nodes = nodes_list
1418 .iter()
1419 .map(Node::try_from)
1420 .collect::<std::result::Result<Vec<_>, _>>()?;
1421
1422 let edges = rels_list
1423 .iter()
1424 .map(Edge::try_from)
1425 .collect::<std::result::Result<Vec<_>, _>>()?;
1426
1427 Ok(Path { nodes, edges })
1428 }
1429 _ => Err(type_error("Path", value)),
1430 }
1431 }
1432}
1433
1434impl From<String> for Value {
1439 fn from(v: String) -> Self {
1440 Value::String(v)
1441 }
1442}
1443
1444impl From<&str> for Value {
1445 fn from(v: &str) -> Self {
1446 Value::String(v.to_string())
1447 }
1448}
1449
1450impl From<i64> for Value {
1451 fn from(v: i64) -> Self {
1452 Value::Int(v)
1453 }
1454}
1455
1456impl From<i32> for Value {
1457 fn from(v: i32) -> Self {
1458 Value::Int(v as i64)
1459 }
1460}
1461
1462impl From<f64> for Value {
1463 fn from(v: f64) -> Self {
1464 Value::Float(v)
1465 }
1466}
1467
1468impl From<bool> for Value {
1469 fn from(v: bool) -> Self {
1470 Value::Bool(v)
1471 }
1472}
1473
1474impl From<Vec<f32>> for Value {
1475 fn from(v: Vec<f32>) -> Self {
1476 Value::Vector(v)
1477 }
1478}
1479
1480impl From<serde_json::Value> for Value {
1485 fn from(v: serde_json::Value) -> Self {
1486 match v {
1487 serde_json::Value::Null => Value::Null,
1488 serde_json::Value::Bool(b) => Value::Bool(b),
1489 serde_json::Value::Number(n) => {
1490 if let Some(i) = n.as_i64() {
1491 Value::Int(i)
1492 } else if let Some(f) = n.as_f64() {
1493 Value::Float(f)
1494 } else {
1495 Value::Null
1496 }
1497 }
1498 serde_json::Value::String(s) => Value::String(s),
1499 serde_json::Value::Array(arr) => {
1500 Value::List(arr.into_iter().map(Value::from).collect())
1501 }
1502 serde_json::Value::Object(obj) => {
1503 Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1504 }
1505 }
1506 }
1507}
1508
1509impl From<Value> for serde_json::Value {
1510 fn from(v: Value) -> Self {
1511 match v {
1512 Value::Null => serde_json::Value::Null,
1513 Value::Bool(b) => serde_json::Value::Bool(b),
1514 Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1515 Value::Float(f) => serde_json::Number::from_f64(f)
1516 .map(serde_json::Value::Number)
1517 .unwrap_or(serde_json::Value::Null), Value::String(s) => serde_json::Value::String(s),
1519 Value::Bytes(b) => {
1520 use base64::Engine;
1521 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1522 }
1523 Value::List(l) => {
1524 serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1525 }
1526 Value::Map(m) => {
1527 let mut map = serde_json::Map::new();
1528 for (k, v) in m {
1529 map.insert(k, v.into());
1530 }
1531 serde_json::Value::Object(map)
1532 }
1533 Value::Node(n) => {
1534 let mut map = serde_json::Map::new();
1535 map.insert(
1536 "_id".to_string(),
1537 serde_json::Value::String(n.vid.to_string()),
1538 );
1539 map.insert(
1540 "_labels".to_string(),
1541 serde_json::Value::Array(
1542 n.labels
1543 .into_iter()
1544 .map(serde_json::Value::String)
1545 .collect(),
1546 ),
1547 );
1548 let props: serde_json::Value = Value::Map(n.properties).into();
1549 map.insert("properties".to_string(), props);
1550 serde_json::Value::Object(map)
1551 }
1552 Value::Edge(e) => {
1553 let mut map = serde_json::Map::new();
1554 map.insert(
1555 "_id".to_string(),
1556 serde_json::Value::String(e.eid.to_string()),
1557 );
1558 map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1559 map.insert(
1560 "_src".to_string(),
1561 serde_json::Value::String(e.src.to_string()),
1562 );
1563 map.insert(
1564 "_dst".to_string(),
1565 serde_json::Value::String(e.dst.to_string()),
1566 );
1567 let props: serde_json::Value = Value::Map(e.properties).into();
1568 map.insert("properties".to_string(), props);
1569 serde_json::Value::Object(map)
1570 }
1571 Value::Path(p) => {
1572 let mut map = serde_json::Map::new();
1573 map.insert(
1574 "nodes".to_string(),
1575 Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1576 );
1577 map.insert(
1578 "relationships".to_string(),
1579 Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1580 );
1581 serde_json::Value::Object(map)
1582 }
1583 Value::Vector(v) => serde_json::Value::Array(
1584 v.into_iter()
1585 .map(|f| {
1586 serde_json::Number::from_f64(f as f64)
1587 .map(serde_json::Value::Number)
1588 .unwrap_or(serde_json::Value::Null)
1589 })
1590 .collect(),
1591 ),
1592 Value::SparseVector { indices, values } => {
1593 let idx = serde_json::Value::Array(
1594 indices
1595 .into_iter()
1596 .map(|i| serde_json::Value::Number(serde_json::Number::from(i)))
1597 .collect(),
1598 );
1599 let vals = serde_json::Value::Array(
1600 values
1601 .into_iter()
1602 .map(|f| {
1603 serde_json::Number::from_f64(f as f64)
1604 .map(serde_json::Value::Number)
1605 .unwrap_or(serde_json::Value::Null)
1606 })
1607 .collect(),
1608 );
1609 let mut map = serde_json::Map::new();
1610 map.insert("indices".to_string(), idx);
1611 map.insert("values".to_string(), vals);
1612 serde_json::Value::Object(map)
1613 }
1614 Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1615 }
1616 }
1617}
1618
1619#[macro_export]
1641macro_rules! unival {
1642 (null) => {
1644 $crate::Value::Null
1645 };
1646
1647 (true) => {
1649 $crate::Value::Bool(true)
1650 };
1651 (false) => {
1652 $crate::Value::Bool(false)
1653 };
1654
1655 ([ $($elem:tt),* $(,)? ]) => {
1657 $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1658 };
1659
1660 ({ $($key:tt : $val:tt),* $(,)? }) => {
1662 $crate::Value::Map({
1663 #[allow(unused_mut)]
1664 let mut map = ::std::collections::HashMap::new();
1665 $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1666 map
1667 })
1668 };
1669
1670 ($e:expr) => {
1672 $crate::Value::from($e)
1673 };
1674}
1675
1676impl From<usize> for Value {
1681 fn from(v: usize) -> Self {
1682 Value::Int(v as i64)
1683 }
1684}
1685
1686impl From<u64> for Value {
1687 fn from(v: u64) -> Self {
1688 Value::Int(v as i64)
1689 }
1690}
1691
1692impl From<f32> for Value {
1693 fn from(v: f32) -> Self {
1694 Value::Float(v as f64)
1695 }
1696}
1697
1698#[cfg(test)]
1703mod tests {
1704 use super::*;
1705
1706 #[test]
1707 fn test_accessor_methods() {
1708 assert!(Value::Null.is_null());
1709 assert!(!Value::Int(1).is_null());
1710
1711 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1712 assert_eq!(Value::Int(42).as_bool(), None);
1713
1714 assert_eq!(Value::Int(42).as_i64(), Some(42));
1715 assert_eq!(Value::Float(2.5).as_i64(), None);
1716
1717 assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1719 assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1720 assert_eq!(Value::String("x".into()).as_f64(), None);
1721
1722 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1723 assert_eq!(Value::Int(1).as_str(), None);
1724
1725 assert!(Value::Int(1).is_i64());
1726 assert!(!Value::Float(1.0).is_i64());
1727
1728 assert!(Value::Float(1.0).is_f64());
1729 assert!(!Value::Int(1).is_f64());
1730
1731 assert!(Value::Int(1).is_number());
1732 assert!(Value::Float(1.0).is_number());
1733 assert!(!Value::String("x".into()).is_number());
1734 }
1735
1736 #[test]
1737 fn test_serde_json_roundtrip() {
1738 let val = Value::Int(42);
1739 let json: serde_json::Value = val.clone().into();
1740 let back: Value = json.into();
1741 assert_eq!(val, back);
1742
1743 let val = Value::Float(2.5);
1744 let json: serde_json::Value = val.clone().into();
1745 let back: Value = json.into();
1746 assert_eq!(val, back);
1747
1748 let val = Value::String("hello".into());
1749 let json: serde_json::Value = val.clone().into();
1750 let back: Value = json.into();
1751 assert_eq!(val, back);
1752
1753 let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1754 let json: serde_json::Value = val.clone().into();
1755 let back: Value = json.into();
1756 assert_eq!(val, back);
1757 }
1758
1759 #[test]
1760 fn test_unival_macro() {
1761 assert_eq!(unival!(null), Value::Null);
1762 assert_eq!(unival!(true), Value::Bool(true));
1763 assert_eq!(unival!(false), Value::Bool(false));
1764 assert_eq!(unival!(42_i64), Value::Int(42));
1765 assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1766 assert_eq!(unival!("hello"), Value::String("hello".into()));
1767
1768 let list = unival!([1_i64, 2_i64]);
1770 assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1771
1772 let map = unival!({"key": "val", "num": 42_i64});
1774 if let Value::Map(m) = &map {
1775 assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1776 assert_eq!(m.get("num"), Some(&Value::Int(42)));
1777 } else {
1778 panic!("Expected Map");
1779 }
1780
1781 let x: i64 = 99;
1783 assert_eq!(unival!(x), Value::Int(99));
1784 }
1785
1786 #[test]
1787 fn test_int_float_distinction_preserved() {
1788 let int_val = Value::Int(42);
1790 let float_val = Value::Float(42.0);
1791
1792 assert!(int_val.is_i64());
1793 assert!(!int_val.is_f64());
1794
1795 assert!(float_val.is_f64());
1796 assert!(!float_val.is_i64());
1797
1798 assert_ne!(int_val, float_val);
1800 }
1801
1802 #[test]
1803 fn test_temporal_display_zero_seconds_omitted() {
1804 let lt = TemporalValue::LocalTime {
1806 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1807 };
1808 assert_eq!(lt.to_string(), "12:00");
1809
1810 let lt2 = TemporalValue::LocalTime {
1812 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1813 };
1814 assert_eq!(lt2.to_string(), "12:31:14");
1815
1816 let lt3 = TemporalValue::LocalTime {
1818 nanos_since_midnight: 500_000_000,
1819 };
1820 assert_eq!(lt3.to_string(), "00:00:00.5");
1821
1822 let t = TemporalValue::Time {
1824 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1825 offset_seconds: 0,
1826 };
1827 assert_eq!(t.to_string(), "12:00Z");
1828
1829 let t2 = TemporalValue::Time {
1831 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1832 offset_seconds: 3600,
1833 };
1834 assert_eq!(t2.to_string(), "12:31:14+01:00");
1835
1836 let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1838 .unwrap()
1839 .and_hms_opt(12, 31, 0)
1840 .unwrap()
1841 .and_utc()
1842 .timestamp_nanos_opt()
1843 .unwrap();
1844 let ldt = TemporalValue::LocalDateTime {
1845 nanos_since_epoch: epoch_nanos,
1846 };
1847 assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1848
1849 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1851 .unwrap()
1852 .and_hms_opt(11, 31, 0)
1853 .unwrap()
1854 .and_utc()
1855 .timestamp_nanos_opt()
1856 .unwrap();
1857 let dt = TemporalValue::DateTime {
1858 nanos_since_epoch: utc_nanos,
1859 offset_seconds: 3600,
1860 timezone_name: None,
1861 };
1862 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1863
1864 let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1866 .unwrap()
1867 .and_hms_nano_opt(20, 40, 32, 142_000_000)
1868 .unwrap()
1869 .and_utc()
1870 .timestamp_nanos_opt()
1871 .unwrap();
1872 let dt2 = TemporalValue::DateTime {
1873 nanos_since_epoch: utc_nanos2,
1874 offset_seconds: 3600,
1875 timezone_name: None,
1876 };
1877 assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1878
1879 let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1881 .unwrap()
1882 .and_hms_opt(12, 31, 0)
1883 .unwrap()
1884 .and_utc()
1885 .timestamp_nanos_opt()
1886 .unwrap();
1887 let dt3 = TemporalValue::DateTime {
1888 nanos_since_epoch: utc_nanos3,
1889 offset_seconds: 0,
1890 timezone_name: None,
1891 };
1892 assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1893 }
1894
1895 #[test]
1896 fn test_temporal_display_fractional_trailing_zeros_stripped() {
1897 let d = TemporalValue::Duration {
1899 months: 0,
1900 days: 0,
1901 nanos: 900_000_000,
1902 };
1903 assert_eq!(d.to_string(), "PT0.9S");
1904
1905 let d2 = TemporalValue::Duration {
1907 months: 0,
1908 days: 0,
1909 nanos: 400_000_000,
1910 };
1911 assert_eq!(d2.to_string(), "PT0.4S");
1912
1913 let d3 = TemporalValue::Duration {
1915 months: 0,
1916 days: 0,
1917 nanos: 142_000_000,
1918 };
1919 assert_eq!(d3.to_string(), "PT0.142S");
1920
1921 let d4 = TemporalValue::Duration {
1923 months: 0,
1924 days: 0,
1925 nanos: 1,
1926 };
1927 assert_eq!(d4.to_string(), "PT0.000000001S");
1928 }
1929
1930 #[test]
1931 fn test_temporal_display_offset_second_precision() {
1932 let t = TemporalValue::Time {
1934 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1935 offset_seconds: 2 * 3600 + 5 * 60 + 59,
1936 };
1937 assert_eq!(t.to_string(), "12:00+02:05:59");
1938
1939 let t2 = TemporalValue::Time {
1941 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1942 offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1943 };
1944 assert_eq!(t2.to_string(), "12:00-02:05:07");
1945 }
1946
1947 #[test]
1948 fn test_temporal_display_datetime_with_timezone_name() {
1949 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1950 .unwrap()
1951 .and_hms_opt(11, 31, 0)
1952 .unwrap()
1953 .and_utc()
1954 .timestamp_nanos_opt()
1955 .unwrap();
1956 let dt = TemporalValue::DateTime {
1957 nanos_since_epoch: utc_nanos,
1958 offset_seconds: 3600,
1959 timezone_name: Some("Europe/Stockholm".to_string()),
1960 };
1961 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
1962 }
1963
1964 #[test]
1971 fn value_hash_eq_contract_float_signed_zero() {
1972 use std::collections::hash_map::DefaultHasher;
1973 use std::hash::{Hash, Hasher};
1974
1975 fn h(v: &Value) -> u64 {
1976 let mut s = DefaultHasher::new();
1977 v.hash(&mut s);
1978 s.finish()
1979 }
1980
1981 let pos = Value::Float(0.0);
1982 let neg = Value::Float(-0.0);
1983 assert_eq!(pos, neg, "0.0 and -0.0 compare equal");
1984 assert_eq!(
1985 h(&pos),
1986 h(&neg),
1987 "equal Values must hash equally (Hash/Eq contract)"
1988 );
1989 }
1990}