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
824fn hash_f32_normalized<H: Hasher>(f: f32, state: &mut H) {
831 let bits = if f == 0.0 {
832 0.0f32.to_bits()
833 } else if f.is_nan() {
834 f32::NAN.to_bits()
835 } else {
836 f.to_bits()
837 };
838 bits.hash(state);
839}
840
841impl Hash for Value {
842 fn hash<H: Hasher>(&self, state: &mut H) {
843 std::mem::discriminant(self).hash(state);
845 match self {
846 Value::Null => {}
847 Value::Bool(b) => b.hash(state),
848 Value::Int(i) => i.hash(state),
849 Value::Float(f) => hash_f64_normalized(*f, state),
853 Value::String(s) => s.hash(state),
854 Value::Bytes(b) => b.hash(state),
855 Value::List(l) => l.hash(state),
856 Value::Map(m) => hash_map(m, state),
857 Value::Node(n) => n.hash(state),
858 Value::Edge(e) => e.hash(state),
859 Value::Path(p) => p.hash(state),
860 Value::Vector(v) => {
861 v.len().hash(state);
864 for f in v {
865 hash_f32_normalized(*f, state);
866 }
867 }
868 Value::SparseVector { indices, values } => {
869 indices.hash(state);
873 values.len().hash(state);
874 for f in values {
875 hash_f32_normalized(*f, state);
876 }
877 }
878 Value::Temporal(t) => t.hash(state),
879 }
880 }
881}
882
883fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
889 let mut pairs: Vec<_> = m.iter().collect();
890 pairs.sort_by_key(|(k, _)| *k);
891 pairs.len().hash(state);
892 for (k, v) in pairs {
893 k.hash(state);
894 v.hash(state);
895 }
896}
897
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
900pub struct Node {
901 pub vid: Vid,
903 pub labels: Vec<String>,
905 pub properties: HashMap<String, Value>,
907}
908
909impl Hash for Node {
910 fn hash<H: Hasher>(&self, state: &mut H) {
911 self.vid.hash(state);
912 let mut sorted_labels = self.labels.clone();
913 sorted_labels.sort();
914 sorted_labels.hash(state);
915 hash_map(&self.properties, state);
916 }
917}
918
919impl Node {
920 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
927 let val = self
928 .properties
929 .get(property)
930 .ok_or_else(|| UniError::Query {
931 message: format!("Property '{}' not found on node {}", property, self.vid),
932 query: None,
933 })?;
934 T::from_value(val)
935 }
936
937 pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
939 self.properties
940 .get(property)
941 .and_then(|v| T::from_value(v).ok())
942 }
943}
944
945#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
947pub struct Edge {
948 pub eid: Eid,
950 pub edge_type: String,
952 pub src: Vid,
954 pub dst: Vid,
956 pub properties: HashMap<String, Value>,
958}
959
960impl Hash for Edge {
961 fn hash<H: Hasher>(&self, state: &mut H) {
962 self.eid.hash(state);
963 self.edge_type.hash(state);
964 self.src.hash(state);
965 self.dst.hash(state);
966 hash_map(&self.properties, state);
967 }
968}
969
970impl Edge {
971 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
978 let val = self
979 .properties
980 .get(property)
981 .ok_or_else(|| UniError::Query {
982 message: format!("Property '{}' not found on edge {}", property, self.eid),
983 query: None,
984 })?;
985 T::from_value(val)
986 }
987}
988
989#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
991pub struct Path {
992 pub nodes: Vec<Node>,
994 #[serde(rename = "relationships")]
996 pub edges: Vec<Edge>,
997}
998
999impl Path {
1000 pub fn nodes(&self) -> &[Node] {
1002 &self.nodes
1003 }
1004
1005 pub fn edges(&self) -> &[Edge] {
1007 &self.edges
1008 }
1009
1010 pub fn len(&self) -> usize {
1012 self.edges.len()
1013 }
1014
1015 pub fn is_empty(&self) -> bool {
1017 self.edges.is_empty()
1018 }
1019
1020 pub fn start(&self) -> Option<&Node> {
1022 self.nodes.first()
1023 }
1024
1025 pub fn end(&self) -> Option<&Node> {
1027 self.nodes.last()
1028 }
1029}
1030
1031pub trait FromValue: Sized {
1037 fn from_value(value: &Value) -> crate::Result<Self>;
1043}
1044
1045impl<T> FromValue for T
1047where
1048 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1049{
1050 fn from_value(value: &Value) -> crate::Result<Self> {
1051 Self::try_from(value)
1052 }
1053}
1054
1055macro_rules! impl_try_from_value_owned {
1060 ($($t:ty),+ $(,)?) => {
1061 $(
1062 impl TryFrom<Value> for $t {
1063 type Error = UniError;
1064 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1065 Self::try_from(&value)
1066 }
1067 }
1068 )+
1069 };
1070}
1071
1072impl_try_from_value_owned!(
1073 String,
1074 i64,
1075 i32,
1076 f64,
1077 bool,
1078 Vid,
1079 Eid,
1080 Vec<f32>,
1081 Path,
1082 Node,
1083 Edge
1084);
1085
1086fn type_error(expected: &str, value: &Value) -> UniError {
1092 UniError::Type {
1093 expected: expected.to_string(),
1094 actual: format!("{:?}", value),
1095 }
1096}
1097
1098impl TryFrom<&Value> for String {
1099 type Error = UniError;
1100
1101 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1102 match value {
1103 Value::String(s) => Ok(s.clone()),
1104 Value::Int(i) => Ok(i.to_string()),
1105 Value::Float(f) => Ok(f.to_string()),
1106 Value::Bool(b) => Ok(b.to_string()),
1107 Value::Temporal(t) => Ok(t.to_string()),
1108 _ => Err(type_error("String", value)),
1109 }
1110 }
1111}
1112
1113impl TryFrom<&Value> for i64 {
1114 type Error = UniError;
1115
1116 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1123 match value {
1124 Value::Int(i) => Ok(*i),
1125 Value::Float(f) => Ok(*f as i64),
1126 _ => Err(type_error("Int", value)),
1127 }
1128 }
1129}
1130
1131impl TryFrom<&Value> for i32 {
1132 type Error = UniError;
1133
1134 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1139 match value {
1140 Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
1141 expected: "i32".to_string(),
1142 actual: format!("Integer {} out of range", i),
1143 }),
1144 Value::Float(f) => {
1145 if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1146 return Err(UniError::Type {
1147 expected: "i32".to_string(),
1148 actual: format!("Float {} out of range", f),
1149 });
1150 }
1151 if f.fract() != 0.0 {
1152 return Err(UniError::Type {
1153 expected: "i32".to_string(),
1154 actual: format!("Float {} has fractional part", f),
1155 });
1156 }
1157 Ok(*f as i32)
1158 }
1159 _ => Err(type_error("Int", value)),
1160 }
1161 }
1162}
1163
1164impl TryFrom<&Value> for f64 {
1165 type Error = UniError;
1166
1167 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1168 match value {
1169 Value::Float(f) => Ok(*f),
1170 Value::Int(i) => Ok(*i as f64),
1171 _ => Err(type_error("Float", value)),
1172 }
1173 }
1174}
1175
1176impl TryFrom<&Value> for bool {
1177 type Error = UniError;
1178
1179 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1180 match value {
1181 Value::Bool(b) => Ok(*b),
1182 _ => Err(type_error("Bool", value)),
1183 }
1184 }
1185}
1186
1187impl TryFrom<&Value> for Vid {
1188 type Error = UniError;
1189
1190 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1191 match value {
1192 Value::Node(n) => Ok(n.vid),
1193 Value::String(s) => {
1194 if let Ok(id) = s.parse::<u64>() {
1195 return Ok(Vid::new(id));
1196 }
1197 Err(UniError::Type {
1198 expected: "Vid".into(),
1199 actual: s.clone(),
1200 })
1201 }
1202 Value::Int(i) => Ok(Vid::new(*i as u64)),
1203 _ => Err(type_error("Vid", value)),
1204 }
1205 }
1206}
1207
1208impl TryFrom<&Value> for Eid {
1209 type Error = UniError;
1210
1211 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1212 match value {
1213 Value::Edge(e) => Ok(e.eid),
1214 Value::String(s) => {
1215 if let Ok(id) = s.parse::<u64>() {
1216 return Ok(Eid::new(id));
1217 }
1218 Err(UniError::Type {
1219 expected: "Eid".into(),
1220 actual: s.clone(),
1221 })
1222 }
1223 Value::Int(i) => Ok(Eid::new(*i as u64)),
1224 _ => Err(type_error("Eid", value)),
1225 }
1226 }
1227}
1228
1229impl TryFrom<&Value> for Vec<f32> {
1230 type Error = UniError;
1231
1232 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1233 match value {
1234 Value::Vector(v) => Ok(v.clone()),
1235 Value::List(l) => {
1236 let mut vec = Vec::with_capacity(l.len());
1237 for item in l {
1238 match item {
1239 Value::Float(f) => vec.push(*f as f32),
1240 Value::Int(i) => vec.push(*i as f32),
1241 _ => return Err(type_error("Float", item)),
1242 }
1243 }
1244 Ok(vec)
1245 }
1246 _ => Err(type_error("Vector", value)),
1247 }
1248 }
1249}
1250
1251impl<T> TryFrom<&Value> for Option<T>
1252where
1253 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1254{
1255 type Error = UniError;
1256
1257 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1258 match value {
1259 Value::Null => Ok(None),
1260 _ => T::try_from(value).map(Some),
1261 }
1262 }
1263}
1264
1265impl<T> TryFrom<Value> for Option<T>
1266where
1267 T: TryFrom<Value, Error = UniError>,
1268{
1269 type Error = UniError;
1270 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1271 match value {
1272 Value::Null => Ok(None),
1273 _ => T::try_from(value).map(Some),
1274 }
1275 }
1276}
1277
1278impl<T> TryFrom<&Value> for Vec<T>
1279where
1280 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1281{
1282 type Error = UniError;
1283
1284 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1285 match value {
1286 Value::List(l) => {
1287 let mut vec = Vec::with_capacity(l.len());
1288 for item in l {
1289 vec.push(T::try_from(item)?);
1290 }
1291 Ok(vec)
1292 }
1293 _ => Err(type_error("List", value)),
1294 }
1295 }
1296}
1297
1298impl<T> TryFrom<Value> for Vec<T>
1299where
1300 T: TryFrom<Value, Error = UniError>,
1301{
1302 type Error = UniError;
1303 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1304 match value {
1305 Value::List(l) => {
1306 let mut vec = Vec::with_capacity(l.len());
1307 for item in l {
1308 vec.push(T::try_from(item)?);
1309 }
1310 Ok(vec)
1311 }
1312 other => Err(type_error("List", &other)),
1313 }
1314 }
1315}
1316
1317fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1323 keys.iter().find_map(|k| map.get(*k))
1324}
1325
1326fn extract_properties(value: &Value) -> HashMap<String, Value> {
1328 match value {
1329 Value::Map(m) => m.clone(),
1330 _ => HashMap::new(),
1331 }
1332}
1333
1334impl TryFrom<&Value> for Node {
1335 type Error = UniError;
1336
1337 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1338 match value {
1339 Value::Node(n) => Ok(n.clone()),
1340 Value::Map(m) => {
1341 let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1342 let props_val = m.get("properties");
1343
1344 let (Some(v), Some(p)) = (vid_val, props_val) else {
1345 return Err(type_error("Node Map", value));
1346 };
1347
1348 let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1350 label_list
1351 .iter()
1352 .filter_map(|v| {
1353 if let Value::String(s) = v {
1354 Some(s.clone())
1355 } else {
1356 None
1357 }
1358 })
1359 .collect()
1360 } else {
1361 Vec::new()
1362 };
1363
1364 Ok(Node {
1365 vid: Vid::try_from(v)?,
1366 labels,
1367 properties: extract_properties(p),
1368 })
1369 }
1370 _ => Err(type_error("Node", value)),
1371 }
1372 }
1373}
1374
1375impl TryFrom<&Value> for Edge {
1376 type Error = UniError;
1377
1378 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1379 match value {
1380 Value::Edge(e) => Ok(e.clone()),
1381 Value::Map(m) => {
1382 let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1383 let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1384 let src_val = get_with_fallback(m, &["_src", "src"]);
1385 let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1386 let props_val = m.get("properties");
1387
1388 let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1389 (eid_val, type_val, src_val, dst_val, props_val)
1390 else {
1391 return Err(type_error("Edge Map", value));
1392 };
1393
1394 Ok(Edge {
1395 eid: Eid::try_from(id)?,
1396 edge_type: String::try_from(t)?,
1397 src: Vid::try_from(s)?,
1398 dst: Vid::try_from(d)?,
1399 properties: extract_properties(p),
1400 })
1401 }
1402 _ => Err(type_error("Edge", value)),
1403 }
1404 }
1405}
1406
1407impl TryFrom<&Value> for Path {
1408 type Error = UniError;
1409
1410 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1411 match value {
1412 Value::Path(p) => Ok(p.clone()),
1413 Value::Map(m) => {
1414 let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1415 (m.get("nodes"), m.get("relationships"))
1416 else {
1417 return Err(type_error("Path (Map with nodes/relationships)", value));
1418 };
1419
1420 let nodes = nodes_list
1421 .iter()
1422 .map(Node::try_from)
1423 .collect::<std::result::Result<Vec<_>, _>>()?;
1424
1425 let edges = rels_list
1426 .iter()
1427 .map(Edge::try_from)
1428 .collect::<std::result::Result<Vec<_>, _>>()?;
1429
1430 Ok(Path { nodes, edges })
1431 }
1432 _ => Err(type_error("Path", value)),
1433 }
1434 }
1435}
1436
1437impl From<String> for Value {
1442 fn from(v: String) -> Self {
1443 Value::String(v)
1444 }
1445}
1446
1447impl From<&str> for Value {
1448 fn from(v: &str) -> Self {
1449 Value::String(v.to_string())
1450 }
1451}
1452
1453impl From<i64> for Value {
1454 fn from(v: i64) -> Self {
1455 Value::Int(v)
1456 }
1457}
1458
1459impl From<i32> for Value {
1460 fn from(v: i32) -> Self {
1461 Value::Int(v as i64)
1462 }
1463}
1464
1465impl From<f64> for Value {
1466 fn from(v: f64) -> Self {
1467 Value::Float(v)
1468 }
1469}
1470
1471impl From<bool> for Value {
1472 fn from(v: bool) -> Self {
1473 Value::Bool(v)
1474 }
1475}
1476
1477impl From<Vec<f32>> for Value {
1478 fn from(v: Vec<f32>) -> Self {
1479 Value::Vector(v)
1480 }
1481}
1482
1483impl From<serde_json::Value> for Value {
1488 fn from(v: serde_json::Value) -> Self {
1489 match v {
1490 serde_json::Value::Null => Value::Null,
1491 serde_json::Value::Bool(b) => Value::Bool(b),
1492 serde_json::Value::Number(n) => {
1493 if let Some(i) = n.as_i64() {
1494 Value::Int(i)
1495 } else if let Some(f) = n.as_f64() {
1496 Value::Float(f)
1497 } else {
1498 Value::Null
1499 }
1500 }
1501 serde_json::Value::String(s) => Value::String(s),
1502 serde_json::Value::Array(arr) => {
1503 Value::List(arr.into_iter().map(Value::from).collect())
1504 }
1505 serde_json::Value::Object(obj) => {
1506 Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1507 }
1508 }
1509 }
1510}
1511
1512impl From<Value> for serde_json::Value {
1513 fn from(v: Value) -> Self {
1514 match v {
1515 Value::Null => serde_json::Value::Null,
1516 Value::Bool(b) => serde_json::Value::Bool(b),
1517 Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1518 Value::Float(f) => serde_json::Number::from_f64(f)
1519 .map(serde_json::Value::Number)
1520 .unwrap_or(serde_json::Value::Null), Value::String(s) => serde_json::Value::String(s),
1522 Value::Bytes(b) => {
1523 use base64::Engine;
1524 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1525 }
1526 Value::List(l) => {
1527 serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1528 }
1529 Value::Map(m) => {
1530 let mut map = serde_json::Map::new();
1531 for (k, v) in m {
1532 map.insert(k, v.into());
1533 }
1534 serde_json::Value::Object(map)
1535 }
1536 Value::Node(n) => {
1537 let mut map = serde_json::Map::new();
1538 map.insert(
1539 "_id".to_string(),
1540 serde_json::Value::String(n.vid.to_string()),
1541 );
1542 map.insert(
1543 "_labels".to_string(),
1544 serde_json::Value::Array(
1545 n.labels
1546 .into_iter()
1547 .map(serde_json::Value::String)
1548 .collect(),
1549 ),
1550 );
1551 let props: serde_json::Value = Value::Map(n.properties).into();
1552 map.insert("properties".to_string(), props);
1553 serde_json::Value::Object(map)
1554 }
1555 Value::Edge(e) => {
1556 let mut map = serde_json::Map::new();
1557 map.insert(
1558 "_id".to_string(),
1559 serde_json::Value::String(e.eid.to_string()),
1560 );
1561 map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1562 map.insert(
1563 "_src".to_string(),
1564 serde_json::Value::String(e.src.to_string()),
1565 );
1566 map.insert(
1567 "_dst".to_string(),
1568 serde_json::Value::String(e.dst.to_string()),
1569 );
1570 let props: serde_json::Value = Value::Map(e.properties).into();
1571 map.insert("properties".to_string(), props);
1572 serde_json::Value::Object(map)
1573 }
1574 Value::Path(p) => {
1575 let mut map = serde_json::Map::new();
1576 map.insert(
1577 "nodes".to_string(),
1578 Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1579 );
1580 map.insert(
1581 "relationships".to_string(),
1582 Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1583 );
1584 serde_json::Value::Object(map)
1585 }
1586 Value::Vector(v) => serde_json::Value::Array(
1587 v.into_iter()
1588 .map(|f| {
1589 serde_json::Number::from_f64(f as f64)
1590 .map(serde_json::Value::Number)
1591 .unwrap_or(serde_json::Value::Null)
1592 })
1593 .collect(),
1594 ),
1595 Value::SparseVector { indices, values } => {
1596 let idx = serde_json::Value::Array(
1597 indices
1598 .into_iter()
1599 .map(|i| serde_json::Value::Number(serde_json::Number::from(i)))
1600 .collect(),
1601 );
1602 let vals = serde_json::Value::Array(
1603 values
1604 .into_iter()
1605 .map(|f| {
1606 serde_json::Number::from_f64(f as f64)
1607 .map(serde_json::Value::Number)
1608 .unwrap_or(serde_json::Value::Null)
1609 })
1610 .collect(),
1611 );
1612 let mut map = serde_json::Map::new();
1613 map.insert("indices".to_string(), idx);
1614 map.insert("values".to_string(), vals);
1615 serde_json::Value::Object(map)
1616 }
1617 Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1618 }
1619 }
1620}
1621
1622#[macro_export]
1644macro_rules! unival {
1645 (null) => {
1647 $crate::Value::Null
1648 };
1649
1650 (true) => {
1652 $crate::Value::Bool(true)
1653 };
1654 (false) => {
1655 $crate::Value::Bool(false)
1656 };
1657
1658 ([ $($elem:tt),* $(,)? ]) => {
1660 $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1661 };
1662
1663 ({ $($key:tt : $val:tt),* $(,)? }) => {
1665 $crate::Value::Map({
1666 #[allow(unused_mut)]
1667 let mut map = ::std::collections::HashMap::new();
1668 $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1669 map
1670 })
1671 };
1672
1673 ($e:expr) => {
1675 $crate::Value::from($e)
1676 };
1677}
1678
1679impl From<usize> for Value {
1684 fn from(v: usize) -> Self {
1685 Value::Int(v as i64)
1686 }
1687}
1688
1689impl From<u64> for Value {
1690 fn from(v: u64) -> Self {
1691 Value::Int(v as i64)
1692 }
1693}
1694
1695impl From<f32> for Value {
1696 fn from(v: f32) -> Self {
1697 Value::Float(v as f64)
1698 }
1699}
1700
1701#[cfg(test)]
1706mod tests {
1707 use super::*;
1708
1709 #[test]
1710 fn test_accessor_methods() {
1711 assert!(Value::Null.is_null());
1712 assert!(!Value::Int(1).is_null());
1713
1714 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1715 assert_eq!(Value::Int(42).as_bool(), None);
1716
1717 assert_eq!(Value::Int(42).as_i64(), Some(42));
1718 assert_eq!(Value::Float(2.5).as_i64(), None);
1719
1720 assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1722 assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1723 assert_eq!(Value::String("x".into()).as_f64(), None);
1724
1725 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1726 assert_eq!(Value::Int(1).as_str(), None);
1727
1728 assert!(Value::Int(1).is_i64());
1729 assert!(!Value::Float(1.0).is_i64());
1730
1731 assert!(Value::Float(1.0).is_f64());
1732 assert!(!Value::Int(1).is_f64());
1733
1734 assert!(Value::Int(1).is_number());
1735 assert!(Value::Float(1.0).is_number());
1736 assert!(!Value::String("x".into()).is_number());
1737 }
1738
1739 #[test]
1740 fn test_serde_json_roundtrip() {
1741 let val = Value::Int(42);
1742 let json: serde_json::Value = val.clone().into();
1743 let back: Value = json.into();
1744 assert_eq!(val, back);
1745
1746 let val = Value::Float(2.5);
1747 let json: serde_json::Value = val.clone().into();
1748 let back: Value = json.into();
1749 assert_eq!(val, back);
1750
1751 let val = Value::String("hello".into());
1752 let json: serde_json::Value = val.clone().into();
1753 let back: Value = json.into();
1754 assert_eq!(val, back);
1755
1756 let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1757 let json: serde_json::Value = val.clone().into();
1758 let back: Value = json.into();
1759 assert_eq!(val, back);
1760 }
1761
1762 #[test]
1763 fn test_unival_macro() {
1764 assert_eq!(unival!(null), Value::Null);
1765 assert_eq!(unival!(true), Value::Bool(true));
1766 assert_eq!(unival!(false), Value::Bool(false));
1767 assert_eq!(unival!(42_i64), Value::Int(42));
1768 assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1769 assert_eq!(unival!("hello"), Value::String("hello".into()));
1770
1771 let list = unival!([1_i64, 2_i64]);
1773 assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1774
1775 let map = unival!({"key": "val", "num": 42_i64});
1777 if let Value::Map(m) = &map {
1778 assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1779 assert_eq!(m.get("num"), Some(&Value::Int(42)));
1780 } else {
1781 panic!("Expected Map");
1782 }
1783
1784 let x: i64 = 99;
1786 assert_eq!(unival!(x), Value::Int(99));
1787 }
1788
1789 #[test]
1790 fn test_int_float_distinction_preserved() {
1791 let int_val = Value::Int(42);
1793 let float_val = Value::Float(42.0);
1794
1795 assert!(int_val.is_i64());
1796 assert!(!int_val.is_f64());
1797
1798 assert!(float_val.is_f64());
1799 assert!(!float_val.is_i64());
1800
1801 assert_ne!(int_val, float_val);
1803 }
1804
1805 #[test]
1806 fn test_temporal_display_zero_seconds_omitted() {
1807 let lt = TemporalValue::LocalTime {
1809 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1810 };
1811 assert_eq!(lt.to_string(), "12:00");
1812
1813 let lt2 = TemporalValue::LocalTime {
1815 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1816 };
1817 assert_eq!(lt2.to_string(), "12:31:14");
1818
1819 let lt3 = TemporalValue::LocalTime {
1821 nanos_since_midnight: 500_000_000,
1822 };
1823 assert_eq!(lt3.to_string(), "00:00:00.5");
1824
1825 let t = TemporalValue::Time {
1827 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1828 offset_seconds: 0,
1829 };
1830 assert_eq!(t.to_string(), "12:00Z");
1831
1832 let t2 = TemporalValue::Time {
1834 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1835 offset_seconds: 3600,
1836 };
1837 assert_eq!(t2.to_string(), "12:31:14+01:00");
1838
1839 let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1841 .unwrap()
1842 .and_hms_opt(12, 31, 0)
1843 .unwrap()
1844 .and_utc()
1845 .timestamp_nanos_opt()
1846 .unwrap();
1847 let ldt = TemporalValue::LocalDateTime {
1848 nanos_since_epoch: epoch_nanos,
1849 };
1850 assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1851
1852 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1854 .unwrap()
1855 .and_hms_opt(11, 31, 0)
1856 .unwrap()
1857 .and_utc()
1858 .timestamp_nanos_opt()
1859 .unwrap();
1860 let dt = TemporalValue::DateTime {
1861 nanos_since_epoch: utc_nanos,
1862 offset_seconds: 3600,
1863 timezone_name: None,
1864 };
1865 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1866
1867 let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1869 .unwrap()
1870 .and_hms_nano_opt(20, 40, 32, 142_000_000)
1871 .unwrap()
1872 .and_utc()
1873 .timestamp_nanos_opt()
1874 .unwrap();
1875 let dt2 = TemporalValue::DateTime {
1876 nanos_since_epoch: utc_nanos2,
1877 offset_seconds: 3600,
1878 timezone_name: None,
1879 };
1880 assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1881
1882 let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1884 .unwrap()
1885 .and_hms_opt(12, 31, 0)
1886 .unwrap()
1887 .and_utc()
1888 .timestamp_nanos_opt()
1889 .unwrap();
1890 let dt3 = TemporalValue::DateTime {
1891 nanos_since_epoch: utc_nanos3,
1892 offset_seconds: 0,
1893 timezone_name: None,
1894 };
1895 assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1896 }
1897
1898 #[test]
1899 fn test_temporal_display_fractional_trailing_zeros_stripped() {
1900 let d = TemporalValue::Duration {
1902 months: 0,
1903 days: 0,
1904 nanos: 900_000_000,
1905 };
1906 assert_eq!(d.to_string(), "PT0.9S");
1907
1908 let d2 = TemporalValue::Duration {
1910 months: 0,
1911 days: 0,
1912 nanos: 400_000_000,
1913 };
1914 assert_eq!(d2.to_string(), "PT0.4S");
1915
1916 let d3 = TemporalValue::Duration {
1918 months: 0,
1919 days: 0,
1920 nanos: 142_000_000,
1921 };
1922 assert_eq!(d3.to_string(), "PT0.142S");
1923
1924 let d4 = TemporalValue::Duration {
1926 months: 0,
1927 days: 0,
1928 nanos: 1,
1929 };
1930 assert_eq!(d4.to_string(), "PT0.000000001S");
1931 }
1932
1933 #[test]
1934 fn test_temporal_display_offset_second_precision() {
1935 let t = TemporalValue::Time {
1937 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1938 offset_seconds: 2 * 3600 + 5 * 60 + 59,
1939 };
1940 assert_eq!(t.to_string(), "12:00+02:05:59");
1941
1942 let t2 = TemporalValue::Time {
1944 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1945 offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1946 };
1947 assert_eq!(t2.to_string(), "12:00-02:05:07");
1948 }
1949
1950 #[test]
1951 fn test_temporal_display_datetime_with_timezone_name() {
1952 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1953 .unwrap()
1954 .and_hms_opt(11, 31, 0)
1955 .unwrap()
1956 .and_utc()
1957 .timestamp_nanos_opt()
1958 .unwrap();
1959 let dt = TemporalValue::DateTime {
1960 nanos_since_epoch: utc_nanos,
1961 offset_seconds: 3600,
1962 timezone_name: Some("Europe/Stockholm".to_string()),
1963 };
1964 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
1965 }
1966
1967 #[test]
1974 fn value_hash_eq_contract_float_signed_zero() {
1975 use std::collections::hash_map::DefaultHasher;
1976 use std::hash::{Hash, Hasher};
1977
1978 fn h(v: &Value) -> u64 {
1979 let mut s = DefaultHasher::new();
1980 v.hash(&mut s);
1981 s.finish()
1982 }
1983
1984 let pos = Value::Float(0.0);
1985 let neg = Value::Float(-0.0);
1986 assert_eq!(pos, neg, "0.0 and -0.0 compare equal");
1987 assert_eq!(
1988 h(&pos),
1989 h(&neg),
1990 "equal Values must hash equally (Hash/Eq contract)"
1991 );
1992 }
1993}