Skip to main content

seam_core/
value.rs

1use std::collections::BTreeMap;
2
3use crate::input::Kind;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Value {
7    Null,
8    Bool(bool),
9    Int(Int),
10    Float(f64),
11    String(String),
12    Array(Vec<Value>),
13    Object(BTreeMap<String, Value>),
14    /// An integer too wide for `Int`, kept as a fact so validation can report
15    /// it at its own path instead of the parse failing the whole document.
16    IntTooWide,
17}
18
19/// An integer that has not been through a float. Keeping the source signedness
20/// is what lets a `u64` above `i64::MAX` survive the boundary intact.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Int {
23    Signed(i64),
24    Unsigned(u64),
25}
26
27impl Int {
28    pub fn as_i128(self) -> i128 {
29        match self {
30            Int::Signed(v) => i128::from(v),
31            Int::Unsigned(v) => i128::from(v),
32        }
33    }
34
35    pub fn is_negative(self) -> bool {
36        matches!(self, Int::Signed(v) if v < 0)
37    }
38}
39
40impl From<i64> for Int {
41    fn from(v: i64) -> Self {
42        Int::Signed(v)
43    }
44}
45
46impl From<u64> for Int {
47    fn from(v: u64) -> Self {
48        match i64::try_from(v) {
49            Ok(v) => Int::Signed(v),
50            Err(_) => Int::Unsigned(v),
51        }
52    }
53}
54
55/// What a field found at its key. Absence and null are separate states here;
56/// collapsing them is the bug this type exists to prevent.
57#[derive(Debug, Clone, Copy, PartialEq)]
58pub enum Slot<T> {
59    Absent,
60    Null,
61    Present(T),
62}
63
64impl<'a> Slot<&'a Value> {
65    pub fn read(object: &'a BTreeMap<String, Value>, key: &str) -> Self {
66        match object.get(key) {
67            None => Slot::Absent,
68            Some(Value::Null) => Slot::Null,
69            Some(v) => Slot::Present(v),
70        }
71    }
72}
73
74impl crate::input::Input for Value {
75    type Child<'a> = &'a Value;
76
77    fn kind(&self) -> Kind {
78        match self {
79            Value::Null => Kind::Null,
80            Value::Bool(_) => Kind::Bool,
81            Value::Int(_) => Kind::Int,
82            Value::Float(_) => Kind::Float,
83            Value::String(_) => Kind::String,
84            Value::Array(_) => Kind::Array,
85            Value::Object(_) => Kind::Object,
86            Value::IntTooWide => Kind::IntegerTooWide,
87        }
88    }
89
90    fn as_bool(&self) -> Option<bool> {
91        match self {
92            Value::Bool(b) => Some(*b),
93            _ => None,
94        }
95    }
96
97    fn as_int(&self) -> Option<Int> {
98        match self {
99            Value::Int(n) => Some(*n),
100            _ => None,
101        }
102    }
103
104    fn as_f64(&self) -> Option<f64> {
105        match self {
106            Value::Float(f) => Some(*f),
107            // An integer is an acceptable float.
108            Value::Int(n) => Some(n.as_i128() as f64),
109            _ => None,
110        }
111    }
112
113    fn as_str(&self) -> Option<std::borrow::Cow<'_, str>> {
114        match self {
115            Value::String(s) => Some(std::borrow::Cow::Borrowed(s)),
116            _ => None,
117        }
118    }
119
120    fn len(&self) -> usize {
121        match self {
122            Value::Array(items) => items.len(),
123            Value::Object(map) => map.len(),
124            _ => 0,
125        }
126    }
127
128    fn item(&self, index: usize) -> Option<&Value> {
129        match self {
130            Value::Array(items) => items.get(index),
131            _ => None,
132        }
133    }
134
135    fn slot(&self, key: &str) -> Slot<&Value> {
136        match self {
137            Value::Object(map) => Slot::read(map, key),
138            _ => Slot::Absent,
139        }
140    }
141
142    fn each_key(&self, f: &mut dyn FnMut(&str)) {
143        if let Value::Object(map) = self {
144            for k in map.keys() {
145                f(k);
146            }
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn u64_above_i64_max_keeps_its_value() {
157        let v = Int::from(u64::MAX);
158        assert_eq!(v, Int::Unsigned(u64::MAX));
159        assert_eq!(v.as_i128(), i128::from(u64::MAX));
160    }
161
162    #[test]
163    fn the_max_safe_integer_boundary_is_exact() {
164        let v = Int::from(9_007_199_254_740_993_u64);
165        assert_eq!(v.as_i128(), 9_007_199_254_740_993_i128);
166    }
167
168    #[test]
169    fn absent_and_null_do_not_collapse() {
170        let mut o = BTreeMap::new();
171        o.insert("null_key".to_string(), Value::Null);
172
173        assert_eq!(Slot::read(&o, "null_key"), Slot::Null);
174        assert_eq!(Slot::read(&o, "missing_key"), Slot::Absent);
175    }
176}