Skip to main content

meshdb_core/
property.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// A Cypher duration — months, days, seconds, nanoseconds. Matches
5/// the Bolt 4.4 Duration struct (tag `0x45`) field-for-field so no
6/// conversion is needed at the wire boundary. Stored as four
7/// separate components because the calendar units (`months`, `days`)
8/// can't be reduced to seconds without knowing a reference date,
9/// while the exact units (`seconds`, `nanos`) can.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct Duration {
12    pub months: i64,
13    pub days: i64,
14    pub seconds: i64,
15    pub nanos: i32,
16}
17
18/// A Cypher spatial point. Stored as raw coordinates plus an EPSG
19/// SRID tag — the SRID determines the coordinate reference system
20/// (CRS) and whether `z` is present:
21///
22/// | SRID | CRS name        | Dims | Axes                        |
23/// |------|-----------------|------|-----------------------------|
24/// | 7203 | `cartesian`     | 2    | x, y                        |
25/// | 9157 | `cartesian-3d`  | 3    | x, y, z                     |
26/// | 4326 | `wgs-84`        | 2    | longitude (x), latitude (y) |
27/// | 4979 | `wgs-84-3d`     | 3    | longitude, latitude, height |
28///
29/// The 2D/3D distinction is carried by `z.is_some()` — the SRID is
30/// required to agree with that (a 2D SRID with `Some(z)`, or a 3D
31/// SRID with `None`, is an invariant violation caller-side).
32///
33/// Matches the Bolt 4.4 Point2D (tag `0x58`) / Point3D (tag `0x59`)
34/// struct shape so the wire conversion is a straight field copy.
35#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
36pub struct Point {
37    pub srid: i32,
38    pub x: f64,
39    pub y: f64,
40    pub z: Option<f64>,
41}
42
43/// EPSG SRID for the Cartesian 2D coordinate system.
44pub const SRID_CARTESIAN_2D: i32 = 7203;
45/// EPSG SRID for the Cartesian 3D coordinate system.
46pub const SRID_CARTESIAN_3D: i32 = 9157;
47/// EPSG SRID for WGS-84 (geographic 2D).
48pub const SRID_WGS84_2D: i32 = 4326;
49/// EPSG SRID for WGS-84 3D (geographic + height).
50pub const SRID_WGS84_3D: i32 = 4979;
51
52impl Point {
53    /// The CRS name for this point's SRID, matching Neo4j's output of
54    /// `point.crs` — e.g. `"cartesian"`, `"wgs-84-3d"`. Returns
55    /// `"unknown"` for any SRID outside the four recognised values.
56    pub fn crs_name(&self) -> &'static str {
57        match self.srid {
58            SRID_CARTESIAN_2D => "cartesian",
59            SRID_CARTESIAN_3D => "cartesian-3d",
60            SRID_WGS84_2D => "wgs-84",
61            SRID_WGS84_3D => "wgs-84-3d",
62            _ => "unknown",
63        }
64    }
65
66    /// True when the SRID is a WGS-84 (geographic) CRS.
67    pub fn is_geographic(&self) -> bool {
68        matches!(self.srid, SRID_WGS84_2D | SRID_WGS84_3D)
69    }
70
71    /// True when the point has a z coordinate.
72    pub fn is_3d(&self) -> bool {
73        self.z.is_some()
74    }
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[serde(tag = "type", content = "value")]
79pub enum Property {
80    Null,
81    String(String),
82    Int64(i64),
83    Float64(f64),
84    Bool(bool),
85    List(Vec<Property>),
86    Map(HashMap<String, Property>),
87    /// UTC epoch **nanoseconds** with an optional timezone offset
88    /// and optional IANA region name. The nanos value is always UTC;
89    /// `tz_offset_secs` is the offset at this instant (used for the
90    /// `+HH:MM` / `Z` suffix), and `tz_name` — when set — is the
91    /// zone identifier (e.g. `"Europe/Stockholm"`) that produced
92    /// that offset, rendered as a `[Region/City]` suffix.
93    DateTime {
94        nanos: i128,
95        tz_offset_secs: Option<i32>,
96        tz_name: Option<String>,
97    },
98    /// Local (naive) datetime as epoch nanoseconds.
99    /// Formatters omit any timezone suffix.
100    LocalDateTime(i128),
101    /// Days since the UNIX epoch (1970-01-01, UTC). `i64` gives
102    /// roughly ±25 billion years — wide enough to round-trip the
103    /// ±999999999 year range the TCK exercises at its extremes.
104    /// Maps to Bolt `Date` (struct tag `0x44`), which is also i64.
105    Date(i64),
106    /// A Cypher duration value — see [`Duration`].
107    Duration(Duration),
108    /// Time of day as nanoseconds since midnight, with an optional
109    /// timezone offset in seconds. Distinct from `DateTime` so the
110    /// formatter can produce `'12:31:14.645876123'` instead of a
111    /// full date-time string.
112    Time {
113        nanos: i64,
114        tz_offset_secs: Option<i32>,
115    },
116    /// A spatial point — see [`Point`]. Encoded on the Bolt wire as
117    /// either a `Point2D` (tag `0x58`) or `Point3D` (tag `0x59`)
118    /// struct depending on whether `z` is set.
119    Point(Point),
120}
121
122impl Property {
123    pub fn type_name(&self) -> &'static str {
124        match self {
125            Property::Null => "Null",
126            Property::String(_) => "String",
127            Property::Int64(_) => "Int64",
128            Property::Float64(_) => "Float64",
129            Property::Bool(_) => "Bool",
130            Property::List(_) => "List",
131            Property::Map(_) => "Map",
132            Property::DateTime { .. } => "DateTime",
133            Property::LocalDateTime(_) => "LocalDateTime",
134            Property::Date(_) => "Date",
135            Property::Duration(_) => "Duration",
136            Property::Time { .. } => "Time",
137            Property::Point(_) => "Point",
138        }
139    }
140}
141
142impl From<String> for Property {
143    fn from(v: String) -> Self {
144        Property::String(v)
145    }
146}
147
148impl From<&str> for Property {
149    fn from(v: &str) -> Self {
150        Property::String(v.to_string())
151    }
152}
153
154impl From<i64> for Property {
155    fn from(v: i64) -> Self {
156        Property::Int64(v)
157    }
158}
159
160impl From<i32> for Property {
161    fn from(v: i32) -> Self {
162        Property::Int64(v as i64)
163    }
164}
165
166impl From<f64> for Property {
167    fn from(v: f64) -> Self {
168        Property::Float64(v)
169    }
170}
171
172impl From<bool> for Property {
173    fn from(v: bool) -> Self {
174        Property::Bool(v)
175    }
176}
177
178impl<T: Into<Property>> From<Vec<T>> for Property {
179    fn from(v: Vec<T>) -> Self {
180        Property::List(v.into_iter().map(Into::into).collect())
181    }
182}