Skip to main content

agent_first_data/document/
value.rs

1//! Custom Value type — zero external format dependencies.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
6
7/// Custom Value IR independent of any format crate.
8/// Supports all formats: JSON, TOML, YAML, dotenv, and INI.
9#[derive(Debug, Clone, PartialEq)]
10pub enum Value {
11    Null,
12    Bool(bool),
13    Integer(i64),
14    Unsigned(u64),
15    Float(f64),
16    /// A numeric literal that would lose digits if forced through
17    /// [`Value::Integer`]/[`Value::Unsigned`]/[`Value::Float`]: an integer
18    /// outside `u64::MAX`, or any literal written with a fractional part or
19    /// exponent (`3.14`, `1e-10`, `-0.0`, …). Holds the exact source text
20    /// verbatim — never parsed and reserialized through `f64` — so a 30-digit
21    /// integer or a high-precision decimal round-trips digit for digit.
22    ///
23    /// Produced by the JSON backend's reader (`format::json::load`), which is
24    /// the format whose number grammar is genuinely arbitrary-precision;
25    /// TOML integers are grammar-limited to `i64` and TOML/YAML floats are
26    /// canonically `f64` at the format level, so those backends keep using
27    /// `Integer`/`Unsigned`/`Float` for values in range. `set --value-type
28    /// number` also produces this variant, preserving the CLI argument's
29    /// literal spelling the same way.
30    Number(String),
31    String(String),
32    Array(Vec<Value>),
33    /// Object map. Keys are stored sorted (BTreeMap), not in insertion order.
34    Object(BTreeMap<String, Value>),
35}
36
37impl Value {
38    /// Stable value-kind label for diagnostics and machine-readable metadata.
39    ///
40    /// Signed and unsigned fixed-width integers both report `integer`; exact
41    /// numeric literals stored in [`Value::Number`] report `number`.
42    #[must_use]
43    pub const fn kind_name(&self) -> &'static str {
44        match self {
45            Self::Null => "null",
46            Self::Bool(_) => "boolean",
47            Self::Integer(_) | Self::Unsigned(_) => "integer",
48            Self::Float(_) => "float",
49            Self::Number(_) => "number",
50            Self::String(_) => "string",
51            Self::Array(_) => "array",
52            Self::Object(_) => "object",
53        }
54    }
55
56    pub fn is_null(&self) -> bool {
57        matches!(self, Value::Null)
58    }
59
60    pub fn is_bool(&self) -> bool {
61        matches!(self, Value::Bool(_))
62    }
63
64    pub fn as_bool(&self) -> Option<bool> {
65        match self {
66            Value::Bool(b) => Some(*b),
67            _ => None,
68        }
69    }
70
71    /// True for `Integer`/`Unsigned`, and for a [`Value::Number`] literal
72    /// with no fractional part or exponent — a big integer that only missed
73    /// `Integer`/`Unsigned` because it overflows `i64`/`u64`, not because it
74    /// is actually fractional.
75    pub fn is_integer(&self) -> bool {
76        match self {
77            Value::Integer(_) | Value::Unsigned(_) => true,
78            Value::Number(text) => is_integer_literal(text),
79            _ => false,
80        }
81    }
82
83    /// Exact `i64` value, when this holds one. `None` for a [`Value::Number`]
84    /// literal even when it is integral — by construction that variant only
85    /// exists because the literal does *not* fit `i64`/`u64`; read the exact
86    /// digits via [`Value::as_number_literal`] instead.
87    pub fn as_integer(&self) -> Option<i64> {
88        match self {
89            Value::Integer(i) => Some(*i),
90            _ => None,
91        }
92    }
93
94    pub fn as_unsigned(&self) -> Option<u64> {
95        match self {
96            Value::Unsigned(value) => Some(*value),
97            _ => None,
98        }
99    }
100
101    /// True for `Float`, and for a [`Value::Number`] literal with a
102    /// fractional part or exponent.
103    pub fn is_float(&self) -> bool {
104        match self {
105            Value::Float(_) => true,
106            Value::Number(text) => !is_integer_literal(text),
107            _ => false,
108        }
109    }
110
111    /// Best-effort numeric magnitude as `f64` — lossy for a
112    /// [`Value::Number`] literal outside `f64`'s exact range, but never
113    /// fails for a well-formed literal. Callers that need the exact digits
114    /// (not just the magnitude) must read [`Value::as_number_literal`]
115    /// instead; this accessor exists for magnitude-only consumers (numeric
116    /// comparisons, lint checks) that cannot use arbitrary precision anyway.
117    pub fn as_float(&self) -> Option<f64> {
118        match self {
119            Value::Float(f) => Some(*f),
120            Value::Number(text) => text.parse::<f64>().ok(),
121            _ => None,
122        }
123    }
124
125    /// True for a [`Value::Number`] literal (a number outside
126    /// `Integer`/`Unsigned`/`Float`'s exact range, preserved verbatim).
127    pub fn is_number_literal(&self) -> bool {
128        matches!(self, Value::Number(_))
129    }
130
131    /// The exact source text of a [`Value::Number`] literal, digit for
132    /// digit. `None` for every other variant, including `Integer`/
133    /// `Unsigned`/`Float` — those already round-trip exactly through their
134    /// own `Display`, so this accessor is specifically for the literal-only
135    /// case where that would corrupt the value.
136    pub fn as_number_literal(&self) -> Option<&str> {
137        match self {
138            Value::Number(text) => Some(text),
139            _ => None,
140        }
141    }
142
143    pub fn is_string(&self) -> bool {
144        matches!(self, Value::String(_))
145    }
146
147    pub fn as_str(&self) -> Option<&str> {
148        match self {
149            Value::String(s) => Some(s),
150            _ => None,
151        }
152    }
153
154    pub fn is_array(&self) -> bool {
155        matches!(self, Value::Array(_))
156    }
157
158    pub fn as_array(&self) -> Option<&Vec<Value>> {
159        match self {
160            Value::Array(a) => Some(a),
161            _ => None,
162        }
163    }
164
165    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
166        match self {
167            Value::Array(a) => Some(a),
168            _ => None,
169        }
170    }
171
172    pub fn is_object(&self) -> bool {
173        matches!(self, Value::Object(_))
174    }
175
176    pub fn as_object(&self) -> Option<&BTreeMap<String, Value>> {
177        match self {
178            Value::Object(o) => Some(o),
179            _ => None,
180        }
181    }
182
183    pub fn as_object_mut(&mut self) -> Option<&mut BTreeMap<String, Value>> {
184        match self {
185            Value::Object(o) => Some(o),
186            _ => None,
187        }
188    }
189
190    pub fn get(&self, key: &str) -> Option<&Value> {
191        self.as_object().and_then(|o| o.get(key))
192    }
193
194    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
195        self.as_object_mut().and_then(|o| o.get_mut(key))
196    }
197}
198
199/// True when a numeric literal's text has no fractional part or exponent —
200/// a plain (optionally negative) run of decimal digits.
201fn is_integer_literal(text: &str) -> bool {
202    !text.contains(['.', 'e', 'E'])
203}
204
205impl std::fmt::Display for Value {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        match self {
208            Value::Null => write!(f, "null"),
209            Value::Bool(b) => write!(f, "{}", b),
210            Value::Integer(i) => write!(f, "{}", i),
211            Value::Unsigned(i) => write!(f, "{}", i),
212            Value::Float(fl) => write!(f, "{}", fl),
213            Value::Number(text) => write!(f, "{text}"),
214            Value::String(s) => write!(f, "\"{}\"", s.escape_default()),
215            Value::Array(_) => write!(f, "[...]"),
216            Value::Object(_) => write!(f, "{{...}}"),
217        }
218    }
219}
220
221impl Serialize for Value {
222    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
223        match self {
224            Value::Null => serializer.serialize_none(),
225            Value::Bool(value) => serializer.serialize_bool(*value),
226            Value::Integer(value) => serializer.serialize_i64(*value),
227            Value::Unsigned(value) => serializer.serialize_u64(*value),
228            Value::Float(value) => serializer.serialize_f64(*value),
229            // Generic serde data model has no arbitrary-precision number
230            // primitive, so this is a best-effort numeric fallback (used only
231            // by callers serializing a `Value` through some *other* serde
232            // format directly — the crate's own JSON path goes through
233            // `From<Value> for serde_json::Value` below, which preserves the
234            // literal exactly via serde_json's `arbitrary_precision`).
235            Value::Number(text) => {
236                if let Ok(i) = text.parse::<i64>() {
237                    serializer.serialize_i64(i)
238                } else if let Ok(u) = text.parse::<u64>() {
239                    serializer.serialize_u64(u)
240                } else {
241                    serializer.serialize_f64(text.parse::<f64>().unwrap_or(0.0))
242                }
243            }
244            Value::String(value) => serializer.serialize_str(value),
245            Value::Array(values) => values.serialize(serializer),
246            Value::Object(values) => values.serialize(serializer),
247        }
248    }
249}
250
251impl<'de> Deserialize<'de> for Value {
252    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
253        struct ValueVisitor;
254
255        impl<'de> de::Visitor<'de> for ValueVisitor {
256            type Value = Value;
257
258            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259                formatter.write_str("a document value")
260            }
261
262            fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
263                Ok(Value::Null)
264            }
265
266            fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
267                Ok(Value::Null)
268            }
269
270            fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
271                Ok(Value::Bool(value))
272            }
273
274            fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
275                Ok(Value::Integer(value))
276            }
277
278            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
279                Ok(Value::Unsigned(value))
280            }
281
282            fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
283                Ok(Value::Float(value))
284            }
285
286            fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
287                Ok(Value::String(value.to_string()))
288            }
289
290            fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
291                Ok(Value::String(value))
292            }
293
294            fn visit_seq<A: de::SeqAccess<'de>>(
295                self,
296                mut access: A,
297            ) -> Result<Self::Value, A::Error> {
298                let mut values = Vec::new();
299                while let Some(value) = access.next_element()? {
300                    values.push(value);
301                }
302                Ok(Value::Array(values))
303            }
304
305            fn visit_map<A: de::MapAccess<'de>>(
306                self,
307                mut access: A,
308            ) -> Result<Self::Value, A::Error> {
309                let mut values = BTreeMap::new();
310                while let Some((key, value)) = access.next_entry()? {
311                    values.insert(key, value);
312                }
313                Ok(Value::Object(values))
314            }
315        }
316
317        deserializer.deserialize_any(ValueVisitor)
318    }
319}
320
321mod json_convert {
322    use super::*;
323    use serde_json::json;
324
325    impl From<serde_json::Value> for Value {
326        fn from(v: serde_json::Value) -> Self {
327            match v {
328                serde_json::Value::Null => Value::Null,
329                serde_json::Value::Bool(b) => Value::Bool(b),
330                serde_json::Value::Number(n) => {
331                    // With the crate's `arbitrary_precision` feature enabled,
332                    // `n.as_str()` is the exact source literal — `as_i64`/
333                    // `as_u64` only succeed for a literal with no fractional
334                    // part or exponent that also fits the target width, so a
335                    // plain in-range integer still takes the exact
336                    // `Integer`/`Unsigned` path (unchanged from before);
337                    // everything else (every float literal, and integers
338                    // beyond `u64::MAX`) is preserved digit for digit as
339                    // `Value::Number` instead of being forced through `f64`.
340                    if let Some(i) = n.as_i64() {
341                        Value::Integer(i)
342                    } else if let Some(u) = n.as_u64() {
343                        Value::Unsigned(u)
344                    } else {
345                        Value::Number(n.as_str().to_string())
346                    }
347                }
348                serde_json::Value::String(s) => Value::String(s),
349                serde_json::Value::Array(a) => {
350                    Value::Array(a.into_iter().map(Value::from).collect())
351                }
352                serde_json::Value::Object(o) => {
353                    let map = o.into_iter().map(|(k, v)| (k, Value::from(v))).collect();
354                    Value::Object(map)
355                }
356            }
357        }
358    }
359
360    impl From<Value> for serde_json::Value {
361        fn from(v: Value) -> Self {
362            match v {
363                Value::Null => serde_json::Value::Null,
364                Value::Bool(b) => json!(b),
365                Value::Integer(i) => json!(i),
366                Value::Unsigned(u) => json!(u),
367                Value::Float(f) => {
368                    json!(f)
369                }
370                // Re-parse the exact literal text as a JSON number so
371                // `serde_json`'s `arbitrary_precision` `Number` retains it
372                // verbatim (`Number::as_str()` round-trips exactly — see the
373                // stdlib doctest on that method). Our own writers only ever
374                // construct `Value::Number` from text that already passed
375                // JSON-number syntax validation (the JSON reader below, or
376                // `set --value-type number`'s literal check), so the
377                // fallback is unreachable in practice; it stays total rather
378                // than panicking on a hypothetically malformed literal.
379                Value::Number(text) => match serde_json::from_str::<serde_json::Value>(&text) {
380                    Ok(number @ serde_json::Value::Number(_)) => number,
381                    _ => json!(text.parse::<f64>().unwrap_or(0.0)),
382                },
383                Value::String(s) => json!(s),
384                Value::Array(a) => {
385                    let arr: Vec<serde_json::Value> = a.into_iter().map(|v| v.into()).collect();
386                    serde_json::Value::Array(arr)
387                }
388                Value::Object(o) => {
389                    let mut map = serde_json::Map::new();
390                    for (k, v) in o {
391                        map.insert(k, serde_json::Value::from(v));
392                    }
393                    serde_json::Value::Object(map)
394                }
395            }
396        }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::Value;
403    use std::collections::BTreeMap;
404
405    #[test]
406    fn value_kind_names_are_stable() {
407        let cases = [
408            (Value::Null, "null"),
409            (Value::Bool(true), "boolean"),
410            (Value::Integer(-1), "integer"),
411            (Value::Unsigned(1), "integer"),
412            (Value::Float(1.5), "float"),
413            (Value::Number("1e1000".to_string()), "number"),
414            (Value::String("value".to_string()), "string"),
415            (Value::Array(Vec::new()), "array"),
416            (Value::Object(BTreeMap::new()), "object"),
417        ];
418
419        for (value, expected) in cases {
420            assert_eq!(value.kind_name(), expected);
421        }
422    }
423}