Skip to main content

uni_common/
value.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4//! Typed value representation for graph properties and query results.
5//!
6//! [`Value`] is the canonical internal representation for all property values,
7//! query parameters, and expression results. Unlike `serde_json::Value`, it
8//! distinguishes integers from floats (`Int(i64)` vs `Float(f64)`) and includes
9//! graph-specific variants (`Node`, `Edge`, `Path`, `Vector`).
10//!
11//! Conversion to/from `serde_json::Value` is provided at the serialization
12//! boundary via `From` implementations.
13
14use 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// ============================================================================
22// Temporal Value Types
23// ============================================================================
24
25/// Classification of temporal types for dispatch.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum TemporalType {
28    Date,
29    LocalTime,
30    Time,
31    LocalDateTime,
32    DateTime,
33    Duration,
34}
35
36/// Typed temporal value representation.
37///
38/// Stores temporal values in their native numeric form for O(1) comparisons
39/// and direct Arrow column construction, with Cypher formatting applied only
40/// at the output boundary via [`std::fmt::Display`].
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub enum TemporalValue {
43    /// Date: days since Unix epoch (1970-01-01). Arrow: Date32.
44    Date { days_since_epoch: i32 },
45    /// Local time (no timezone): nanoseconds since midnight. Arrow: Time64(ns).
46    LocalTime { nanos_since_midnight: i64 },
47    /// Time with timezone offset: nanoseconds since midnight + offset. Arrow: Time64(ns) + metadata.
48    Time {
49        nanos_since_midnight: i64,
50        offset_seconds: i32,
51    },
52    /// Local datetime (no timezone): nanoseconds since Unix epoch. Arrow: Timestamp(ns, None).
53    LocalDateTime { nanos_since_epoch: i64 },
54    /// Datetime with timezone: nanoseconds since Unix epoch (UTC) + offset + optional tz name.
55    /// Arrow: Timestamp(ns, Some("UTC")).
56    DateTime {
57        nanos_since_epoch: i64,
58        offset_seconds: i32,
59        timezone_name: Option<String>,
60    },
61    /// Duration with calendar semantics: months + days + nanoseconds.
62    /// Matches Cypher's duration model which preserves calendar components.
63    Duration { months: i64, days: i64, nanos: i64 },
64}
65
66impl Eq for TemporalValue {}
67
68impl Hash for TemporalValue {
69    fn hash<H: Hasher>(&self, state: &mut H) {
70        std::mem::discriminant(self).hash(state);
71        match self {
72            TemporalValue::Date { days_since_epoch } => days_since_epoch.hash(state),
73            TemporalValue::LocalTime {
74                nanos_since_midnight,
75            } => nanos_since_midnight.hash(state),
76            TemporalValue::Time {
77                nanos_since_midnight,
78                offset_seconds,
79            } => {
80                nanos_since_midnight.hash(state);
81                offset_seconds.hash(state);
82            }
83            TemporalValue::LocalDateTime { nanos_since_epoch } => nanos_since_epoch.hash(state),
84            TemporalValue::DateTime {
85                nanos_since_epoch,
86                offset_seconds,
87                timezone_name,
88            } => {
89                nanos_since_epoch.hash(state);
90                offset_seconds.hash(state);
91                timezone_name.hash(state);
92            }
93            TemporalValue::Duration {
94                months,
95                days,
96                nanos,
97            } => {
98                months.hash(state);
99                days.hash(state);
100                nanos.hash(state);
101            }
102        }
103    }
104}
105
106impl TemporalValue {
107    /// Returns the temporal type classification.
108    pub fn temporal_type(&self) -> TemporalType {
109        match self {
110            TemporalValue::Date { .. } => TemporalType::Date,
111            TemporalValue::LocalTime { .. } => TemporalType::LocalTime,
112            TemporalValue::Time { .. } => TemporalType::Time,
113            TemporalValue::LocalDateTime { .. } => TemporalType::LocalDateTime,
114            TemporalValue::DateTime { .. } => TemporalType::DateTime,
115            TemporalValue::Duration { .. } => TemporalType::Duration,
116        }
117    }
118
119    // -----------------------------------------------------------------------
120    // Component accessors
121    // -----------------------------------------------------------------------
122
123    /// Year component, or None for time-only/duration types.
124    pub fn year(&self) -> Option<i64> {
125        self.to_date().map(|d| d.year() as i64)
126    }
127
128    /// Month component (1-12), or None for time-only/duration types.
129    pub fn month(&self) -> Option<i64> {
130        self.to_date().map(|d| d.month() as i64)
131    }
132
133    /// Day-of-month component (1-31), or None for time-only/duration types.
134    pub fn day(&self) -> Option<i64> {
135        self.to_date().map(|d| d.day() as i64)
136    }
137
138    /// Hour component (0-23), or None for date-only types.
139    pub fn hour(&self) -> Option<i64> {
140        self.to_time().map(|t| t.hour() as i64)
141    }
142
143    /// Minute component (0-59), or None for date-only types.
144    pub fn minute(&self) -> Option<i64> {
145        self.to_time().map(|t| t.minute() as i64)
146    }
147
148    /// Second component (0-59), or None for date-only types.
149    pub fn second(&self) -> Option<i64> {
150        self.to_time().map(|t| t.second() as i64)
151    }
152
153    /// Millisecond sub-second component (0-999), or None for date-only types.
154    pub fn millisecond(&self) -> Option<i64> {
155        self.to_time().map(|t| (t.nanosecond() / 1_000_000) as i64)
156    }
157
158    /// Microsecond sub-second component (0-999_999), or None for date-only types.
159    pub fn microsecond(&self) -> Option<i64> {
160        self.to_time().map(|t| (t.nanosecond() / 1_000) as i64)
161    }
162
163    /// Nanosecond sub-second component (0-999_999_999), or None for date-only types.
164    pub fn nanosecond(&self) -> Option<i64> {
165        self.to_time().map(|t| t.nanosecond() as i64)
166    }
167
168    /// Quarter (1-4), or None for time-only/duration types.
169    pub fn quarter(&self) -> Option<i64> {
170        self.to_date().map(|d| ((d.month() - 1) / 3 + 1) as i64)
171    }
172
173    /// ISO week number (1-53), or None for time-only/duration types.
174    pub fn week(&self) -> Option<i64> {
175        self.to_date().map(|d| d.iso_week().week() as i64)
176    }
177
178    /// ISO week year, or None for time-only/duration types.
179    pub fn week_year(&self) -> Option<i64> {
180        self.to_date().map(|d| d.iso_week().year() as i64)
181    }
182
183    /// Ordinal day of year (1-366), or None for time-only/duration types.
184    pub fn ordinal_day(&self) -> Option<i64> {
185        self.to_date().map(|d| d.ordinal() as i64)
186    }
187
188    /// ISO day of week (Monday=1, Sunday=7), or None for time-only/duration types.
189    pub fn day_of_week(&self) -> Option<i64> {
190        self.to_date()
191            .map(|d| (d.weekday().num_days_from_monday() + 1) as i64)
192    }
193
194    /// Day of quarter (1-92), or None for time-only/duration types.
195    pub fn day_of_quarter(&self) -> Option<i64> {
196        self.to_date().map(|d| {
197            let quarter_start_month = ((d.month() - 1) / 3) * 3 + 1;
198            let quarter_start =
199                chrono::NaiveDate::from_ymd_opt(d.year(), quarter_start_month, 1).unwrap();
200            d.signed_duration_since(quarter_start).num_days() + 1
201        })
202    }
203
204    /// Timezone name if available (e.g., "Europe/Stockholm").
205    pub fn timezone(&self) -> Option<&str> {
206        match self {
207            TemporalValue::DateTime {
208                timezone_name: Some(name),
209                ..
210            } => Some(name.as_str()),
211            _ => None,
212        }
213    }
214
215    /// Returns the raw offset in seconds for types that carry a timezone offset.
216    fn raw_offset_seconds(&self) -> Option<i32> {
217        match self {
218            TemporalValue::Time { offset_seconds, .. }
219            | TemporalValue::DateTime { offset_seconds, .. } => Some(*offset_seconds),
220            _ => None,
221        }
222    }
223
224    /// Offset string (e.g., "+01:00", "Z").
225    pub fn offset(&self) -> Option<String> {
226        self.raw_offset_seconds().map(format_offset)
227    }
228
229    /// Offset in minutes.
230    pub fn offset_minutes(&self) -> Option<i64> {
231        self.raw_offset_seconds().map(|s| s as i64 / 60)
232    }
233
234    /// Offset in seconds.
235    pub fn offset_seconds_value(&self) -> Option<i64> {
236        self.raw_offset_seconds().map(|s| s as i64)
237    }
238
239    /// Returns the raw epoch nanos for types that store nanoseconds since epoch.
240    fn raw_epoch_nanos(&self) -> Option<i64> {
241        match self {
242            TemporalValue::DateTime {
243                nanos_since_epoch, ..
244            }
245            | TemporalValue::LocalDateTime {
246                nanos_since_epoch, ..
247            } => Some(*nanos_since_epoch),
248            _ => None,
249        }
250    }
251
252    /// Epoch seconds (for datetime/localdatetime types).
253    pub fn epoch_seconds(&self) -> Option<i64> {
254        self.raw_epoch_nanos().map(|n| n / 1_000_000_000)
255    }
256
257    /// Epoch milliseconds (for datetime/localdatetime types).
258    pub fn epoch_millis(&self) -> Option<i64> {
259        self.raw_epoch_nanos().map(|n| n / 1_000_000)
260    }
261
262    // -----------------------------------------------------------------------
263    // Internal chrono conversion helpers
264    // -----------------------------------------------------------------------
265
266    /// Extract a NaiveDate from types that have a date component.
267    pub fn to_date(&self) -> Option<chrono::NaiveDate> {
268        let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1)?;
269        match self {
270            TemporalValue::Date { days_since_epoch } => {
271                epoch.checked_add_signed(chrono::Duration::days(*days_since_epoch as i64))
272            }
273            TemporalValue::LocalDateTime { nanos_since_epoch } => {
274                let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
275                Some(dt.date_naive())
276            }
277            TemporalValue::DateTime {
278                nanos_since_epoch,
279                offset_seconds,
280                ..
281            } => {
282                // Convert UTC nanos to local time by adding offset
283                let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
284                let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
285                Some(dt.date_naive())
286            }
287            _ => None,
288        }
289    }
290
291    /// Extract a NaiveTime from types that have a time component.
292    pub fn to_time(&self) -> Option<chrono::NaiveTime> {
293        match self {
294            TemporalValue::LocalTime {
295                nanos_since_midnight,
296            }
297            | TemporalValue::Time {
298                nanos_since_midnight,
299                ..
300            } => nanos_to_time(*nanos_since_midnight),
301            TemporalValue::LocalDateTime { nanos_since_epoch } => {
302                let dt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch);
303                Some(dt.naive_utc().time())
304            }
305            TemporalValue::DateTime {
306                nanos_since_epoch,
307                offset_seconds,
308                ..
309            } => {
310                let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
311                let dt = chrono::DateTime::from_timestamp_nanos(local_nanos);
312                Some(dt.naive_utc().time())
313            }
314            _ => None,
315        }
316    }
317}
318
319/// Convert nanoseconds since midnight to NaiveTime.
320fn nanos_to_time(nanos: i64) -> Option<chrono::NaiveTime> {
321    let total_secs = nanos / 1_000_000_000;
322    let h = (total_secs / 3600) as u32;
323    let m = ((total_secs % 3600) / 60) as u32;
324    let s = (total_secs % 60) as u32;
325    let ns = (nanos % 1_000_000_000) as u32;
326    chrono::NaiveTime::from_hms_nano_opt(h, m, s, ns)
327}
328
329/// Format an offset in seconds as "+HH:MM" or "Z".
330fn format_offset(offset_seconds: i32) -> String {
331    if offset_seconds == 0 {
332        return "Z".to_string();
333    }
334    format_offset_numeric(offset_seconds)
335}
336
337/// Format offset always as `+HH:MM` or `+HH:MM:SS` (never as `Z`).
338fn format_offset_numeric(offset_seconds: i32) -> String {
339    let sign = if offset_seconds >= 0 { '+' } else { '-' };
340    let abs = offset_seconds.unsigned_abs();
341    let h = abs / 3600;
342    let m = (abs % 3600) / 60;
343    let s = abs % 60;
344    if s != 0 {
345        format!("{}{:02}:{:02}:{:02}", sign, h, m, s)
346    } else {
347        format!("{}{:02}:{:02}", sign, h, m)
348    }
349}
350
351/// Format sub-second fractional part, stripping all trailing zeros.
352fn format_fractional(nanos: u32) -> String {
353    if nanos == 0 {
354        return String::new();
355    }
356    let s = format!("{:09}", nanos);
357    let trimmed = s.trim_end_matches('0');
358    format!(".{}", trimmed)
359}
360
361/// Format time as HH:MM[:SS[.n...]] — omit :SS when seconds and sub-seconds are zero.
362fn format_time_component(hour: u32, minute: u32, second: u32, nanos: u32) -> String {
363    if second == 0 && nanos == 0 {
364        format!("{:02}:{:02}", hour, minute)
365    } else {
366        let frac = format_fractional(nanos);
367        format!("{:02}:{:02}:{:02}{}", hour, minute, second, frac)
368    }
369}
370
371/// Format a NaiveTime as a canonical time string.
372fn format_naive_time(t: &chrono::NaiveTime) -> String {
373    format_time_component(t.hour(), t.minute(), t.second(), t.nanosecond())
374}
375
376/// Convert nanos since midnight to NaiveTime, defaulting to midnight on invalid input.
377fn nanos_to_time_or_midnight(nanos: i64) -> chrono::NaiveTime {
378    nanos_to_time(nanos).unwrap_or_else(|| chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
379}
380
381impl fmt::Display for TemporalValue {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        match self {
384            TemporalValue::Date { days_since_epoch } => {
385                let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
386                let date = epoch + chrono::Duration::days(*days_since_epoch as i64);
387                write!(f, "{}", date.format("%Y-%m-%d"))
388            }
389            TemporalValue::LocalTime {
390                nanos_since_midnight,
391            } => {
392                let time = nanos_to_time_or_midnight(*nanos_since_midnight);
393                write!(f, "{}", format_naive_time(&time))
394            }
395            TemporalValue::Time {
396                nanos_since_midnight,
397                offset_seconds,
398            } => {
399                let time = nanos_to_time_or_midnight(*nanos_since_midnight);
400                write!(
401                    f,
402                    "{}{}",
403                    format_naive_time(&time),
404                    format_offset(*offset_seconds)
405                )
406            }
407            TemporalValue::LocalDateTime { nanos_since_epoch } => {
408                let ndt = chrono::DateTime::from_timestamp_nanos(*nanos_since_epoch).naive_utc();
409                write!(
410                    f,
411                    "{}T{}",
412                    ndt.date().format("%Y-%m-%d"),
413                    format_naive_time(&ndt.time())
414                )
415            }
416            TemporalValue::DateTime {
417                nanos_since_epoch,
418                offset_seconds,
419                timezone_name,
420            } => {
421                // Display in local time (UTC nanos + offset)
422                let local_nanos = nanos_since_epoch + (*offset_seconds as i64) * 1_000_000_000;
423                let ndt = chrono::DateTime::from_timestamp_nanos(local_nanos).naive_utc();
424                let tz = format_offset(*offset_seconds);
425                write!(
426                    f,
427                    "{}T{}{}",
428                    ndt.date().format("%Y-%m-%d"),
429                    format_naive_time(&ndt.time()),
430                    tz
431                )?;
432                if let Some(name) = timezone_name {
433                    write!(f, "[{}]", name)?;
434                }
435                Ok(())
436            }
437            TemporalValue::Duration {
438                months,
439                days,
440                nanos,
441            } => {
442                write!(f, "P")?;
443                let years = months / 12;
444                let rem_months = months % 12;
445                if years != 0 {
446                    write!(f, "{}Y", years)?;
447                }
448                if rem_months != 0 {
449                    write!(f, "{}M", rem_months)?;
450                }
451                if *days != 0 {
452                    write!(f, "{}D", days)?;
453                }
454                // Time part
455                let abs_nanos = nanos.unsigned_abs() as i128;
456                let nanos_sign = if *nanos < 0 { -1i64 } else { 1 };
457                let total_secs = (abs_nanos / 1_000_000_000) as i64;
458                let frac_nanos = (abs_nanos % 1_000_000_000) as u32;
459                let hours = total_secs / 3600;
460                let mins = (total_secs % 3600) / 60;
461                let secs = total_secs % 60;
462
463                if hours != 0 || mins != 0 || secs != 0 || frac_nanos != 0 {
464                    write!(f, "T")?;
465                    if hours != 0 {
466                        write!(f, "{}H", hours * nanos_sign)?;
467                    }
468                    if mins != 0 {
469                        write!(f, "{}M", mins * nanos_sign)?;
470                    }
471                    if secs != 0 || frac_nanos != 0 {
472                        let frac = format_fractional(frac_nanos);
473                        if nanos_sign < 0 && (secs != 0 || frac_nanos != 0) {
474                            write!(f, "-{}{}", secs, frac)?;
475                        } else {
476                            write!(f, "{}{}", secs, frac)?;
477                        }
478                        write!(f, "S")?;
479                    }
480                } else if years == 0 && rem_months == 0 && *days == 0 {
481                    // Zero duration
482                    write!(f, "T0S")?;
483                }
484                Ok(())
485            }
486        }
487    }
488}
489
490// Use chrono traits in component accessors - needed by TemporalValue accessors
491use chrono::Datelike as _;
492use chrono::Timelike as _;
493
494/// Dynamic value type for properties, parameters, and results.
495///
496/// Preserves the distinction between integers and floats, and includes
497/// graph-specific variants for nodes, edges, paths, and vectors.
498///
499/// Note: `Eq` and `Hash` are implemented manually to support using `Value` as
500/// HashMap keys. Floats are compared/hashed by their bit representation.
501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
502#[serde(untagged)]
503#[non_exhaustive]
504pub enum Value {
505    /// JSON/Cypher null.
506    Null,
507    /// Boolean value.
508    Bool(bool),
509    /// 64-bit signed integer.
510    Int(i64),
511    /// 64-bit floating-point number.
512    Float(f64),
513    /// UTF-8 string.
514    String(String),
515    /// Raw byte buffer.
516    Bytes(Vec<u8>),
517    /// Ordered list of values.
518    List(Vec<Value>),
519    /// String-keyed map of values.
520    Map(HashMap<String, Value>),
521
522    // Graph-specific
523    /// Graph node with VID, label, and properties.
524    Node(Node),
525    /// Graph edge with EID, type, endpoints, and properties.
526    Edge(Edge),
527    /// Graph path (alternating nodes and edges).
528    Path(Path),
529
530    // Vector
531    /// Dense float vector for similarity search.
532    Vector(Vec<f32>),
533
534    // Temporal
535    /// Typed temporal value (date, time, datetime, duration).
536    Temporal(TemporalValue),
537}
538
539// ---------------------------------------------------------------------------
540// Accessor methods (mirrors serde_json::Value API for migration ease)
541// ---------------------------------------------------------------------------
542
543impl Value {
544    /// Returns `true` if this value is `Null`.
545    pub fn is_null(&self) -> bool {
546        matches!(self, Value::Null)
547    }
548
549    /// Returns the boolean if this is `Bool`, otherwise `None`.
550    pub fn as_bool(&self) -> Option<bool> {
551        match self {
552            Value::Bool(b) => Some(*b),
553            _ => None,
554        }
555    }
556
557    /// Returns the integer if this is `Int`, otherwise `None`.
558    pub fn as_i64(&self) -> Option<i64> {
559        match self {
560            Value::Int(i) => Some(*i),
561            _ => None,
562        }
563    }
564
565    /// Returns the integer as `u64` if this is a non-negative `Int`, otherwise `None`.
566    pub fn as_u64(&self) -> Option<u64> {
567        match self {
568            Value::Int(i) if *i >= 0 => Some(*i as u64),
569            _ => None,
570        }
571    }
572
573    /// Returns a float, coercing `Int` to `f64` if needed.
574    ///
575    /// Returns `None` for non-numeric variants.
576    pub fn as_f64(&self) -> Option<f64> {
577        match self {
578            Value::Float(f) => Some(*f),
579            Value::Int(i) => Some(*i as f64),
580            _ => None,
581        }
582    }
583
584    /// Returns the string slice if this is `String`, otherwise `None`.
585    pub fn as_str(&self) -> Option<&str> {
586        match self {
587            Value::String(s) => Some(s),
588            _ => None,
589        }
590    }
591
592    /// Returns `true` if this is `Int`.
593    pub fn is_i64(&self) -> bool {
594        matches!(self, Value::Int(_))
595    }
596
597    /// Returns `true` if this is `Float` (not `Int`).
598    pub fn is_f64(&self) -> bool {
599        matches!(self, Value::Float(_))
600    }
601
602    /// Returns `true` if this is `String`.
603    pub fn is_string(&self) -> bool {
604        matches!(self, Value::String(_))
605    }
606
607    /// Returns `true` if this is `Int` or `Float`.
608    pub fn is_number(&self) -> bool {
609        matches!(self, Value::Int(_) | Value::Float(_))
610    }
611
612    /// Returns the list if this is `List`, otherwise `None`.
613    pub fn as_array(&self) -> Option<&Vec<Value>> {
614        match self {
615            Value::List(l) => Some(l),
616            _ => None,
617        }
618    }
619
620    /// Returns the map if this is `Map`, otherwise `None`.
621    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
622        match self {
623            Value::Map(m) => Some(m),
624            _ => None,
625        }
626    }
627
628    /// Returns `true` if this is `Bool`.
629    pub fn is_bool(&self) -> bool {
630        matches!(self, Value::Bool(_))
631    }
632
633    /// Returns `true` if this is `List`.
634    pub fn is_list(&self) -> bool {
635        matches!(self, Value::List(_))
636    }
637
638    /// Returns `true` if this is `Map`.
639    pub fn is_map(&self) -> bool {
640        matches!(self, Value::Map(_))
641    }
642
643    /// Gets a value by key if this is a `Map`.
644    ///
645    /// Returns `None` if not a map or key doesn't exist.
646    pub fn get(&self, key: &str) -> Option<&Value> {
647        match self {
648            Value::Map(m) => m.get(key),
649            _ => None,
650        }
651    }
652
653    /// Returns `true` if this is a `Temporal` value.
654    pub fn is_temporal(&self) -> bool {
655        matches!(self, Value::Temporal(_))
656    }
657
658    /// Returns the temporal value reference if this is `Temporal`, otherwise `None`.
659    pub fn as_temporal(&self) -> Option<&TemporalValue> {
660        match self {
661            Value::Temporal(t) => Some(t),
662            _ => None,
663        }
664    }
665}
666
667impl fmt::Display for Value {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        match self {
670            Value::Null => write!(f, "null"),
671            Value::Bool(b) => write!(f, "{b}"),
672            Value::Int(i) => write!(f, "{i}"),
673            Value::Float(v) => {
674                if v.fract() == 0.0 && v.is_finite() {
675                    write!(f, "{v:.1}")
676                } else {
677                    write!(f, "{v}")
678                }
679            }
680            Value::String(s) => write!(f, "{s}"),
681            Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
682            Value::List(l) => {
683                write!(f, "[")?;
684                for (i, item) in l.iter().enumerate() {
685                    if i > 0 {
686                        write!(f, ", ")?;
687                    }
688                    write!(f, "{item}")?;
689                }
690                write!(f, "]")
691            }
692            Value::Map(m) => {
693                write!(f, "{{")?;
694                for (i, (k, v)) in m.iter().enumerate() {
695                    if i > 0 {
696                        write!(f, ", ")?;
697                    }
698                    write!(f, "{k}: {v}")?;
699                }
700                write!(f, "}}")
701            }
702            Value::Node(n) => write!(f, "(:{} {{vid: {}}})", n.labels.join(":"), n.vid),
703            Value::Edge(e) => write!(f, "-[:{}]-", e.edge_type),
704            Value::Path(p) => write!(
705                f,
706                "<path: {} nodes, {} edges>",
707                p.nodes.len(),
708                p.edges.len()
709            ),
710            Value::Vector(v) => write!(f, "<vector: {} dims>", v.len()),
711            Value::Temporal(t) => write!(f, "{t}"),
712        }
713    }
714}
715
716// ---------------------------------------------------------------------------
717// Eq and Hash implementations
718// ---------------------------------------------------------------------------
719
720impl Eq for Value {}
721
722impl Hash for Value {
723    fn hash<H: Hasher>(&self, state: &mut H) {
724        // Discriminant first for type safety
725        std::mem::discriminant(self).hash(state);
726        match self {
727            Value::Null => {}
728            Value::Bool(b) => b.hash(state),
729            Value::Int(i) => i.hash(state),
730            Value::Float(f) => f.to_bits().hash(state),
731            Value::String(s) => s.hash(state),
732            Value::Bytes(b) => b.hash(state),
733            Value::List(l) => l.hash(state),
734            Value::Map(m) => hash_map(m, state),
735            Value::Node(n) => n.hash(state),
736            Value::Edge(e) => e.hash(state),
737            Value::Path(p) => p.hash(state),
738            Value::Vector(v) => {
739                v.len().hash(state);
740                for f in v {
741                    f.to_bits().hash(state);
742                }
743            }
744            Value::Temporal(t) => t.hash(state),
745        }
746    }
747}
748
749// ---------------------------------------------------------------------------
750// Graph entity types
751// ---------------------------------------------------------------------------
752
753/// Helper to hash a HashMap deterministically by sorting keys.
754fn hash_map<H: Hasher>(m: &HashMap<String, Value>, state: &mut H) {
755    let mut pairs: Vec<_> = m.iter().collect();
756    pairs.sort_by_key(|(k, _)| *k);
757    pairs.len().hash(state);
758    for (k, v) in pairs {
759        k.hash(state);
760        v.hash(state);
761    }
762}
763
764/// Graph node with identity, labels, and properties.
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
766pub struct Node {
767    /// Internal vertex identifier.
768    pub vid: Vid,
769    /// Node labels (multi-label support).
770    pub labels: Vec<String>,
771    /// Property key-value pairs.
772    pub properties: HashMap<String, Value>,
773}
774
775impl Hash for Node {
776    fn hash<H: Hasher>(&self, state: &mut H) {
777        self.vid.hash(state);
778        let mut sorted_labels = self.labels.clone();
779        sorted_labels.sort();
780        sorted_labels.hash(state);
781        hash_map(&self.properties, state);
782    }
783}
784
785impl Node {
786    /// Gets a typed property by name.
787    ///
788    /// # Errors
789    ///
790    /// Returns `UniError::Query` if the property is missing,
791    /// or `UniError::Type` if it cannot be converted.
792    pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
793        let val = self
794            .properties
795            .get(property)
796            .ok_or_else(|| UniError::Query {
797                message: format!("Property '{}' not found on node {}", property, self.vid),
798                query: None,
799            })?;
800        T::from_value(val)
801    }
802
803    /// Tries to get a typed property, returning `None` on failure.
804    pub fn try_get<T: FromValue>(&self, property: &str) -> Option<T> {
805        self.properties
806            .get(property)
807            .and_then(|v| T::from_value(v).ok())
808    }
809}
810
811/// Graph edge with identity, type, endpoints, and properties.
812#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
813pub struct Edge {
814    /// Internal edge identifier.
815    pub eid: Eid,
816    /// Relationship type name.
817    pub edge_type: String,
818    /// Source vertex ID.
819    pub src: Vid,
820    /// Destination vertex ID.
821    pub dst: Vid,
822    /// Property key-value pairs.
823    pub properties: HashMap<String, Value>,
824}
825
826impl Hash for Edge {
827    fn hash<H: Hasher>(&self, state: &mut H) {
828        self.eid.hash(state);
829        self.edge_type.hash(state);
830        self.src.hash(state);
831        self.dst.hash(state);
832        hash_map(&self.properties, state);
833    }
834}
835
836impl Edge {
837    /// Gets a typed property by name.
838    ///
839    /// # Errors
840    ///
841    /// Returns `UniError::Query` if the property is missing,
842    /// or `UniError::Type` if it cannot be converted.
843    pub fn get<T: FromValue>(&self, property: &str) -> crate::Result<T> {
844        let val = self
845            .properties
846            .get(property)
847            .ok_or_else(|| UniError::Query {
848                message: format!("Property '{}' not found on edge {}", property, self.eid),
849                query: None,
850            })?;
851        T::from_value(val)
852    }
853}
854
855/// Graph path consisting of alternating nodes and edges.
856#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
857pub struct Path {
858    /// Ordered sequence of nodes along the path.
859    pub nodes: Vec<Node>,
860    /// Ordered sequence of edges connecting the nodes.
861    #[serde(rename = "relationships")]
862    pub edges: Vec<Edge>,
863}
864
865impl Path {
866    /// Returns the nodes in this path.
867    pub fn nodes(&self) -> &[Node] {
868        &self.nodes
869    }
870
871    /// Returns the edges in this path.
872    pub fn edges(&self) -> &[Edge] {
873        &self.edges
874    }
875
876    /// Returns the number of edges (path length).
877    pub fn len(&self) -> usize {
878        self.edges.len()
879    }
880
881    /// Returns `true` if the path has no edges.
882    pub fn is_empty(&self) -> bool {
883        self.edges.is_empty()
884    }
885
886    /// Returns the starting node, or `None` if the path is empty.
887    pub fn start(&self) -> Option<&Node> {
888        self.nodes.first()
889    }
890
891    /// Returns the ending node, or `None` if the path is empty.
892    pub fn end(&self) -> Option<&Node> {
893        self.nodes.last()
894    }
895}
896
897// ---------------------------------------------------------------------------
898// FromValue trait
899// ---------------------------------------------------------------------------
900
901/// Trait for fallible conversion from [`Value`].
902pub trait FromValue: Sized {
903    /// Converts a `Value` reference to `Self`.
904    ///
905    /// # Errors
906    ///
907    /// Returns `UniError::Type` if the value cannot be converted.
908    fn from_value(value: &Value) -> crate::Result<Self>;
909}
910
911/// Blanket implementation: any `T: TryFrom<&Value, Error = UniError>` is `FromValue`.
912impl<T> FromValue for T
913where
914    T: for<'a> TryFrom<&'a Value, Error = UniError>,
915{
916    fn from_value(value: &Value) -> crate::Result<Self> {
917        Self::try_from(value)
918    }
919}
920
921// ---------------------------------------------------------------------------
922// TryFrom<Value> macro for owned values (delegates to &Value)
923// ---------------------------------------------------------------------------
924
925macro_rules! impl_try_from_value_owned {
926    ($($t:ty),+ $(,)?) => {
927        $(
928            impl TryFrom<Value> for $t {
929                type Error = UniError;
930                fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
931                    Self::try_from(&value)
932                }
933            }
934        )+
935    };
936}
937
938impl_try_from_value_owned!(
939    String,
940    i64,
941    i32,
942    f64,
943    bool,
944    Vid,
945    Eid,
946    Vec<f32>,
947    Path,
948    Node,
949    Edge
950);
951
952// ---------------------------------------------------------------------------
953// TryFrom<&Value> implementations for standard types
954// ---------------------------------------------------------------------------
955
956/// Create a type mismatch error.
957fn type_error(expected: &str, value: &Value) -> UniError {
958    UniError::Type {
959        expected: expected.to_string(),
960        actual: format!("{:?}", value),
961    }
962}
963
964impl TryFrom<&Value> for String {
965    type Error = UniError;
966
967    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
968        match value {
969            Value::String(s) => Ok(s.clone()),
970            Value::Int(i) => Ok(i.to_string()),
971            Value::Float(f) => Ok(f.to_string()),
972            Value::Bool(b) => Ok(b.to_string()),
973            Value::Temporal(t) => Ok(t.to_string()),
974            _ => Err(type_error("String", value)),
975        }
976    }
977}
978
979impl TryFrom<&Value> for i64 {
980    type Error = UniError;
981
982    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
983        match value {
984            Value::Int(i) => Ok(*i),
985            Value::Float(f) => Ok(*f as i64),
986            _ => Err(type_error("Int", value)),
987        }
988    }
989}
990
991impl TryFrom<&Value> for i32 {
992    type Error = UniError;
993
994    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
995        match value {
996            Value::Int(i) => i32::try_from(*i).map_err(|_| UniError::Type {
997                expected: "i32".to_string(),
998                actual: format!("Integer {} out of range", i),
999            }),
1000            Value::Float(f) => {
1001                if *f < i32::MIN as f64 || *f > i32::MAX as f64 {
1002                    return Err(UniError::Type {
1003                        expected: "i32".to_string(),
1004                        actual: format!("Float {} out of range", f),
1005                    });
1006                }
1007                if f.fract() != 0.0 {
1008                    return Err(UniError::Type {
1009                        expected: "i32".to_string(),
1010                        actual: format!("Float {} has fractional part", f),
1011                    });
1012                }
1013                Ok(*f as i32)
1014            }
1015            _ => Err(type_error("Int", value)),
1016        }
1017    }
1018}
1019
1020impl TryFrom<&Value> for f64 {
1021    type Error = UniError;
1022
1023    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1024        match value {
1025            Value::Float(f) => Ok(*f),
1026            Value::Int(i) => Ok(*i as f64),
1027            _ => Err(type_error("Float", value)),
1028        }
1029    }
1030}
1031
1032impl TryFrom<&Value> for bool {
1033    type Error = UniError;
1034
1035    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1036        match value {
1037            Value::Bool(b) => Ok(*b),
1038            _ => Err(type_error("Bool", value)),
1039        }
1040    }
1041}
1042
1043impl TryFrom<&Value> for Vid {
1044    type Error = UniError;
1045
1046    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1047        match value {
1048            Value::Node(n) => Ok(n.vid),
1049            Value::String(s) => {
1050                if let Ok(id) = s.parse::<u64>() {
1051                    return Ok(Vid::new(id));
1052                }
1053                Err(UniError::Type {
1054                    expected: "Vid".into(),
1055                    actual: s.clone(),
1056                })
1057            }
1058            Value::Int(i) => Ok(Vid::new(*i as u64)),
1059            _ => Err(type_error("Vid", value)),
1060        }
1061    }
1062}
1063
1064impl TryFrom<&Value> for Eid {
1065    type Error = UniError;
1066
1067    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1068        match value {
1069            Value::Edge(e) => Ok(e.eid),
1070            Value::String(s) => {
1071                if let Ok(id) = s.parse::<u64>() {
1072                    return Ok(Eid::new(id));
1073                }
1074                Err(UniError::Type {
1075                    expected: "Eid".into(),
1076                    actual: s.clone(),
1077                })
1078            }
1079            Value::Int(i) => Ok(Eid::new(*i as u64)),
1080            _ => Err(type_error("Eid", value)),
1081        }
1082    }
1083}
1084
1085impl TryFrom<&Value> for Vec<f32> {
1086    type Error = UniError;
1087
1088    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1089        match value {
1090            Value::Vector(v) => Ok(v.clone()),
1091            Value::List(l) => {
1092                let mut vec = Vec::with_capacity(l.len());
1093                for item in l {
1094                    match item {
1095                        Value::Float(f) => vec.push(*f as f32),
1096                        Value::Int(i) => vec.push(*i as f32),
1097                        _ => return Err(type_error("Float", item)),
1098                    }
1099                }
1100                Ok(vec)
1101            }
1102            _ => Err(type_error("Vector", value)),
1103        }
1104    }
1105}
1106
1107impl<T> TryFrom<&Value> for Option<T>
1108where
1109    T: for<'a> TryFrom<&'a Value, Error = UniError>,
1110{
1111    type Error = UniError;
1112
1113    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1114        match value {
1115            Value::Null => Ok(None),
1116            _ => T::try_from(value).map(Some),
1117        }
1118    }
1119}
1120
1121impl<T> TryFrom<Value> for Option<T>
1122where
1123    T: TryFrom<Value, Error = UniError>,
1124{
1125    type Error = UniError;
1126    fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1127        match value {
1128            Value::Null => Ok(None),
1129            _ => T::try_from(value).map(Some),
1130        }
1131    }
1132}
1133
1134impl<T> TryFrom<&Value> for Vec<T>
1135where
1136    T: for<'a> TryFrom<&'a Value, Error = UniError>,
1137{
1138    type Error = UniError;
1139
1140    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1141        match value {
1142            Value::List(l) => {
1143                let mut vec = Vec::with_capacity(l.len());
1144                for item in l {
1145                    vec.push(T::try_from(item)?);
1146                }
1147                Ok(vec)
1148            }
1149            _ => Err(type_error("List", value)),
1150        }
1151    }
1152}
1153
1154impl<T> TryFrom<Value> for Vec<T>
1155where
1156    T: TryFrom<Value, Error = UniError>,
1157{
1158    type Error = UniError;
1159    fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
1160        match value {
1161            Value::List(l) => {
1162                let mut vec = Vec::with_capacity(l.len());
1163                for item in l {
1164                    vec.push(T::try_from(item)?);
1165                }
1166                Ok(vec)
1167            }
1168            other => Err(type_error("List", &other)),
1169        }
1170    }
1171}
1172
1173// ---------------------------------------------------------------------------
1174// TryFrom<&Value> for graph entities (deserialization from Map)
1175// ---------------------------------------------------------------------------
1176
1177/// Gets a value from a map trying alternative keys in order.
1178fn get_with_fallback<'a>(map: &'a HashMap<String, Value>, keys: &[&str]) -> Option<&'a Value> {
1179    keys.iter().find_map(|k| map.get(*k))
1180}
1181
1182/// Extracts a properties map from a value, defaulting to empty.
1183fn extract_properties(value: &Value) -> HashMap<String, Value> {
1184    match value {
1185        Value::Map(m) => m.clone(),
1186        _ => HashMap::new(),
1187    }
1188}
1189
1190impl TryFrom<&Value> for Node {
1191    type Error = UniError;
1192
1193    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1194        match value {
1195            Value::Node(n) => Ok(n.clone()),
1196            Value::Map(m) => {
1197                let vid_val = get_with_fallback(m, &["_vid", "_id", "vid"]);
1198                let props_val = m.get("properties");
1199
1200                let (Some(v), Some(p)) = (vid_val, props_val) else {
1201                    return Err(type_error("Node Map", value));
1202                };
1203
1204                // Extract labels from _labels key (List<String>)
1205                let labels = if let Some(Value::List(label_list)) = m.get("_labels") {
1206                    label_list
1207                        .iter()
1208                        .filter_map(|v| {
1209                            if let Value::String(s) = v {
1210                                Some(s.clone())
1211                            } else {
1212                                None
1213                            }
1214                        })
1215                        .collect()
1216                } else {
1217                    Vec::new()
1218                };
1219
1220                Ok(Node {
1221                    vid: Vid::try_from(v)?,
1222                    labels,
1223                    properties: extract_properties(p),
1224                })
1225            }
1226            _ => Err(type_error("Node", value)),
1227        }
1228    }
1229}
1230
1231impl TryFrom<&Value> for Edge {
1232    type Error = UniError;
1233
1234    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1235        match value {
1236            Value::Edge(e) => Ok(e.clone()),
1237            Value::Map(m) => {
1238                let eid_val = get_with_fallback(m, &["_eid", "_id", "eid"]);
1239                let type_val = get_with_fallback(m, &["_type_name", "_type", "edge_type"]);
1240                let src_val = get_with_fallback(m, &["_src", "src"]);
1241                let dst_val = get_with_fallback(m, &["_dst", "dst"]);
1242                let props_val = m.get("properties");
1243
1244                let (Some(id), Some(t), Some(s), Some(d), Some(p)) =
1245                    (eid_val, type_val, src_val, dst_val, props_val)
1246                else {
1247                    return Err(type_error("Edge Map", value));
1248                };
1249
1250                Ok(Edge {
1251                    eid: Eid::try_from(id)?,
1252                    edge_type: String::try_from(t)?,
1253                    src: Vid::try_from(s)?,
1254                    dst: Vid::try_from(d)?,
1255                    properties: extract_properties(p),
1256                })
1257            }
1258            _ => Err(type_error("Edge", value)),
1259        }
1260    }
1261}
1262
1263impl TryFrom<&Value> for Path {
1264    type Error = UniError;
1265
1266    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
1267        match value {
1268            Value::Path(p) => Ok(p.clone()),
1269            Value::Map(m) => {
1270                let (Some(Value::List(nodes_list)), Some(Value::List(rels_list))) =
1271                    (m.get("nodes"), m.get("relationships"))
1272                else {
1273                    return Err(type_error("Path (Map with nodes/relationships)", value));
1274                };
1275
1276                let nodes = nodes_list
1277                    .iter()
1278                    .map(Node::try_from)
1279                    .collect::<std::result::Result<Vec<_>, _>>()?;
1280
1281                let edges = rels_list
1282                    .iter()
1283                    .map(Edge::try_from)
1284                    .collect::<std::result::Result<Vec<_>, _>>()?;
1285
1286                Ok(Path { nodes, edges })
1287            }
1288            _ => Err(type_error("Path", value)),
1289        }
1290    }
1291}
1292
1293// ---------------------------------------------------------------------------
1294// From<T> for Value (primitive constructors)
1295// ---------------------------------------------------------------------------
1296
1297impl From<String> for Value {
1298    fn from(v: String) -> Self {
1299        Value::String(v)
1300    }
1301}
1302
1303impl From<&str> for Value {
1304    fn from(v: &str) -> Self {
1305        Value::String(v.to_string())
1306    }
1307}
1308
1309impl From<i64> for Value {
1310    fn from(v: i64) -> Self {
1311        Value::Int(v)
1312    }
1313}
1314
1315impl From<i32> for Value {
1316    fn from(v: i32) -> Self {
1317        Value::Int(v as i64)
1318    }
1319}
1320
1321impl From<f64> for Value {
1322    fn from(v: f64) -> Self {
1323        Value::Float(v)
1324    }
1325}
1326
1327impl From<bool> for Value {
1328    fn from(v: bool) -> Self {
1329        Value::Bool(v)
1330    }
1331}
1332
1333impl From<Vec<f32>> for Value {
1334    fn from(v: Vec<f32>) -> Self {
1335        Value::Vector(v)
1336    }
1337}
1338
1339// ---------------------------------------------------------------------------
1340// serde_json::Value ↔ Value conversions (JSONB boundary)
1341// ---------------------------------------------------------------------------
1342
1343impl From<serde_json::Value> for Value {
1344    fn from(v: serde_json::Value) -> Self {
1345        match v {
1346            serde_json::Value::Null => Value::Null,
1347            serde_json::Value::Bool(b) => Value::Bool(b),
1348            serde_json::Value::Number(n) => {
1349                if let Some(i) = n.as_i64() {
1350                    Value::Int(i)
1351                } else if let Some(f) = n.as_f64() {
1352                    Value::Float(f)
1353                } else {
1354                    Value::Null
1355                }
1356            }
1357            serde_json::Value::String(s) => Value::String(s),
1358            serde_json::Value::Array(arr) => {
1359                Value::List(arr.into_iter().map(Value::from).collect())
1360            }
1361            serde_json::Value::Object(obj) => {
1362                Value::Map(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
1363            }
1364        }
1365    }
1366}
1367
1368impl From<Value> for serde_json::Value {
1369    fn from(v: Value) -> Self {
1370        match v {
1371            Value::Null => serde_json::Value::Null,
1372            Value::Bool(b) => serde_json::Value::Bool(b),
1373            Value::Int(i) => serde_json::Value::Number(serde_json::Number::from(i)),
1374            Value::Float(f) => serde_json::Number::from_f64(f)
1375                .map(serde_json::Value::Number)
1376                .unwrap_or(serde_json::Value::Null), // NaN/Inf → null
1377            Value::String(s) => serde_json::Value::String(s),
1378            Value::Bytes(b) => {
1379                use base64::Engine;
1380                serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
1381            }
1382            Value::List(l) => {
1383                serde_json::Value::Array(l.into_iter().map(serde_json::Value::from).collect())
1384            }
1385            Value::Map(m) => {
1386                let mut map = serde_json::Map::new();
1387                for (k, v) in m {
1388                    map.insert(k, v.into());
1389                }
1390                serde_json::Value::Object(map)
1391            }
1392            Value::Node(n) => {
1393                let mut map = serde_json::Map::new();
1394                map.insert(
1395                    "_id".to_string(),
1396                    serde_json::Value::String(n.vid.to_string()),
1397                );
1398                map.insert(
1399                    "_labels".to_string(),
1400                    serde_json::Value::Array(
1401                        n.labels
1402                            .into_iter()
1403                            .map(serde_json::Value::String)
1404                            .collect(),
1405                    ),
1406                );
1407                let props: serde_json::Value = Value::Map(n.properties).into();
1408                map.insert("properties".to_string(), props);
1409                serde_json::Value::Object(map)
1410            }
1411            Value::Edge(e) => {
1412                let mut map = serde_json::Map::new();
1413                map.insert(
1414                    "_id".to_string(),
1415                    serde_json::Value::String(e.eid.to_string()),
1416                );
1417                map.insert("_type".to_string(), serde_json::Value::String(e.edge_type));
1418                map.insert(
1419                    "_src".to_string(),
1420                    serde_json::Value::String(e.src.to_string()),
1421                );
1422                map.insert(
1423                    "_dst".to_string(),
1424                    serde_json::Value::String(e.dst.to_string()),
1425                );
1426                let props: serde_json::Value = Value::Map(e.properties).into();
1427                map.insert("properties".to_string(), props);
1428                serde_json::Value::Object(map)
1429            }
1430            Value::Path(p) => {
1431                let mut map = serde_json::Map::new();
1432                map.insert(
1433                    "nodes".to_string(),
1434                    Value::List(p.nodes.into_iter().map(Value::Node).collect()).into(),
1435                );
1436                map.insert(
1437                    "relationships".to_string(),
1438                    Value::List(p.edges.into_iter().map(Value::Edge).collect()).into(),
1439                );
1440                serde_json::Value::Object(map)
1441            }
1442            Value::Vector(v) => serde_json::Value::Array(
1443                v.into_iter()
1444                    .map(|f| {
1445                        serde_json::Number::from_f64(f as f64)
1446                            .map(serde_json::Value::Number)
1447                            .unwrap_or(serde_json::Value::Null)
1448                    })
1449                    .collect(),
1450            ),
1451            Value::Temporal(t) => serde_json::Value::String(t.to_string()),
1452        }
1453    }
1454}
1455
1456// ---------------------------------------------------------------------------
1457// unival! macro
1458// ---------------------------------------------------------------------------
1459
1460/// Constructs a [`Value`] from a literal or expression, similar to `serde_json::json!`.
1461///
1462/// # Examples
1463///
1464/// ```
1465/// use uni_common::unival;
1466/// use uni_common::Value;
1467///
1468/// let null = unival!(null);
1469/// let b = unival!(true);
1470/// let i = unival!(42);
1471/// let f = unival!(3.14);
1472/// let s = unival!("hello");
1473/// let list = unival!([1, 2, "three"]);
1474/// let map = unival!({"key": "val", "num": 42});
1475/// let expr_val = { let x: i64 = 10; unival!(x) };
1476/// ```
1477#[macro_export]
1478macro_rules! unival {
1479    // Null
1480    (null) => {
1481        $crate::Value::Null
1482    };
1483
1484    // Booleans
1485    (true) => {
1486        $crate::Value::Bool(true)
1487    };
1488    (false) => {
1489        $crate::Value::Bool(false)
1490    };
1491
1492    // Array
1493    ([ $($elem:tt),* $(,)? ]) => {
1494        $crate::Value::List(vec![ $( $crate::unival!($elem) ),* ])
1495    };
1496
1497    // Map
1498    ({ $($key:tt : $val:tt),* $(,)? }) => {
1499        $crate::Value::Map({
1500            #[allow(unused_mut)]
1501            let mut map = ::std::collections::HashMap::new();
1502            $( map.insert(($key).to_string(), $crate::unival!($val)); )*
1503            map
1504        })
1505    };
1506
1507    // Fallback: any expression — uses From<T> for Value
1508    ($e:expr) => {
1509        $crate::Value::from($e)
1510    };
1511}
1512
1513// ---------------------------------------------------------------------------
1514// Additional From impls for unival! convenience
1515// ---------------------------------------------------------------------------
1516
1517impl From<usize> for Value {
1518    fn from(v: usize) -> Self {
1519        Value::Int(v as i64)
1520    }
1521}
1522
1523impl From<u64> for Value {
1524    fn from(v: u64) -> Self {
1525        Value::Int(v as i64)
1526    }
1527}
1528
1529impl From<f32> for Value {
1530    fn from(v: f32) -> Self {
1531        Value::Float(v as f64)
1532    }
1533}
1534
1535// ---------------------------------------------------------------------------
1536// Tests
1537// ---------------------------------------------------------------------------
1538
1539#[cfg(test)]
1540mod tests {
1541    use super::*;
1542
1543    #[test]
1544    fn test_accessor_methods() {
1545        assert!(Value::Null.is_null());
1546        assert!(!Value::Int(1).is_null());
1547
1548        assert_eq!(Value::Bool(true).as_bool(), Some(true));
1549        assert_eq!(Value::Int(42).as_bool(), None);
1550
1551        assert_eq!(Value::Int(42).as_i64(), Some(42));
1552        assert_eq!(Value::Float(2.5).as_i64(), None);
1553
1554        // as_f64 coerces Int to Float
1555        assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
1556        assert_eq!(Value::Int(42).as_f64(), Some(42.0));
1557        assert_eq!(Value::String("x".into()).as_f64(), None);
1558
1559        assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1560        assert_eq!(Value::Int(1).as_str(), None);
1561
1562        assert!(Value::Int(1).is_i64());
1563        assert!(!Value::Float(1.0).is_i64());
1564
1565        assert!(Value::Float(1.0).is_f64());
1566        assert!(!Value::Int(1).is_f64());
1567
1568        assert!(Value::Int(1).is_number());
1569        assert!(Value::Float(1.0).is_number());
1570        assert!(!Value::String("x".into()).is_number());
1571    }
1572
1573    #[test]
1574    fn test_serde_json_roundtrip() {
1575        let val = Value::Int(42);
1576        let json: serde_json::Value = val.clone().into();
1577        let back: Value = json.into();
1578        assert_eq!(val, back);
1579
1580        let val = Value::Float(2.5);
1581        let json: serde_json::Value = val.clone().into();
1582        let back: Value = json.into();
1583        assert_eq!(val, back);
1584
1585        let val = Value::String("hello".into());
1586        let json: serde_json::Value = val.clone().into();
1587        let back: Value = json.into();
1588        assert_eq!(val, back);
1589
1590        let val = Value::List(vec![Value::Int(1), Value::Int(2)]);
1591        let json: serde_json::Value = val.clone().into();
1592        let back: Value = json.into();
1593        assert_eq!(val, back);
1594    }
1595
1596    #[test]
1597    fn test_unival_macro() {
1598        assert_eq!(unival!(null), Value::Null);
1599        assert_eq!(unival!(true), Value::Bool(true));
1600        assert_eq!(unival!(false), Value::Bool(false));
1601        assert_eq!(unival!(42_i64), Value::Int(42));
1602        assert_eq!(unival!(2.5_f64), Value::Float(2.5));
1603        assert_eq!(unival!("hello"), Value::String("hello".into()));
1604
1605        // Array
1606        let list = unival!([1_i64, 2_i64]);
1607        assert_eq!(list, Value::List(vec![Value::Int(1), Value::Int(2)]));
1608
1609        // Map
1610        let map = unival!({"key": "val", "num": 42_i64});
1611        if let Value::Map(m) = &map {
1612            assert_eq!(m.get("key"), Some(&Value::String("val".into())));
1613            assert_eq!(m.get("num"), Some(&Value::Int(42)));
1614        } else {
1615            panic!("Expected Map");
1616        }
1617
1618        // Expression fallback
1619        let x: i64 = 99;
1620        assert_eq!(unival!(x), Value::Int(99));
1621    }
1622
1623    #[test]
1624    fn test_int_float_distinction_preserved() {
1625        // This is the key property: Int stays Int, Float stays Float
1626        let int_val = Value::Int(42);
1627        let float_val = Value::Float(42.0);
1628
1629        assert!(int_val.is_i64());
1630        assert!(!int_val.is_f64());
1631
1632        assert!(float_val.is_f64());
1633        assert!(!float_val.is_i64());
1634
1635        // They are NOT equal (different variants)
1636        assert_ne!(int_val, float_val);
1637    }
1638
1639    #[test]
1640    fn test_temporal_display_zero_seconds_omitted() {
1641        // LocalTime: 12:00 (zero seconds omitted)
1642        let lt = TemporalValue::LocalTime {
1643            nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1644        };
1645        assert_eq!(lt.to_string(), "12:00");
1646
1647        // LocalTime: 12:31:14 (non-zero seconds kept)
1648        let lt2 = TemporalValue::LocalTime {
1649            nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1650        };
1651        assert_eq!(lt2.to_string(), "12:31:14");
1652
1653        // LocalTime: 00:00:00.5 (zero seconds but non-zero nanos — keep seconds)
1654        let lt3 = TemporalValue::LocalTime {
1655            nanos_since_midnight: 500_000_000,
1656        };
1657        assert_eq!(lt3.to_string(), "00:00:00.5");
1658
1659        // Time: 12:00Z (zero offset uses Z)
1660        let t = TemporalValue::Time {
1661            nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1662            offset_seconds: 0,
1663        };
1664        assert_eq!(t.to_string(), "12:00Z");
1665
1666        // Time: 12:31:14+01:00 (non-zero offset)
1667        let t2 = TemporalValue::Time {
1668            nanos_since_midnight: (12 * 3600 + 31 * 60 + 14) * 1_000_000_000,
1669            offset_seconds: 3600,
1670        };
1671        assert_eq!(t2.to_string(), "12:31:14+01:00");
1672
1673        // LocalDateTime: 1984-10-11T12:31 (zero seconds omitted)
1674        let epoch_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1675            .unwrap()
1676            .and_hms_opt(12, 31, 0)
1677            .unwrap()
1678            .and_utc()
1679            .timestamp_nanos_opt()
1680            .unwrap();
1681        let ldt = TemporalValue::LocalDateTime {
1682            nanos_since_epoch: epoch_nanos,
1683        };
1684        assert_eq!(ldt.to_string(), "1984-10-11T12:31");
1685
1686        // DateTime: 1984-10-11T12:31+01:00 (zero seconds, with offset)
1687        let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1688            .unwrap()
1689            .and_hms_opt(11, 31, 0)
1690            .unwrap()
1691            .and_utc()
1692            .timestamp_nanos_opt()
1693            .unwrap();
1694        let dt = TemporalValue::DateTime {
1695            nanos_since_epoch: utc_nanos,
1696            offset_seconds: 3600,
1697            timezone_name: None,
1698        };
1699        assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00");
1700
1701        // DateTime: 2015-07-21T21:40:32.142+01:00 (non-zero seconds with fractional)
1702        let utc_nanos2 = chrono::NaiveDate::from_ymd_opt(2015, 7, 21)
1703            .unwrap()
1704            .and_hms_nano_opt(20, 40, 32, 142_000_000)
1705            .unwrap()
1706            .and_utc()
1707            .timestamp_nanos_opt()
1708            .unwrap();
1709        let dt2 = TemporalValue::DateTime {
1710            nanos_since_epoch: utc_nanos2,
1711            offset_seconds: 3600,
1712            timezone_name: None,
1713        };
1714        assert_eq!(dt2.to_string(), "2015-07-21T21:40:32.142+01:00");
1715
1716        // DateTime: 1984-10-11T12:31Z (zero offset uses Z)
1717        let utc_nanos3 = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1718            .unwrap()
1719            .and_hms_opt(12, 31, 0)
1720            .unwrap()
1721            .and_utc()
1722            .timestamp_nanos_opt()
1723            .unwrap();
1724        let dt3 = TemporalValue::DateTime {
1725            nanos_since_epoch: utc_nanos3,
1726            offset_seconds: 0,
1727            timezone_name: None,
1728        };
1729        assert_eq!(dt3.to_string(), "1984-10-11T12:31Z");
1730    }
1731
1732    #[test]
1733    fn test_temporal_display_fractional_trailing_zeros_stripped() {
1734        // Full stripping: .9 not .900
1735        let d = TemporalValue::Duration {
1736            months: 0,
1737            days: 0,
1738            nanos: 900_000_000,
1739        };
1740        assert_eq!(d.to_string(), "PT0.9S");
1741
1742        // Full stripping: .4 not .400
1743        let d2 = TemporalValue::Duration {
1744            months: 0,
1745            days: 0,
1746            nanos: 400_000_000,
1747        };
1748        assert_eq!(d2.to_string(), "PT0.4S");
1749
1750        // Millisecond precision preserved: .142
1751        let d3 = TemporalValue::Duration {
1752            months: 0,
1753            days: 0,
1754            nanos: 142_000_000,
1755        };
1756        assert_eq!(d3.to_string(), "PT0.142S");
1757
1758        // Nanosecond precision: .000000001
1759        let d4 = TemporalValue::Duration {
1760            months: 0,
1761            days: 0,
1762            nanos: 1,
1763        };
1764        assert_eq!(d4.to_string(), "PT0.000000001S");
1765    }
1766
1767    #[test]
1768    fn test_temporal_display_offset_second_precision() {
1769        // Offset with seconds: +02:05:59
1770        let t = TemporalValue::Time {
1771            nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1772            offset_seconds: 2 * 3600 + 5 * 60 + 59,
1773        };
1774        assert_eq!(t.to_string(), "12:00+02:05:59");
1775
1776        // Negative offset with seconds: -02:05:07
1777        let t2 = TemporalValue::Time {
1778            nanos_since_midnight: 12 * 3600 * 1_000_000_000,
1779            offset_seconds: -(2 * 3600 + 5 * 60 + 7),
1780        };
1781        assert_eq!(t2.to_string(), "12:00-02:05:07");
1782    }
1783
1784    #[test]
1785    fn test_temporal_display_datetime_with_timezone_name() {
1786        let utc_nanos = chrono::NaiveDate::from_ymd_opt(1984, 10, 11)
1787            .unwrap()
1788            .and_hms_opt(11, 31, 0)
1789            .unwrap()
1790            .and_utc()
1791            .timestamp_nanos_opt()
1792            .unwrap();
1793        let dt = TemporalValue::DateTime {
1794            nanos_since_epoch: utc_nanos,
1795            offset_seconds: 3600,
1796            timezone_name: Some("Europe/Stockholm".to_string()),
1797        };
1798        assert_eq!(dt.to_string(), "1984-10-11T12:31+01:00[Europe/Stockholm]");
1799    }
1800}