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, PartialEq, Serialize, Deserialize)]
516#[serde(untagged)]
517#[non_exhaustive]
518pub enum Value {
519 Null,
521 Bool(bool),
523 Int(i64),
525 Float(f64),
527 String(String),
529 Bytes(Vec<u8>),
531 List(Vec<Value>),
533 Map(HashMap<String, Value>),
535
536 Node(Node),
539 Edge(Edge),
541 Path(Path),
543
544 Vector(Vec<f32>),
547
548 Temporal(TemporalValue),
551}
552
553impl Value {
558 pub fn is_null(&self) -> bool {
560 matches!(self, Value::Null)
561 }
562
563 pub fn as_bool(&self) -> Option<bool> {
565 match self {
566 Value::Bool(b) => Some(*b),
567 _ => None,
568 }
569 }
570
571 pub fn as_i64(&self) -> Option<i64> {
573 match self {
574 Value::Int(i) => Some(*i),
575 _ => None,
576 }
577 }
578
579 pub fn as_u64(&self) -> Option<u64> {
581 match self {
582 Value::Int(i) if *i >= 0 => Some(*i as u64),
583 _ => None,
584 }
585 }
586
587 pub fn as_f64(&self) -> Option<f64> {
591 match self {
592 Value::Float(f) => Some(*f),
593 Value::Int(i) => Some(*i as f64),
594 _ => None,
595 }
596 }
597
598 pub fn as_str(&self) -> Option<&str> {
600 match self {
601 Value::String(s) => Some(s),
602 _ => None,
603 }
604 }
605
606 pub fn is_i64(&self) -> bool {
608 matches!(self, Value::Int(_))
609 }
610
611 pub fn is_f64(&self) -> bool {
613 matches!(self, Value::Float(_))
614 }
615
616 pub fn is_string(&self) -> bool {
618 matches!(self, Value::String(_))
619 }
620
621 pub fn is_number(&self) -> bool {
623 matches!(self, Value::Int(_) | Value::Float(_))
624 }
625
626 pub fn as_array(&self) -> Option<&Vec<Value>> {
628 match self {
629 Value::List(l) => Some(l),
630 _ => None,
631 }
632 }
633
634 pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
636 match self {
637 Value::Map(m) => Some(m),
638 _ => None,
639 }
640 }
641
642 pub fn is_bool(&self) -> bool {
644 matches!(self, Value::Bool(_))
645 }
646
647 pub fn is_list(&self) -> bool {
649 matches!(self, Value::List(_))
650 }
651
652 pub fn is_map(&self) -> bool {
654 matches!(self, Value::Map(_))
655 }
656
657 pub fn get(&self, key: &str) -> Option<&Value> {
661 match self {
662 Value::Map(m) => m.get(key),
663 _ => None,
664 }
665 }
666
667 pub fn is_temporal(&self) -> bool {
669 matches!(self, Value::Temporal(_))
670 }
671
672 pub fn as_temporal(&self) -> Option<&TemporalValue> {
674 match self {
675 Value::Temporal(t) => Some(t),
676 _ => None,
677 }
678 }
679}
680
681impl fmt::Display for Value {
682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683 match self {
684 Value::Null => write!(f, "null"),
685 Value::Bool(b) => write!(f, "{b}"),
686 Value::Int(i) => write!(f, "{i}"),
687 Value::Float(v) => {
688 if v.fract() == 0.0 && v.is_finite() {
689 write!(f, "{v:.1}")
690 } else {
691 write!(f, "{v}")
692 }
693 }
694 Value::String(s) => write!(f, "{s}"),
695 Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
696 Value::List(l) => {
697 write!(f, "[")?;
698 for (i, item) in l.iter().enumerate() {
699 if i > 0 {
700 write!(f, ", ")?;
701 }
702 write!(f, "{item}")?;
703 }
704 write!(f, "]")
705 }
706 Value::Map(m) => {
707 write!(f, "{{")?;
708 for (i, (k, v)) in m.iter().enumerate() {
709 if i > 0 {
710 write!(f, ", ")?;
711 }
712 write!(f, "{k}: {v}")?;
713 }
714 write!(f, "}}")
715 }
716 Value::Node(n) => write!(f, "(:{} {{vid: {}}})", n.labels.join(":"), n.vid),
717 Value::Edge(e) => write!(f, "-[:{}]-", e.edge_type),
718 Value::Path(p) => write!(
719 f,
720 "<path: {} nodes, {} edges>",
721 p.nodes.len(),
722 p.edges.len()
723 ),
724 Value::Vector(v) => write!(f, "<vector: {} dims>", v.len()),
725 Value::Temporal(t) => write!(f, "{t}"),
726 }
727 }
728}
729
730impl Eq for Value {}
735
736impl Hash for Value {
737 fn hash<H: Hasher>(&self, state: &mut H) {
738 std::mem::discriminant(self).hash(state);
740 match self {
741 Value::Null => {}
742 Value::Bool(b) => b.hash(state),
743 Value::Int(i) => i.hash(state),
744 Value::Float(f) => f.to_bits().hash(state),
745 Value::String(s) => s.hash(state),
746 Value::Bytes(b) => b.hash(state),
747 Value::List(l) => l.hash(state),
748 Value::Map(m) => hash_map(m, state),
749 Value::Node(n) => n.hash(state),
750 Value::Edge(e) => e.hash(state),
751 Value::Path(p) => p.hash(state),
752 Value::Vector(v) => {
753 v.len().hash(state);
754 for f in v {
755 f.to_bits().hash(state);
756 }
757 }
758 Value::Temporal(t) => t.hash(state),
759 }
760 }
761}
762
763fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
769 let mut pairs: Vec<_> = m.iter().collect();
770 pairs.sort_by_key(|(k, _)| *k);
771 pairs.len().hash(state);
772 for (k, v) in pairs {
773 k.hash(state);
774 v.hash(state);
775 }
776}
777
778#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
780pub struct Node {
781 pub vid: Vid,
783 pub labels: Vec<String>,
785 pub properties: HashMap<String, Value>,
787}
788
789impl Hash for Node {
790 fn hash<H: Hasher>(&self, state: &mut H) {
791 self.vid.hash(state);
792 let mut sorted_labels = self.labels.clone();
793 sorted_labels.sort();
794 sorted_labels.hash(state);
795 hash_map(&self.properties, state);
796 }
797}
798
799impl Node {
800 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
807 let val = self
808 .properties
809 .get(property)
810 .ok_or_else(|| UniError::Query {
811 message: format!("Property '{}' not found on node {}", property, self.vid),
812 query: None,
813 })?;
814 T::from_value(val)
815 }
816
817 pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
819 self.properties
820 .get(property)
821 .and_then(|v| T::from_value(v).ok())
822 }
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
827pub struct Edge {
828 pub eid: Eid,
830 pub edge_type: String,
832 pub src: Vid,
834 pub dst: Vid,
836 pub properties: HashMap<String, Value>,
838}
839
840impl Hash for Edge {
841 fn hash<H: Hasher>(&self, state: &mut H) {
842 self.eid.hash(state);
843 self.edge_type.hash(state);
844 self.src.hash(state);
845 self.dst.hash(state);
846 hash_map(&self.properties, state);
847 }
848}
849
850impl Edge {
851 pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
858 let val = self
859 .properties
860 .get(property)
861 .ok_or_else(|| UniError::Query {
862 message: format!("Property '{}' not found on edge {}", property, self.eid),
863 query: None,
864 })?;
865 T::from_value(val)
866 }
867}
868
869#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
871pub struct Path {
872 pub nodes: Vec<Node>,
874 #[serde(rename = "relationships")]
876 pub edges: Vec<Edge>,
877}
878
879impl Path {
880 pub fn nodes(&self) -> &[Node] {
882 &self.nodes
883 }
884
885 pub fn edges(&self) -> &[Edge] {
887 &self.edges
888 }
889
890 pub fn len(&self) -> usize {
892 self.edges.len()
893 }
894
895 pub fn is_empty(&self) -> bool {
897 self.edges.is_empty()
898 }
899
900 pub fn start(&self) -> Option<&Node> {
902 self.nodes.first()
903 }
904
905 pub fn end(&self) -> Option<&Node> {
907 self.nodes.last()
908 }
909}
910
911pub trait FromValue: Sized {
917 fn from_value(value: &Value) -> crate::Result<Self>;
923}
924
925impl<T> FromValue for T
927where
928 T: for<'a> TryFrom<&'a Value, Error = UniError>,
929{
930 fn from_value(value: &Value) -> crate::Result<Self> {
931 Self::try_from(value)
932 }
933}
934
935macro_rules! impl_try_from_value_owned {
940 ($($t:ty),+ $(,)?) => {
941 $(
942 impl TryFrom<Value> for $t {
943 type Error = UniError;
944 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
945 Self::try_from(&value)
946 }
947 }
948 )+
949 };
950}
951
952impl_try_from_value_owned!(
953 String,
954 i64,
955 i32,
956 f64,
957 bool,
958 Vid,
959 Eid,
960 Vec<f32>,
961 Path,
962 Node,
963 Edge
964);
965
966fn type_error(expected: &str, value: &Value) -> UniError {
972 UniError::Type {
973 expected: expected.to_string(),
974 actual: format!("{:?}", value),
975 }
976}
977
978impl TryFrom<&Value> for String {
979 type Error = UniError;
980
981 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
982 match value {
983 Value::String(s) => Ok(s.clone()),
984 Value::Int(i) => Ok(i.to_string()),
985 Value::Float(f) => Ok(f.to_string()),
986 Value::Bool(b) => Ok(b.to_string()),
987 Value::Temporal(t) => Ok(t.to_string()),
988 _ => Err(type_error("String", value)),
989 }
990 }
991}
992
993impl TryFrom<&Value> for i64 {
994 type Error = UniError;
995
996 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1003 match value {
1004 Value::Int(i) => Ok(*i),
1005 Value::Float(f) => Ok(*f as i64),
1006 _ => Err(type_error("Int", value)),
1007 }
1008 }
1009}
1010
1011impl TryFrom<&Value> for i32 {
1012 type Error = UniError;
1013
1014 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1019 match value {
1020 Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
1021 expected: "i32".to_string(),
1022 actual: format!("Integer {} out of range", i),
1023 }),
1024 Value::Float(f) => {
1025 if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1026 return Err(UniError::Type {
1027 expected: "i32".to_string(),
1028 actual: format!("Float {} out of range", f),
1029 });
1030 }
1031 if f.fract() != 0.0 {
1032 return Err(UniError::Type {
1033 expected: "i32".to_string(),
1034 actual: format!("Float {} has fractional part", f),
1035 });
1036 }
1037 Ok(*f as i32)
1038 }
1039 _ => Err(type_error("Int", value)),
1040 }
1041 }
1042}
1043
1044impl TryFrom<&Value> for f64 {
1045 type Error = UniError;
1046
1047 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1048 match value {
1049 Value::Float(f) => Ok(*f),
1050 Value::Int(i) => Ok(*i as f64),
1051 _ => Err(type_error("Float", value)),
1052 }
1053 }
1054}
1055
1056impl TryFrom<&Value> for bool {
1057 type Error = UniError;
1058
1059 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1060 match value {
1061 Value::Bool(b) => Ok(*b),
1062 _ => Err(type_error("Bool", value)),
1063 }
1064 }
1065}
1066
1067impl TryFrom<&Value> for Vid {
1068 type Error = UniError;
1069
1070 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1071 match value {
1072 Value::Node(n) => Ok(n.vid),
1073 Value::String(s) => {
1074 if let Ok(id) = s.parse::<u64>() {
1075 return Ok(Vid::new(id));
1076 }
1077 Err(UniError::Type {
1078 expected: "Vid".into(),
1079 actual: s.clone(),
1080 })
1081 }
1082 Value::Int(i) => Ok(Vid::new(*i as u64)),
1083 _ => Err(type_error("Vid", value)),
1084 }
1085 }
1086}
1087
1088impl TryFrom<&Value> for Eid {
1089 type Error = UniError;
1090
1091 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1092 match value {
1093 Value::Edge(e) => Ok(e.eid),
1094 Value::String(s) => {
1095 if let Ok(id) = s.parse::<u64>() {
1096 return Ok(Eid::new(id));
1097 }
1098 Err(UniError::Type {
1099 expected: "Eid".into(),
1100 actual: s.clone(),
1101 })
1102 }
1103 Value::Int(i) => Ok(Eid::new(*i as u64)),
1104 _ => Err(type_error("Eid", value)),
1105 }
1106 }
1107}
1108
1109impl TryFrom<&Value> for Vec<f32> {
1110 type Error = UniError;
1111
1112 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1113 match value {
1114 Value::Vector(v) => Ok(v.clone()),
1115 Value::List(l) => {
1116 let mut vec = Vec::with_capacity(l.len());
1117 for item in l {
1118 match item {
1119 Value::Float(f) => vec.push(*f as f32),
1120 Value::Int(i) => vec.push(*i as f32),
1121 _ => return Err(type_error("Float", item)),
1122 }
1123 }
1124 Ok(vec)
1125 }
1126 _ => Err(type_error("Vector", value)),
1127 }
1128 }
1129}
1130
1131impl<T> TryFrom<&Value> for Option<T>
1132where
1133 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1134{
1135 type Error = UniError;
1136
1137 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1138 match value {
1139 Value::Null => Ok(None),
1140 _ => T::try_from(value).map(Some),
1141 }
1142 }
1143}
1144
1145impl<T> TryFrom<Value> for Option<T>
1146where
1147 T: TryFrom<Value, Error = UniError>,
1148{
1149 type Error = UniError;
1150 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1151 match value {
1152 Value::Null => Ok(None),
1153 _ => T::try_from(value).map(Some),
1154 }
1155 }
1156}
1157
1158impl<T> TryFrom<&Value> for Vec<T>
1159where
1160 T: for<'a> TryFrom<&'a Value, Error = UniError>,
1161{
1162 type Error = UniError;
1163
1164 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1165 match value {
1166 Value::List(l) => {
1167 let mut vec = Vec::with_capacity(l.len());
1168 for item in l {
1169 vec.push(T::try_from(item)?);
1170 }
1171 Ok(vec)
1172 }
1173 _ => Err(type_error("List", value)),
1174 }
1175 }
1176}
1177
1178impl<T> TryFrom<Value> for Vec<T>
1179where
1180 T: TryFrom<Value, Error = UniError>,
1181{
1182 type Error = UniError;
1183 fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1184 match value {
1185 Value::List(l) => {
1186 let mut vec = Vec::with_capacity(l.len());
1187 for item in l {
1188 vec.push(T::try_from(item)?);
1189 }
1190 Ok(vec)
1191 }
1192 other => Err(type_error("List", &other)),
1193 }
1194 }
1195}
1196
1197fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1203 keys.iter().find_map(|k| map.get(*k))
1204}
1205
1206fn extract_properties(value: &Value) -> HashMap<String, Value> {
1208 match value {
1209 Value::Map(m) => m.clone(),
1210 _ => HashMap::new(),
1211 }
1212}
1213
1214impl TryFrom<&Value> for Node {
1215 type Error = UniError;
1216
1217 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1218 match value {
1219 Value::Node(n) => Ok(n.clone()),
1220 Value::Map(m) => {
1221 let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1222 let props_val = m.get("properties");
1223
1224 let (Some(v), Some(p)) = (vid_val, props_val) else {
1225 return Err(type_error("Node Map", value));
1226 };
1227
1228 let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1230 label_list
1231 .iter()
1232 .filter_map(|v| {
1233 if let Value::String(s) = v {
1234 Some(s.clone())
1235 } else {
1236 None
1237 }
1238 })
1239 .collect()
1240 } else {
1241 Vec::new()
1242 };
1243
1244 Ok(Node {
1245 vid: Vid::try_from(v)?,
1246 labels,
1247 properties: extract_properties(p),
1248 })
1249 }
1250 _ => Err(type_error("Node", value)),
1251 }
1252 }
1253}
1254
1255impl TryFrom<&Value> for Edge {
1256 type Error = UniError;
1257
1258 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1259 match value {
1260 Value::Edge(e) => Ok(e.clone()),
1261 Value::Map(m) => {
1262 let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1263 let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1264 let src_val = get_with_fallback(m, &["_src", "src"]);
1265 let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1266 let props_val = m.get("properties");
1267
1268 let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1269 (eid_val, type_val, src_val, dst_val, props_val)
1270 else {
1271 return Err(type_error("Edge Map", value));
1272 };
1273
1274 Ok(Edge {
1275 eid: Eid::try_from(id)?,
1276 edge_type: String::try_from(t)?,
1277 src: Vid::try_from(s)?,
1278 dst: Vid::try_from(d)?,
1279 properties: extract_properties(p),
1280 })
1281 }
1282 _ => Err(type_error("Edge", value)),
1283 }
1284 }
1285}
1286
1287impl TryFrom<&Value> for Path {
1288 type Error = UniError;
1289
1290 fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1291 match value {
1292 Value::Path(p) => Ok(p.clone()),
1293 Value::Map(m) => {
1294 let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1295 (m.get("nodes"), m.get("relationships"))
1296 else {
1297 return Err(type_error("Path (Map with nodes/relationships)", value));
1298 };
1299
1300 let nodes = nodes_list
1301 .iter()
1302 .map(Node::try_from)
1303 .collect::<std::result::Result<Vec<_>, _>>()?;
1304
1305 let edges = rels_list
1306 .iter()
1307 .map(Edge::try_from)
1308 .collect::<std::result::Result<Vec<_>, _>>()?;
1309
1310 Ok(Path { nodes, edges })
1311 }
1312 _ => Err(type_error("Path", value)),
1313 }
1314 }
1315}
1316
1317impl From<String> for Value {
1322 fn from(v: String) -> Self {
1323 Value::String(v)
1324 }
1325}
1326
1327impl From<&str> for Value {
1328 fn from(v: &str) -> Self {
1329 Value::String(v.to_string())
1330 }
1331}
1332
1333impl From<i64> for Value {
1334 fn from(v: i64) -> Self {
1335 Value::Int(v)
1336 }
1337}
1338
1339impl From<i32> for Value {
1340 fn from(v: i32) -> Self {
1341 Value::Int(v as i64)
1342 }
1343}
1344
1345impl From<f64> for Value {
1346 fn from(v: f64) -> Self {
1347 Value::Float(v)
1348 }
1349}
1350
1351impl From<bool> for Value {
1352 fn from(v: bool) -> Self {
1353 Value::Bool(v)
1354 }
1355}
1356
1357impl From<Vec<f32>> for Value {
1358 fn from(v: Vec<f32>) -> Self {
1359 Value::Vector(v)
1360 }
1361}
1362
1363impl From<serde_json::Value> for Value {
1368 fn from(v: serde_json::Value) -> Self {
1369 match v {
1370 serde_json::Value::Null => Value::Null,
1371 serde_json::Value::Bool(b) => Value::Bool(b),
1372 serde_json::Value::Number(n) => {
1373 if let Some(i) = n.as_i64() {
1374 Value::Int(i)
1375 } else if let Some(f) = n.as_f64() {
1376 Value::Float(f)
1377 } else {
1378 Value::Null
1379 }
1380 }
1381 serde_json::Value::String(s) => Value::String(s),
1382 serde_json::Value::Array(arr) => {
1383 Value::List(arr.into_iter().map(Value::from).collect())
1384 }
1385 serde_json::Value::Object(obj) => {
1386 Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1387 }
1388 }
1389 }
1390}
1391
1392impl From<Value> for serde_json::Value {
1393 fn from(v: Value) -> Self {
1394 match v {
1395 Value::Null => serde_json::Value::Null,
1396 Value::Bool(b) => serde_json::Value::Bool(b),
1397 Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1398 Value::Float(f) => serde_json::Number::from_f64(f)
1399 .map(serde_json::Value::Number)
1400 .unwrap_or(serde_json::Value::Null), Value::String(s) => serde_json::Value::String(s),
1402 Value::Bytes(b) => {
1403 use base64::Engine;
1404 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1405 }
1406 Value::List(l) => {
1407 serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1408 }
1409 Value::Map(m) => {
1410 let mut map = serde_json::Map::new();
1411 for (k, v) in m {
1412 map.insert(k, v.into());
1413 }
1414 serde_json::Value::Object(map)
1415 }
1416 Value::Node(n) => {
1417 let mut map = serde_json::Map::new();
1418 map.insert(
1419 "_id".to_string(),
1420 serde_json::Value::String(n.vid.to_string()),
1421 );
1422 map.insert(
1423 "_labels".to_string(),
1424 serde_json::Value::Array(
1425 n.labels
1426 .into_iter()
1427 .map(serde_json::Value::String)
1428 .collect(),
1429 ),
1430 );
1431 let props: serde_json::Value = Value::Map(n.properties).into();
1432 map.insert("properties".to_string(), props);
1433 serde_json::Value::Object(map)
1434 }
1435 Value::Edge(e) => {
1436 let mut map = serde_json::Map::new();
1437 map.insert(
1438 "_id".to_string(),
1439 serde_json::Value::String(e.eid.to_string()),
1440 );
1441 map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1442 map.insert(
1443 "_src".to_string(),
1444 serde_json::Value::String(e.src.to_string()),
1445 );
1446 map.insert(
1447 "_dst".to_string(),
1448 serde_json::Value::String(e.dst.to_string()),
1449 );
1450 let props: serde_json::Value = Value::Map(e.properties).into();
1451 map.insert("properties".to_string(), props);
1452 serde_json::Value::Object(map)
1453 }
1454 Value::Path(p) => {
1455 let mut map = serde_json::Map::new();
1456 map.insert(
1457 "nodes".to_string(),
1458 Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1459 );
1460 map.insert(
1461 "relationships".to_string(),
1462 Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1463 );
1464 serde_json::Value::Object(map)
1465 }
1466 Value::Vector(v) => serde_json::Value::Array(
1467 v.into_iter()
1468 .map(|f| {
1469 serde_json::Number::from_f64(f as f64)
1470 .map(serde_json::Value::Number)
1471 .unwrap_or(serde_json::Value::Null)
1472 })
1473 .collect(),
1474 ),
1475 Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1476 }
1477 }
1478}
1479
1480#[macro_export]
1502macro_rules! unival {
1503 (null) => {
1505 $crate::Value::Null
1506 };
1507
1508 (true) => {
1510 $crate::Value::Bool(true)
1511 };
1512 (false) => {
1513 $crate::Value::Bool(false)
1514 };
1515
1516 ([ $($elem:tt),* $(,)? ]) => {
1518 $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1519 };
1520
1521 ({ $($key:tt : $val:tt),* $(,)? }) => {
1523 $crate::Value::Map({
1524 #[allow(unused_mut)]
1525 let mut map = ::std::collections::HashMap::new();
1526 $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1527 map
1528 })
1529 };
1530
1531 ($e:expr) => {
1533 $crate::Value::from($e)
1534 };
1535}
1536
1537impl From<usize> for Value {
1542 fn from(v: usize) -> Self {
1543 Value::Int(v as i64)
1544 }
1545}
1546
1547impl From<u64> for Value {
1548 fn from(v: u64) -> Self {
1549 Value::Int(v as i64)
1550 }
1551}
1552
1553impl From<f32> for Value {
1554 fn from(v: f32) -> Self {
1555 Value::Float(v as f64)
1556 }
1557}
1558
1559#[cfg(test)]
1564mod tests {
1565 use super::*;
1566
1567 #[test]
1568 fn test_accessor_methods() {
1569 assert!(Value::Null.is_null());
1570 assert!(!Value::Int(1).is_null());
1571
1572 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1573 assert_eq!(Value::Int(42).as_bool(), None);
1574
1575 assert_eq!(Value::Int(42).as_i64(), Some(42));
1576 assert_eq!(Value::Float(2.5).as_i64(), None);
1577
1578 assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1580 assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1581 assert_eq!(Value::String("x".into()).as_f64(), None);
1582
1583 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1584 assert_eq!(Value::Int(1).as_str(), None);
1585
1586 assert!(Value::Int(1).is_i64());
1587 assert!(!Value::Float(1.0).is_i64());
1588
1589 assert!(Value::Float(1.0).is_f64());
1590 assert!(!Value::Int(1).is_f64());
1591
1592 assert!(Value::Int(1).is_number());
1593 assert!(Value::Float(1.0).is_number());
1594 assert!(!Value::String("x".into()).is_number());
1595 }
1596
1597 #[test]
1598 fn test_serde_json_roundtrip() {
1599 let val = Value::Int(42);
1600 let json: serde_json::Value = val.clone().into();
1601 let back: Value = json.into();
1602 assert_eq!(val, back);
1603
1604 let val = Value::Float(2.5);
1605 let json: serde_json::Value = val.clone().into();
1606 let back: Value = json.into();
1607 assert_eq!(val, back);
1608
1609 let val = Value::String("hello".into());
1610 let json: serde_json::Value = val.clone().into();
1611 let back: Value = json.into();
1612 assert_eq!(val, back);
1613
1614 let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1615 let json: serde_json::Value = val.clone().into();
1616 let back: Value = json.into();
1617 assert_eq!(val, back);
1618 }
1619
1620 #[test]
1621 fn test_unival_macro() {
1622 assert_eq!(unival!(null), Value::Null);
1623 assert_eq!(unival!(true), Value::Bool(true));
1624 assert_eq!(unival!(false), Value::Bool(false));
1625 assert_eq!(unival!(42_i64), Value::Int(42));
1626 assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1627 assert_eq!(unival!("hello"), Value::String("hello".into()));
1628
1629 let list = unival!([1_i64, 2_i64]);
1631 assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1632
1633 let map = unival!({"key": "val", "num": 42_i64});
1635 if let Value::Map(m) = &map {
1636 assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1637 assert_eq!(m.get("num"), Some(&Value::Int(42)));
1638 } else {
1639 panic!("Expected Map");
1640 }
1641
1642 let x: i64 = 99;
1644 assert_eq!(unival!(x), Value::Int(99));
1645 }
1646
1647 #[test]
1648 fn test_int_float_distinction_preserved() {
1649 let int_val = Value::Int(42);
1651 let float_val = Value::Float(42.0);
1652
1653 assert!(int_val.is_i64());
1654 assert!(!int_val.is_f64());
1655
1656 assert!(float_val.is_f64());
1657 assert!(!float_val.is_i64());
1658
1659 assert_ne!(int_val, float_val);
1661 }
1662
1663 #[test]
1664 fn test_temporal_display_zero_seconds_omitted() {
1665 let lt = TemporalValue::LocalTime {
1667 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1668 };
1669 assert_eq!(lt.to_string(), "12:00");
1670
1671 let lt2 = TemporalValue::LocalTime {
1673 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1674 };
1675 assert_eq!(lt2.to_string(), "12:31:14");
1676
1677 let lt3 = TemporalValue::LocalTime {
1679 nanos_since_midnight: 500_000_000,
1680 };
1681 assert_eq!(lt3.to_string(), "00:00:00.5");
1682
1683 let t = TemporalValue::Time {
1685 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1686 offset_seconds: 0,
1687 };
1688 assert_eq!(t.to_string(), "12:00Z");
1689
1690 let t2 = TemporalValue::Time {
1692 nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1693 offset_seconds: 3600,
1694 };
1695 assert_eq!(t2.to_string(), "12:31:14+01:00");
1696
1697 let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1699 .unwrap()
1700 .and_hms_opt(12, 31, 0)
1701 .unwrap()
1702 .and_utc()
1703 .timestamp_nanos_opt()
1704 .unwrap();
1705 let ldt = TemporalValue::LocalDateTime {
1706 nanos_since_epoch: epoch_nanos,
1707 };
1708 assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1709
1710 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1712 .unwrap()
1713 .and_hms_opt(11, 31, 0)
1714 .unwrap()
1715 .and_utc()
1716 .timestamp_nanos_opt()
1717 .unwrap();
1718 let dt = TemporalValue::DateTime {
1719 nanos_since_epoch: utc_nanos,
1720 offset_seconds: 3600,
1721 timezone_name: None,
1722 };
1723 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1724
1725 let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1727 .unwrap()
1728 .and_hms_nano_opt(20, 40, 32, 142_000_000)
1729 .unwrap()
1730 .and_utc()
1731 .timestamp_nanos_opt()
1732 .unwrap();
1733 let dt2 = TemporalValue::DateTime {
1734 nanos_since_epoch: utc_nanos2,
1735 offset_seconds: 3600,
1736 timezone_name: None,
1737 };
1738 assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1739
1740 let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1742 .unwrap()
1743 .and_hms_opt(12, 31, 0)
1744 .unwrap()
1745 .and_utc()
1746 .timestamp_nanos_opt()
1747 .unwrap();
1748 let dt3 = TemporalValue::DateTime {
1749 nanos_since_epoch: utc_nanos3,
1750 offset_seconds: 0,
1751 timezone_name: None,
1752 };
1753 assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1754 }
1755
1756 #[test]
1757 fn test_temporal_display_fractional_trailing_zeros_stripped() {
1758 let d = TemporalValue::Duration {
1760 months: 0,
1761 days: 0,
1762 nanos: 900_000_000,
1763 };
1764 assert_eq!(d.to_string(), "PT0.9S");
1765
1766 let d2 = TemporalValue::Duration {
1768 months: 0,
1769 days: 0,
1770 nanos: 400_000_000,
1771 };
1772 assert_eq!(d2.to_string(), "PT0.4S");
1773
1774 let d3 = TemporalValue::Duration {
1776 months: 0,
1777 days: 0,
1778 nanos: 142_000_000,
1779 };
1780 assert_eq!(d3.to_string(), "PT0.142S");
1781
1782 let d4 = TemporalValue::Duration {
1784 months: 0,
1785 days: 0,
1786 nanos: 1,
1787 };
1788 assert_eq!(d4.to_string(), "PT0.000000001S");
1789 }
1790
1791 #[test]
1792 fn test_temporal_display_offset_second_precision() {
1793 let t = TemporalValue::Time {
1795 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1796 offset_seconds: 2 * 3600 + 5 * 60 + 59,
1797 };
1798 assert_eq!(t.to_string(), "12:00+02:05:59");
1799
1800 let t2 = TemporalValue::Time {
1802 nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1803 offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1804 };
1805 assert_eq!(t2.to_string(), "12:00-02:05:07");
1806 }
1807
1808 #[test]
1809 fn test_temporal_display_datetime_with_timezone_name() {
1810 let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1811 .unwrap()
1812 .and_hms_opt(11, 31, 0)
1813 .unwrap()
1814 .and_utc()
1815 .timestamp_nanos_opt()
1816 .unwrap();
1817 let dt = TemporalValue::DateTime {
1818 nanos_since_epoch: utc_nanos,
1819 offset_seconds: 3600,
1820 timezone_name: Some("Europe/Stockholm".to_string()),
1821 };
1822 assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
1823 }
1824}