Skip to main content

crous_core/
value.rs

1//! Value types for schema-less Crous data.
2//!
3//! Two representations:
4//! - `Value`: owned, heap-allocated, suitable for building documents.
5//! - `CrousValue<'a>`: zero-copy, borrow-backed, returned by the decoder.
6
7use std::fmt;
8
9/// Owned, schema-less Crous value (analogous to `serde_json::Value`).
10///
11/// This type owns all its data and can be freely moved, cloned, and serialized.
12#[derive(Debug, Clone, PartialEq)]
13pub enum Value {
14    /// Null / absent value.
15    Null,
16    /// Boolean.
17    Bool(bool),
18    /// Unsigned integer (up to u64).
19    UInt(u64),
20    /// Signed integer (i64).
21    Int(i64),
22    /// 64-bit IEEE 754 float.
23    Float(f64),
24    /// UTF-8 string.
25    Str(String),
26    /// Raw binary blob.
27    Bytes(Vec<u8>),
28    /// Ordered array of values.
29    Array(Vec<Value>),
30    /// Ordered map of key-value pairs (keys are strings).
31    /// Uses Vec to preserve insertion order (deterministic encoding).
32    Object(Vec<(String, Value)>),
33}
34
35impl Value {
36    /// Returns the type name as a human-readable string.
37    pub fn type_name(&self) -> &'static str {
38        match self {
39            Value::Null => "null",
40            Value::Bool(_) => "bool",
41            Value::UInt(_) => "uint",
42            Value::Int(_) => "int",
43            Value::Float(_) => "float",
44            Value::Str(_) => "str",
45            Value::Bytes(_) => "bytes",
46            Value::Array(_) => "array",
47            Value::Object(_) => "object",
48        }
49    }
50
51    /// Returns true if this value is null.
52    pub fn is_null(&self) -> bool {
53        matches!(self, Value::Null)
54    }
55
56    /// Try to get as a string reference.
57    pub fn as_str(&self) -> Option<&str> {
58        match self {
59            Value::Str(s) => Some(s),
60            _ => None,
61        }
62    }
63
64    /// Try to get as u64.
65    pub fn as_uint(&self) -> Option<u64> {
66        match self {
67            Value::UInt(n) => Some(*n),
68            _ => None,
69        }
70    }
71
72    /// Try to get as i64.
73    pub fn as_int(&self) -> Option<i64> {
74        match self {
75            Value::Int(n) => Some(*n),
76            _ => None,
77        }
78    }
79
80    /// Try to get as f64.
81    pub fn as_float(&self) -> Option<f64> {
82        match self {
83            Value::Float(n) => Some(*n),
84            _ => None,
85        }
86    }
87
88    /// Try to get as a boolean.
89    pub fn as_bool(&self) -> Option<bool> {
90        match self {
91            Value::Bool(b) => Some(*b),
92            _ => None,
93        }
94    }
95
96    /// Try to get as an array.
97    pub fn as_array(&self) -> Option<&[Value]> {
98        match self {
99            Value::Array(a) => Some(a),
100            _ => None,
101        }
102    }
103
104    /// Try to get as an object (ordered key-value pairs).
105    pub fn as_object(&self) -> Option<&[(String, Value)]> {
106        match self {
107            Value::Object(o) => Some(o),
108            _ => None,
109        }
110    }
111}
112
113impl fmt::Display for Value {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match self {
116            Value::Null => write!(f, "null"),
117            Value::Bool(b) => write!(f, "{b}"),
118            Value::UInt(n) => write!(f, "{n}"),
119            Value::Int(n) => write!(f, "{n}"),
120            Value::Float(n) => write!(f, "{n}"),
121            Value::Str(s) => write!(f, "\"{s}\""),
122            Value::Bytes(b) => write!(
123                f,
124                "b64#{}",
125                base64::engine::general_purpose::STANDARD.encode(b)
126            ),
127            Value::Array(items) => {
128                write!(f, "[")?;
129                for (i, item) in items.iter().enumerate() {
130                    if i > 0 {
131                        write!(f, ", ")?;
132                    }
133                    write!(f, "{item}")?;
134                }
135                write!(f, "]")
136            }
137            Value::Object(entries) => {
138                write!(f, "{{ ")?;
139                for (i, (k, v)) in entries.iter().enumerate() {
140                    if i > 0 {
141                        write!(f, " ")?;
142                    }
143                    write!(f, "{k}: {v};")?;
144                }
145                write!(f, " }}")
146            }
147        }
148    }
149}
150
151use base64::Engine;
152
153/// Zero-copy, borrow-backed Crous value returned by the decoder.
154///
155/// Borrows string and bytes data directly from the input buffer,
156/// avoiding allocation for read-heavy workloads.
157#[derive(Debug, Clone, PartialEq)]
158pub enum CrousValue<'a> {
159    Null,
160    Bool(bool),
161    UInt(u64),
162    Int(i64),
163    Float(f64),
164    /// Borrowed UTF-8 string slice from the input buffer.
165    Str(&'a str),
166    /// Borrowed byte slice from the input buffer.
167    Bytes(&'a [u8]),
168    /// Array of zero-copy values.
169    Array(Vec<CrousValue<'a>>),
170    /// Object with borrowed keys.
171    Object(Vec<(&'a str, CrousValue<'a>)>),
172}
173
174impl<'a> CrousValue<'a> {
175    /// Convert a borrowed CrousValue into an owned Value (copies strings/bytes).
176    pub fn to_owned_value(&self) -> Value {
177        match self {
178            CrousValue::Null => Value::Null,
179            CrousValue::Bool(b) => Value::Bool(*b),
180            CrousValue::UInt(n) => Value::UInt(*n),
181            CrousValue::Int(n) => Value::Int(*n),
182            CrousValue::Float(n) => Value::Float(*n),
183            CrousValue::Str(s) => Value::Str((*s).to_string()),
184            CrousValue::Bytes(b) => Value::Bytes(b.to_vec()),
185            CrousValue::Array(items) => {
186                Value::Array(items.iter().map(|v| v.to_owned_value()).collect())
187            }
188            CrousValue::Object(entries) => Value::Object(
189                entries
190                    .iter()
191                    .map(|(k, v)| ((*k).to_string(), v.to_owned_value()))
192                    .collect(),
193            ),
194        }
195    }
196}
197
198/// Convert from serde_json::Value to crous Value for interop.
199impl From<&serde_json::Value> for Value {
200    fn from(jv: &serde_json::Value) -> Self {
201        match jv {
202            serde_json::Value::Null => Value::Null,
203            serde_json::Value::Bool(b) => Value::Bool(*b),
204            serde_json::Value::Number(n) => {
205                if let Some(u) = n.as_u64() {
206                    Value::UInt(u)
207                } else if let Some(i) = n.as_i64() {
208                    Value::Int(i)
209                } else if let Some(f) = n.as_f64() {
210                    Value::Float(f)
211                } else {
212                    Value::Null
213                }
214            }
215            serde_json::Value::String(s) => Value::Str(s.clone()),
216            serde_json::Value::Array(arr) => Value::Array(arr.iter().map(Value::from).collect()),
217            serde_json::Value::Object(map) => Value::Object(
218                map.iter()
219                    .map(|(k, v)| (k.clone(), Value::from(v)))
220                    .collect(),
221            ),
222        }
223    }
224}
225
226/// Convert from crous Value to serde_json::Value for interop.
227impl From<&Value> for serde_json::Value {
228    fn from(cv: &Value) -> Self {
229        match cv {
230            Value::Null => serde_json::Value::Null,
231            Value::Bool(b) => serde_json::Value::Bool(*b),
232            Value::UInt(n) => serde_json::json!(*n),
233            Value::Int(n) => serde_json::json!(*n),
234            Value::Float(n) => serde_json::json!(*n),
235            Value::Str(s) => serde_json::Value::String(s.clone()),
236            Value::Bytes(b) => {
237                serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
238            }
239            Value::Array(items) => {
240                serde_json::Value::Array(items.iter().map(serde_json::Value::from).collect())
241            }
242            Value::Object(entries) => {
243                let map: serde_json::Map<String, serde_json::Value> = entries
244                    .iter()
245                    .map(|(k, v)| (k.clone(), serde_json::Value::from(v)))
246                    .collect();
247                serde_json::Value::Object(map)
248            }
249        }
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn value_type_names() {
259        assert_eq!(Value::Null.type_name(), "null");
260        assert_eq!(Value::Bool(true).type_name(), "bool");
261        assert_eq!(Value::UInt(42).type_name(), "uint");
262        assert_eq!(Value::Str("hi".into()).type_name(), "str");
263    }
264
265    #[test]
266    fn crous_value_to_owned() {
267        let cv = CrousValue::Object(vec![
268            ("name", CrousValue::Str("Alice")),
269            ("age", CrousValue::UInt(30)),
270        ]);
271        let owned = cv.to_owned_value();
272        assert_eq!(
273            owned,
274            Value::Object(vec![
275                ("name".into(), Value::Str("Alice".into())),
276                ("age".into(), Value::UInt(30)),
277            ])
278        );
279    }
280
281    #[test]
282    fn json_roundtrip() {
283        let cv = Value::Object(vec![
284            ("name".into(), Value::Str("Bob".into())),
285            ("score".into(), Value::Float(99.5)),
286        ]);
287        let jv = serde_json::Value::from(&cv);
288        let back = Value::from(&jv);
289        // Float comes back as Float since serde_json preserves f64
290        match &back {
291            Value::Object(entries) => {
292                assert_eq!(entries[0].0, "name");
293                assert_eq!(entries[1].0, "score");
294            }
295            _ => panic!("expected object"),
296        }
297    }
298}