Skip to main content

geonative_core/
value.rs

1//! Attribute values and their type tags.
2//!
3//! `Value` is the per-cell payload; `ValueType` is the discriminant used in
4//! [`crate::FieldDef`] to declare what type a column holds.
5//!
6//! ## DateTime
7//!
8//! v0.1 stores datetimes as `f64` **days since 1899-12-30 00:00:00**, matching
9//! the raw GDB encoding. This is lossless and zero-dep. Writers convert to
10//! their preferred representation (ISO 8601, Arrow Timestamp, …) at write time.
11//! A strong-typed `DateTime` value is on the roadmap for v0.2.
12
13/// One attribute value. Marked `#[non_exhaustive]` because future versions
14/// may add variants (e.g. `Date` / `Time` / `DateTimeOffset` once we move
15/// off the f64-days representation, or `Json` / `Decimal` for richer schemas)
16/// without that counting as a SemVer-breaking change.
17#[derive(Debug, Clone, PartialEq)]
18#[non_exhaustive]
19pub enum Value {
20    Null,
21    Bool(bool),
22    Int16(i16),
23    Int32(i32),
24    Int64(i64),
25    Float32(f32),
26    Float64(f64),
27    String(String),
28    Binary(Vec<u8>),
29    /// Days since 1899-12-30 00:00:00 (Esri convention). See module docs.
30    DateTime(f64),
31    /// 16-byte UUID, raw bytes (no string formatting).
32    Guid([u8; 16]),
33    /// XML payload, stored verbatim.
34    Xml(String),
35}
36
37/// Type discriminant for [`Value`]. Used by [`crate::FieldDef`] to describe
38/// schema without binding a per-cell value. Tracks [`Value`] one-for-one;
39/// the same `#[non_exhaustive]` rationale applies.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[non_exhaustive]
42pub enum ValueType {
43    Bool,
44    Int16,
45    Int32,
46    Int64,
47    Float32,
48    Float64,
49    String,
50    Binary,
51    DateTime,
52    Guid,
53    Xml,
54}
55
56impl Value {
57    /// Returns `None` for `Value::Null`, otherwise the type of the variant.
58    pub fn ty(&self) -> Option<ValueType> {
59        Some(match self {
60            Value::Null => return None,
61            Value::Bool(_) => ValueType::Bool,
62            Value::Int16(_) => ValueType::Int16,
63            Value::Int32(_) => ValueType::Int32,
64            Value::Int64(_) => ValueType::Int64,
65            Value::Float32(_) => ValueType::Float32,
66            Value::Float64(_) => ValueType::Float64,
67            Value::String(_) => ValueType::String,
68            Value::Binary(_) => ValueType::Binary,
69            Value::DateTime(_) => ValueType::DateTime,
70            Value::Guid(_) => ValueType::Guid,
71            Value::Xml(_) => ValueType::Xml,
72        })
73    }
74
75    pub fn is_null(&self) -> bool {
76        matches!(self, Value::Null)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn ty_returns_none_for_null() {
86        assert_eq!(Value::Null.ty(), None);
87        assert!(Value::Null.is_null());
88    }
89
90    #[test]
91    fn ty_matches_variant() {
92        assert_eq!(Value::Bool(true).ty(), Some(ValueType::Bool));
93        assert_eq!(Value::Int16(1).ty(), Some(ValueType::Int16));
94        assert_eq!(Value::Int32(1).ty(), Some(ValueType::Int32));
95        assert_eq!(Value::Int64(1).ty(), Some(ValueType::Int64));
96        assert_eq!(Value::Float32(1.0).ty(), Some(ValueType::Float32));
97        assert_eq!(Value::Float64(1.0).ty(), Some(ValueType::Float64));
98        assert_eq!(Value::String("x".into()).ty(), Some(ValueType::String));
99        assert_eq!(Value::Binary(vec![]).ty(), Some(ValueType::Binary));
100        assert_eq!(Value::DateTime(0.0).ty(), Some(ValueType::DateTime));
101        assert_eq!(Value::Guid([0; 16]).ty(), Some(ValueType::Guid));
102        assert_eq!(Value::Xml("<x/>".into()).ty(), Some(ValueType::Xml));
103        assert!(!Value::Int32(1).is_null());
104    }
105}