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 Temporal(TemporalValue),
556}
557
558impl Value {
563 pub fn is_null(&self) -> bool {
565 matches!(self, Value::Null)
566 }
567
568 pub fn as_bool(&self) -> Option<bool> {
570 match self {
571 Value::Bool(b) => Some(*b),
572 _ => None,
573 }
574 }
575
576 pub fn as_i64(&self) -> Option<i64> {
578 match self {
579 Value::Int(i) => Some(*i),
580 _ => None,
581 }
582 }
583
584 pub fn as_u64(&self) -> Option<u64> {
586 match self {
587 Value::Int(i) if *i >= 0 => Some(*i as u64),
588 _ => None,
589 }
590 }
591
592 pub fn as_f64(&self) -> Option<f64> {
596 match self {
597 Value::Float(f) => Some(*f),
598 Value::Int(i) => Some(*i as f64),
599 _ => None,
600 }
601 }
602
603 pub fn as_str(&self) -> Option<&str> {
605 match self {
606 Value::String(s) => Some(s),
607 _ => None,
608 }
609 }
610
611 pub fn is_i64(&self) -> bool {
613 matches!(self, Value::Int(_))
614 }
615
616 pub fn is_f64(&self) -> bool {
618 matches!(self, Value::Float(_))
619 }
620
621 pub fn is_string(&self) -> bool {
623 matches!(self, Value::String(_))
624 }
625
626 pub fn is_number(&self) -> bool {
628 matches!(self, Value::Int(_) | Value::Float(_))
629 }
630
631 pub fn as_array(&self) -> Option<&Vec<Value>> {
633 match self {
634 Value::List(l) => Some(l),
635 _ => None,
636 }
637 }
638
639 pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
641 match self {
642 Value::Map(m) => Some(m),
643 _ => None,
644 }
645 }
646
647 pub fn is_bool(&self) -> bool {
649 matches!(self, Value::Bool(_))
650 }
651
652 pub fn is_list(&self) -> bool {
654 matches!(self, Value::List(_))
655 }
656
657 pub fn is_map(&self) -> bool {
659 matches!(self, Value::Map(_))
660 }
661
662 pub fn get(&self, key: &str) -> Option<&Value> {
666 match self {
667 Value::Map(m) => m.get(key),
668 _ => None,
669 }
670 }
671
672 pub fn is_temporal(&self) -> bool {
674 matches!(self, Value::Temporal(_))
675 }
676
677 pub fn as_temporal(&self) -> Option<&TemporalValue> {
679 match self {
680 Value::Temporal(t) => Some(t),
681 _ => None,
682 }
683 }
684}
685
686impl fmt::Display for Value {
687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688 match self {
689 Value::Null => write!(f, "null"),
690 Value::Bool(b) => write!(f, "{b}"),
691 Value::Int(i) => write!(f, "{i}"),
692 Value::Float(v) => {
693 if v.fract() == 0.0 && v.is_finite() {
694 write!(f, "{v:.1}")
695 } else {
696 write!(f, "{v}")
697 }
698 }
699 Value::String(s) => write!(f, "{s}"),
700 Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
701 Value::List(l) => {
702 write!(f, "[")?;
703 for (i, item) in l.iter().enumerate() {
704 if i > 0 {
705 write!(f, ", ")?;
706 }
707 write!(f, "{item}")?;
708 }
709 write!(f, "]")
710 }
711 Value::Map(m) => {
712 write!(f, "{{")?;
713 for (i, (k, v)) in m.iter().enumerate() {
714 if i > 0 {
715 write!(f, ", ")?;
716 }
717 write!(f, "{k}: {v}")?;
718 }
719 write!(f, "}}")
720 }
721 Value::Node(n) => write!(f, "(:{} {{vid: {}}})", n.labels.join(":"), n.vid),
722 Value::Edge(e) => write!(f, "-[:{}]-", e.edge_type),
723 Value::Path(p) => write!(
724 f,
725 "<path: {} nodes, {} edges>",
726 p.nodes.len(),
727 p.edges.len()
728 ),
729 Value::Vector(v) => write!(f, "<vector: {} dims>", v.len()),
730 Value::Temporal(t) => write!(f, "{t}"),
731 }
732 }
733}
734
735fn float_eq_normalized(a: f64, b: f64) -> bool {
746 a.total_cmp(&b) == std::cmp::Ordering::Equal
747 || (a == 0.0 && b == 0.0)
748 || (a.is_nan() && b.is_nan())
749}
750
751impl PartialEq for Value {
752 fn eq(&self, other: &Self) -> bool {
759 match (self, other) {
760 (Value::Float(a), Value::Float(b)) => float_eq_normalized(*a, *b),
762 (Value::Null, Value::Null) => true,
764 (Value::Bool(a), Value::Bool(b)) => a == b,
765 (Value::Int(a), Value::Int(b)) => a == b,
766 (Value::String(a), Value::String(b)) => a == b,
767 (Value::Bytes(a), Value::Bytes(b)) => a == b,
768 (Value::List(a), Value::List(b)) => a == b,
769 (Value::Map(a), Value::Map(b)) => a == b,
770 (Value::Node(a), Value::Node(b)) => a == b,
771 (Value::Edge(a), Value::Edge(b)) => a == b,
772 (Value::Path(a), Value::Path(b)) => a == b,
773 (Value::Vector(a), Value::Vector(b)) => a == b,
774 (Value::Temporal(a), Value::Temporal(b)) => a == b,
775 _ => false,
777 }
778 }
779}
780
781impl Eq for Value {}
782
783fn hash_f64_normalized<H: Hasher>(f: f64, state: &mut H) {
788 let bits = if f == 0.0 {
789 0.0f64.to_bits()
790 } else if f.is_nan() {
791 f64::NAN.to_bits()
792 } else {
793 f.to_bits()
794 };
795 bits.hash(state);
796}
797
798impl Hash for Value {
799 fn hash<H: Hasher>(&self, state: &mut H) {
800 std::mem::discriminant(self).hash(state);
802 match self {
803 Value::Null => {}
804 Value::Bool(b) => b.hash(state),
805 Value::Int(i) => i.hash(state),
806 Value::Float(f) => hash_f64_normalized(*f, state),
810 Value::String(s) => s.hash(state),
811 Value::Bytes(b) => b.hash(state),
812 Value::List(l) => l.hash(state),
813 Value::Map(m) => hash_map(m, state),
814 Value::Node(n) => n.hash(state),
815 Value::Edge(e) => e.hash(state),
816 Value::Path(p) => p.hash(state),
817 Value::Vector(v) => {
818 v.len().hash(state);
821 for f in v {
822 let bits = if *f == 0.0f32 {
823 0.0f32.to_bits()
824 } else if f.is_nan() {
825 f32::NAN.to_bits()
826 } else {
827 f.to_bits()
828 };
829 bits.hash(state);
830 }
831 }
832 Value::Temporal(t) => t.hash(state),
833 }
834 }
835}
836
837fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
843 let mut pairs: Vec<_> = m.iter().collect();
844 pairs.sort_by_key(|(k, _)| *k);
845 pairs.len().hash(state);
846 for (k, v) in pairs {
847 k.hash(state);
848 v.hash(state);
849 }
850}
851
852#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
854pub struct Node {
855 pub vid: Vid,
857 pub labels: Vec<String>,
859 pub properties: HashMap<String, Value>,
861}
862
863impl Hash for Node {
864 fn hash<H: Hasher>(&self, state: &mut H) {
865 self.vid.hash(state);
866 let mut sorted_labels = self.labels.clone();
867 sorted_labels.sort();
868 sorted_labels.hash(state);
869 hash_map(&self.properties, state);
870 }
871}
872
873impl Node {
874 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
881 let val = self
882 .properties
883 .get(property)
884 .ok_or_else(|| UniError::Query {
885 message: format!("Property '{}' not found on node {}", property, self.vid),
886 query: None,
887 })?;
888 T::from_value(val)
889 }
890
891 pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
893 self.properties
894 .get(property)
895 .and_then(|v| T::from_value(v).ok())
896 }
897}
898
899#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
901pub struct Edge {
902 pub eid: Eid,
904 pub edge_type: String,
906 pub src: Vid,
908 pub dst: Vid,
910 pub properties: HashMap<String, Value>,
912}
913
914impl Hash for Edge {
915 fn hash<H: Hasher>(&self, state: &mut H) {
916 self.eid.hash(state);
917 self.edge_type.hash(state);
918 self.src.hash(state);
919 self.dst.hash(state);
920 hash_map(&self.properties, state);
921 }
922}
923
924impl Edge {
925 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
932 let val = self
933 .properties
934 .get(property)
935 .ok_or_else(|| UniError::Query {
936 message: format!("Property '{}' not found on edge {}", property, self.eid),
937 query: None,
938 })?;
939 T::from_value(val)
940 }
941}
942
943#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
945pub struct Path {
946 pub nodes: Vec<Node>,
948 #[serde(rename = "relationships")]
950 pub edges: Vec<Edge>,
951}
952
953impl Path {
954 pub fn nodes(&self) -> &[Node] {
956 &self.nodes
957 }
958
959 pub fn edges(&self) -> &[Edge] {
961 &self.edges
962 }
963
964 pub fn len(&self) -> usize {
966 self.edges.len()
967 }
968
969 pub fn is_empty(&self) -> bool {
971 self.edges.is_empty()
972 }
973
974 pub fn start(&self) -> Option<&Node> {
976 self.nodes.first()
977 }
978
979 pub fn end(&self) -> Option<&Node> {
981 self.nodes.last()
982 }
983}
984
985pub trait FromValue: Sized {
991 fn from_value(value: &Value) -> crate::Result<Self>;
997}
998
999impl<T> FromValue for T
1001where
1002 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1003{
1004 fn from_value(value: &Value) -> crate::Result<Self> {
1005 Self::try_from(value)
1006 }
1007}
1008
1009macro_rules! impl_try_from_value_owned {
1014 ($($t:ty),+ $(,)?) => {
1015 $(
1016 impl TryFrom<Value> for $t {
1017 type Error = UniError;
1018 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1019 Self::try_from(&value)
1020 }
1021 }
1022 )+
1023 };
1024}
1025
1026impl_try_from_value_owned!(
1027 String,
1028 i64,
1029 i32,
1030 f64,
1031 bool,
1032 Vid,
1033 Eid,
1034 Vec<f32>,
1035 Path,
1036 Node,
1037 Edge
1038);
1039
1040fn type_error(expected: &str, value: &Value) -> UniError {
1046 UniError::Type {
1047 expected: expected.to_string(),
1048 actual: format!("{:?}", value),
1049 }
1050}
1051
1052impl TryFrom<&Value> for String {
1053 type Error = UniError;
1054
1055 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1056 match value {
1057 Value::String(s) => Ok(s.clone()),
1058 Value::Int(i) => Ok(i.to_string()),
1059 Value::Float(f) => Ok(f.to_string()),
1060 Value::Bool(b) => Ok(b.to_string()),
1061 Value::Temporal(t) => Ok(t.to_string()),
1062 _ => Err(type_error("String", value)),
1063 }
1064 }
1065}
1066
1067impl TryFrom<&Value> for i64 {
1068 type Error = UniError;
1069
1070 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1077 match value {
1078 Value::Int(i) => Ok(*i),
1079 Value::Float(f) => Ok(*f as i64),
1080 _ => Err(type_error("Int", value)),
1081 }
1082 }
1083}
1084
1085impl TryFrom<&Value> for i32 {
1086 type Error = UniError;
1087
1088 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1093 match value {
1094 Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
1095 expected: "i32".to_string(),
1096 actual: format!("Integer {} out of range", i),
1097 }),
1098 Value::Float(f) => {
1099 if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1100 return Err(UniError::Type {
1101 expected: "i32".to_string(),
1102 actual: format!("Float {} out of range", f),
1103 });
1104 }
1105 if f.fract() != 0.0 {
1106 return Err(UniError::Type {
1107 expected: "i32".to_string(),
1108 actual: format!("Float {} has fractional part", f),
1109 });
1110 }
1111 Ok(*f as i32)
1112 }
1113 _ => Err(type_error("Int", value)),
1114 }
1115 }
1116}
1117
1118impl TryFrom<&Value> for f64 {
1119 type Error = UniError;
1120
1121 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1122 match value {
1123 Value::Float(f) => Ok(*f),
1124 Value::Int(i) => Ok(*i as f64),
1125 _ => Err(type_error("Float", value)),
1126 }
1127 }
1128}
1129
1130impl TryFrom<&Value> for bool {
1131 type Error = UniError;
1132
1133 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1134 match value {
1135 Value::Bool(b) => Ok(*b),
1136 _ => Err(type_error("Bool", value)),
1137 }
1138 }
1139}
1140
1141impl TryFrom<&Value> for Vid {
1142 type Error = UniError;
1143
1144 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1145 match value {
1146 Value::Node(n) => Ok(n.vid),
1147 Value::String(s) => {
1148 if let Ok(id) = s.parse::<u64>() {
1149 return Ok(Vid::new(id));
1150 }
1151 Err(UniError::Type {
1152 expected: "Vid".into(),
1153 actual: s.clone(),
1154 })
1155 }
1156 Value::Int(i) => Ok(Vid::new(*i as u64)),
1157 _ => Err(type_error("Vid", value)),
1158 }
1159 }
1160}
1161
1162impl TryFrom<&Value> for Eid {
1163 type Error = UniError;
1164
1165 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1166 match value {
1167 Value::Edge(e) => Ok(e.eid),
1168 Value::String(s) => {
1169 if let Ok(id) = s.parse::<u64>() {
1170 return Ok(Eid::new(id));
1171 }
1172 Err(UniError::Type {
1173 expected: "Eid".into(),
1174 actual: s.clone(),
1175 })
1176 }
1177 Value::Int(i) => Ok(Eid::new(*i as u64)),
1178 _ => Err(type_error("Eid", value)),
1179 }
1180 }
1181}
1182
1183impl TryFrom<&Value> for Vec<f32> {
1184 type Error = UniError;
1185
1186 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1187 match value {
1188 Value::Vector(v) => Ok(v.clone()),
1189 Value::List(l) => {
1190 let mut vec = Vec::with_capacity(l.len());
1191 for item in l {
1192 match item {
1193 Value::Float(f) => vec.push(*f as f32),
1194 Value::Int(i) => vec.push(*i as f32),
1195 _ => return Err(type_error("Float", item)),
1196 }
1197 }
1198 Ok(vec)
1199 }
1200 _ => Err(type_error("Vector", value)),
1201 }
1202 }
1203}
1204
1205impl<T> TryFrom<&Value> for Option<T>
1206where
1207 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1208{
1209 type Error = UniError;
1210
1211 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1212 match value {
1213 Value::Null => Ok(None),
1214 _ => T::try_from(value).map(Some),
1215 }
1216 }
1217}
1218
1219impl<T> TryFrom<Value> for Option<T>
1220where
1221 T: TryFrom<Value, Error = UniError>,
1222{
1223 type Error = UniError;
1224 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1225 match value {
1226 Value::Null => Ok(None),
1227 _ => T::try_from(value).map(Some),
1228 }
1229 }
1230}
1231
1232impl<T> TryFrom<&Value> for Vec<T>
1233where
1234 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1235{
1236 type Error = UniError;
1237
1238 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1239 match value {
1240 Value::List(l) => {
1241 let mut vec = Vec::with_capacity(l.len());
1242 for item in l {
1243 vec.push(T::try_from(item)?);
1244 }
1245 Ok(vec)
1246 }
1247 _ => Err(type_error("List", value)),
1248 }
1249 }
1250}
1251
1252impl<T> TryFrom<Value> for Vec<T>
1253where
1254 T: TryFrom<Value, Error = UniError>,
1255{
1256 type Error = UniError;
1257 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1258 match value {
1259 Value::List(l) => {
1260 let mut vec = Vec::with_capacity(l.len());
1261 for item in l {
1262 vec.push(T::try_from(item)?);
1263 }
1264 Ok(vec)
1265 }
1266 other => Err(type_error("List", &other)),
1267 }
1268 }
1269}
1270
1271fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1277 keys.iter().find_map(|k| map.get(*k))
1278}
1279
1280fn extract_properties(value: &Value) -> HashMap<String, Value> {
1282 match value {
1283 Value::Map(m) => m.clone(),
1284 _ => HashMap::new(),
1285 }
1286}
1287
1288impl TryFrom<&Value> for Node {
1289 type Error = UniError;
1290
1291 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1292 match value {
1293 Value::Node(n) => Ok(n.clone()),
1294 Value::Map(m) => {
1295 let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1296 let props_val = m.get("properties");
1297
1298 let (Some(v), Some(p)) = (vid_val, props_val) else {
1299 return Err(type_error("Node Map", value));
1300 };
1301
1302 let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1304 label_list
1305 .iter()
1306 .filter_map(|v| {
1307 if let Value::String(s) = v {
1308 Some(s.clone())
1309 } else {
1310 None
1311 }
1312 })
1313 .collect()
1314 } else {
1315 Vec::new()
1316 };
1317
1318 Ok(Node {
1319 vid: Vid::try_from(v)?,
1320 labels,
1321 properties: extract_properties(p),
1322 })
1323 }
1324 _ => Err(type_error("Node", value)),
1325 }
1326 }
1327}
1328
1329impl TryFrom<&Value> for Edge {
1330 type Error = UniError;
1331
1332 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1333 match value {
1334 Value::Edge(e) => Ok(e.clone()),
1335 Value::Map(m) => {
1336 let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1337 let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1338 let src_val = get_with_fallback(m, &["_src", "src"]);
1339 let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1340 let props_val = m.get("properties");
1341
1342 let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1343 (eid_val, type_val, src_val, dst_val, props_val)
1344 else {
1345 return Err(type_error("Edge Map", value));
1346 };
1347
1348 Ok(Edge {
1349 eid: Eid::try_from(id)?,
1350 edge_type: String::try_from(t)?,
1351 src: Vid::try_from(s)?,
1352 dst: Vid::try_from(d)?,
1353 properties: extract_properties(p),
1354 })
1355 }
1356 _ => Err(type_error("Edge", value)),
1357 }
1358 }
1359}
1360
1361impl TryFrom<&Value> for Path {
1362 type Error = UniError;
1363
1364 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1365 match value {
1366 Value::Path(p) => Ok(p.clone()),
1367 Value::Map(m) => {
1368 let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1369 (m.get("nodes"), m.get("relationships"))
1370 else {
1371 return Err(type_error("Path (Map with nodes/relationships)", value));
1372 };
1373
1374 let nodes = nodes_list
1375 .iter()
1376 .map(Node::try_from)
1377 .collect::<std::result::Result<Vec<_>, _>>()?;
1378
1379 let edges = rels_list
1380 .iter()
1381 .map(Edge::try_from)
1382 .collect::<std::result::Result<Vec<_>, _>>()?;
1383
1384 Ok(Path { nodes, edges })
1385 }
1386 _ => Err(type_error("Path", value)),
1387 }
1388 }
1389}
1390
1391impl From<String> for Value {
1396 fn from(v: String) -> Self {
1397 Value::String(v)
1398 }
1399}
1400
1401impl From<&str> for Value {
1402 fn from(v: &str) -> Self {
1403 Value::String(v.to_string())
1404 }
1405}
1406
1407impl From<i64> for Value {
1408 fn from(v: i64) -> Self {
1409 Value::Int(v)
1410 }
1411}
1412
1413impl From<i32> for Value {
1414 fn from(v: i32) -> Self {
1415 Value::Int(v as i64)
1416 }
1417}
1418
1419impl From<f64> for Value {
1420 fn from(v: f64) -> Self {
1421 Value::Float(v)
1422 }
1423}
1424
1425impl From<bool> for Value {
1426 fn from(v: bool) -> Self {
1427 Value::Bool(v)
1428 }
1429}
1430
1431impl From<Vec<f32>> for Value {
1432 fn from(v: Vec<f32>) -> Self {
1433 Value::Vector(v)
1434 }
1435}
1436
1437impl From<serde_json::Value> for Value {
1442 fn from(v: serde_json::Value) -> Self {
1443 match v {
1444 serde_json::Value::Null => Value::Null,
1445 serde_json::Value::Bool(b) => Value::Bool(b),
1446 serde_json::Value::Number(n) => {
1447 if let Some(i) = n.as_i64() {
1448 Value::Int(i)
1449 } else if let Some(f) = n.as_f64() {
1450 Value::Float(f)
1451 } else {
1452 Value::Null
1453 }
1454 }
1455 serde_json::Value::String(s) => Value::String(s),
1456 serde_json::Value::Array(arr) => {
1457 Value::List(arr.into_iter().map(Value::from).collect())
1458 }
1459 serde_json::Value::Object(obj) => {
1460 Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1461 }
1462 }
1463 }
1464}
1465
1466impl From<Value> for serde_json::Value {
1467 fn from(v: Value) -> Self {
1468 match v {
1469 Value::Null => serde_json::Value::Null,
1470 Value::Bool(b) => serde_json::Value::Bool(b),
1471 Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1472 Value::Float(f) => serde_json::Number::from_f64(f)
1473 .map(serde_json::Value::Number)
1474 .unwrap_or(serde_json::Value::Null), Value::String(s) => serde_json::Value::String(s),
1476 Value::Bytes(b) => {
1477 use base64::Engine;
1478 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1479 }
1480 Value::List(l) => {
1481 serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1482 }
1483 Value::Map(m) => {
1484 let mut map = serde_json::Map::new();
1485 for (k, v) in m {
1486 map.insert(k, v.into());
1487 }
1488 serde_json::Value::Object(map)
1489 }
1490 Value::Node(n) => {
1491 let mut map = serde_json::Map::new();
1492 map.insert(
1493 "_id".to_string(),
1494 serde_json::Value::String(n.vid.to_string()),
1495 );
1496 map.insert(
1497 "_labels".to_string(),
1498 serde_json::Value::Array(
1499 n.labels
1500 .into_iter()
1501 .map(serde_json::Value::String)
1502 .collect(),
1503 ),
1504 );
1505 let props: serde_json::Value = Value::Map(n.properties).into();
1506 map.insert("properties".to_string(), props);
1507 serde_json::Value::Object(map)
1508 }
1509 Value::Edge(e) => {
1510 let mut map = serde_json::Map::new();
1511 map.insert(
1512 "_id".to_string(),
1513 serde_json::Value::String(e.eid.to_string()),
1514 );
1515 map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1516 map.insert(
1517 "_src".to_string(),
1518 serde_json::Value::String(e.src.to_string()),
1519 );
1520 map.insert(
1521 "_dst".to_string(),
1522 serde_json::Value::String(e.dst.to_string()),
1523 );
1524 let props: serde_json::Value = Value::Map(e.properties).into();
1525 map.insert("properties".to_string(), props);
1526 serde_json::Value::Object(map)
1527 }
1528 Value::Path(p) => {
1529 let mut map = serde_json::Map::new();
1530 map.insert(
1531 "nodes".to_string(),
1532 Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1533 );
1534 map.insert(
1535 "relationships".to_string(),
1536 Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1537 );
1538 serde_json::Value::Object(map)
1539 }
1540 Value::Vector(v) => serde_json::Value::Array(
1541 v.into_iter()
1542 .map(|f| {
1543 serde_json::Number::from_f64(f as f64)
1544 .map(serde_json::Value::Number)
1545 .unwrap_or(serde_json::Value::Null)
1546 })
1547 .collect(),
1548 ),
1549 Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1550 }
1551 }
1552}
1553
1554#[macro_export]
1576macro_rules! unival {
1577 (null) => {
1579 $crate::Value::Null
1580 };
1581
1582 (true) => {
1584 $crate::Value::Bool(true)
1585 };
1586 (false) => {
1587 $crate::Value::Bool(false)
1588 };
1589
1590 ([ $($elem:tt),* $(,)? ]) => {
1592 $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1593 };
1594
1595 ({ $($key:tt : $val:tt),* $(,)? }) => {
1597 $crate::Value::Map({
1598 #[allow(unused_mut)]
1599 let mut map = ::std::collections::HashMap::new();
1600 $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1601 map
1602 })
1603 };
1604
1605 ($e:expr) => {
1607 $crate::Value::from($e)
1608 };
1609}
1610
1611impl From<usize> for Value {
1616 fn from(v: usize) -> Self {
1617 Value::Int(v as i64)
1618 }
1619}
1620
1621impl From<u64> for Value {
1622 fn from(v: u64) -> Self {
1623 Value::Int(v as i64)
1624 }
1625}
1626
1627impl From<f32> for Value {
1628 fn from(v: f32) -> Self {
1629 Value::Float(v as f64)
1630 }
1631}
1632
1633#[cfg(test)]
1638mod tests {
1639 use super::*;
1640
1641 #[test]
1642 fn test_accessor_methods() {
1643 assert!(Value::Null.is_null());
1644 assert!(!Value::Int(1).is_null());
1645
1646 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1647 assert_eq!(Value::Int(42).as_bool(), None);
1648
1649 assert_eq!(Value::Int(42).as_i64(), Some(42));
1650 assert_eq!(Value::Float(2.5).as_i64(), None);
1651
1652 assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1654 assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1655 assert_eq!(Value::String("x".into()).as_f64(), None);
1656
1657 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1658 assert_eq!(Value::Int(1).as_str(), None);
1659
1660 assert!(Value::Int(1).is_i64());
1661 assert!(!Value::Float(1.0).is_i64());
1662
1663 assert!(Value::Float(1.0).is_f64());
1664 assert!(!Value::Int(1).is_f64());
1665
1666 assert!(Value::Int(1).is_number());
1667 assert!(Value::Float(1.0).is_number());
1668 assert!(!Value::String("x".into()).is_number());
1669 }
1670
1671 #[test]
1672 fn test_serde_json_roundtrip() {
1673 let val = Value::Int(42);
1674 let json: serde_json::Value = val.clone().into();
1675 let back: Value = json.into();
1676 assert_eq!(val, back);
1677
1678 let val = Value::Float(2.5);
1679 let json: serde_json::Value = val.clone().into();
1680 let back: Value = json.into();
1681 assert_eq!(val, back);
1682
1683 let val = Value::String("hello".into());
1684 let json: serde_json::Value = val.clone().into();
1685 let back: Value = json.into();
1686 assert_eq!(val, back);
1687
1688 let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1689 let json: serde_json::Value = val.clone().into();
1690 let back: Value = json.into();
1691 assert_eq!(val, back);
1692 }
1693
1694 #[test]
1695 fn test_unival_macro() {
1696 assert_eq!(unival!(null), Value::Null);
1697 assert_eq!(unival!(true), Value::Bool(true));
1698 assert_eq!(unival!(false), Value::Bool(false));
1699 assert_eq!(unival!(42_i64), Value::Int(42));
1700 assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1701 assert_eq!(unival!("hello"), Value::String("hello".into()));
1702
1703 let list = unival!([1_i64, 2_i64]);
1705 assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1706
1707 let map = unival!({"key": "val", "num": 42_i64});
1709 if let Value::Map(m) = &map {
1710 assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1711 assert_eq!(m.get("num"), Some(&Value::Int(42)));
1712 } else {
1713 panic!("Expected Map");
1714 }
1715
1716 let x: i64 = 99;
1718 assert_eq!(unival!(x), Value::Int(99));
1719 }
1720
1721 #[test]
1722 fn test_int_float_distinction_preserved() {
1723 let int_val = Value::Int(42);
1725 let float_val = Value::Float(42.0);
1726
1727 assert!(int_val.is_i64());
1728 assert!(!int_val.is_f64());
1729
1730 assert!(float_val.is_f64());
1731 assert!(!float_val.is_i64());
1732
1733 assert_ne!(int_val, float_val);
1735 }
1736
1737 #[test]
1738 fn test_temporal_display_zero_seconds_omitted() {
1739 let lt = TemporalValue::LocalTime {
1741 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1742 };
1743 assert_eq!(lt.to_string(), "12:00");
1744
1745 let lt2 = TemporalValue::LocalTime {
1747 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1748 };
1749 assert_eq!(lt2.to_string(), "12:31:14");
1750
1751 let lt3 = TemporalValue::LocalTime {
1753 nanos_since_midnight: 500_000_000,
1754 };
1755 assert_eq!(lt3.to_string(), "00:00:00.5");
1756
1757 let t = TemporalValue::Time {
1759 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1760 offset_seconds: 0,
1761 };
1762 assert_eq!(t.to_string(), "12:00Z");
1763
1764 let t2 = TemporalValue::Time {
1766 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1767 offset_seconds: 3600,
1768 };
1769 assert_eq!(t2.to_string(), "12:31:14+01:00");
1770
1771 let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1773 .unwrap()
1774 .and_hms_opt(12, 31, 0)
1775 .unwrap()
1776 .and_utc()
1777 .timestamp_nanos_opt()
1778 .unwrap();
1779 let ldt = TemporalValue::LocalDateTime {
1780 nanos_since_epoch: epoch_nanos,
1781 };
1782 assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1783
1784 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1786 .unwrap()
1787 .and_hms_opt(11, 31, 0)
1788 .unwrap()
1789 .and_utc()
1790 .timestamp_nanos_opt()
1791 .unwrap();
1792 let dt = TemporalValue::DateTime {
1793 nanos_since_epoch: utc_nanos,
1794 offset_seconds: 3600,
1795 timezone_name: None,
1796 };
1797 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1798
1799 let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1801 .unwrap()
1802 .and_hms_nano_opt(20, 40, 32, 142_000_000)
1803 .unwrap()
1804 .and_utc()
1805 .timestamp_nanos_opt()
1806 .unwrap();
1807 let dt2 = TemporalValue::DateTime {
1808 nanos_since_epoch: utc_nanos2,
1809 offset_seconds: 3600,
1810 timezone_name: None,
1811 };
1812 assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1813
1814 let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1816 .unwrap()
1817 .and_hms_opt(12, 31, 0)
1818 .unwrap()
1819 .and_utc()
1820 .timestamp_nanos_opt()
1821 .unwrap();
1822 let dt3 = TemporalValue::DateTime {
1823 nanos_since_epoch: utc_nanos3,
1824 offset_seconds: 0,
1825 timezone_name: None,
1826 };
1827 assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1828 }
1829
1830 #[test]
1831 fn test_temporal_display_fractional_trailing_zeros_stripped() {
1832 let d = TemporalValue::Duration {
1834 months: 0,
1835 days: 0,
1836 nanos: 900_000_000,
1837 };
1838 assert_eq!(d.to_string(), "PT0.9S");
1839
1840 let d2 = TemporalValue::Duration {
1842 months: 0,
1843 days: 0,
1844 nanos: 400_000_000,
1845 };
1846 assert_eq!(d2.to_string(), "PT0.4S");
1847
1848 let d3 = TemporalValue::Duration {
1850 months: 0,
1851 days: 0,
1852 nanos: 142_000_000,
1853 };
1854 assert_eq!(d3.to_string(), "PT0.142S");
1855
1856 let d4 = TemporalValue::Duration {
1858 months: 0,
1859 days: 0,
1860 nanos: 1,
1861 };
1862 assert_eq!(d4.to_string(), "PT0.000000001S");
1863 }
1864
1865 #[test]
1866 fn test_temporal_display_offset_second_precision() {
1867 let t = TemporalValue::Time {
1869 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1870 offset_seconds: 2 * 3600 + 5 * 60 + 59,
1871 };
1872 assert_eq!(t.to_string(), "12:00+02:05:59");
1873
1874 let t2 = TemporalValue::Time {
1876 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1877 offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1878 };
1879 assert_eq!(t2.to_string(), "12:00-02:05:07");
1880 }
1881
1882 #[test]
1883 fn test_temporal_display_datetime_with_timezone_name() {
1884 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1885 .unwrap()
1886 .and_hms_opt(11, 31, 0)
1887 .unwrap()
1888 .and_utc()
1889 .timestamp_nanos_opt()
1890 .unwrap();
1891 let dt = TemporalValue::DateTime {
1892 nanos_since_epoch: utc_nanos,
1893 offset_seconds: 3600,
1894 timezone_name: Some("Europe/Stockholm".to_string()),
1895 };
1896 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
1897 }
1898
1899 #[test]
1906 fn value_hash_eq_contract_float_signed_zero() {
1907 use std::collections::hash_map::DefaultHasher;
1908 use std::hash::{Hash, Hasher};
1909
1910 fn h(v: &Value) -> u64 {
1911 let mut s = DefaultHasher::new();
1912 v.hash(&mut s);
1913 s.finish()
1914 }
1915
1916 let pos = Value::Float(0.0);
1917 let neg = Value::Float(-0.0);
1918 assert_eq!(pos, neg, "0.0 and -0.0 compare equal");
1919 assert_eq!(
1920 h(&pos),
1921 h(&neg),
1922 "equal Values must hash equally (Hash/Eq contract)"
1923 );
1924 }
1925}