1use alloc::string::String;
2use alloc::vec::Vec;
3
4use crate::error::AccessError;
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum JsonValue {
8 Null,
9 Bool(bool),
10 Number(f64),
11 String(String),
12 Array(Vec<JsonValue>),
13 Object(Vec<(String, JsonValue)>),
14}
15
16impl JsonValue {
17 pub fn is_null(&self) -> bool {
18 matches!(self, Self::Null)
19 }
20
21 pub fn is_bool(&self) -> bool {
22 matches!(self, Self::Bool(_))
23 }
24
25 pub fn is_number(&self) -> bool {
26 matches!(self, Self::Number(_))
27 }
28
29 pub fn is_string(&self) -> bool {
30 matches!(self, Self::String(_))
31 }
32
33 pub fn is_array(&self) -> bool {
34 matches!(self, Self::Array(_))
35 }
36
37 pub fn is_object(&self) -> bool {
38 matches!(self, Self::Object(_))
39 }
40
41 pub fn as_bool(&self) -> Option<bool> {
42 match self {
43 Self::Bool(value) => Some(*value),
44 _ => None,
45 }
46 }
47
48 pub fn as_f64(&self) -> Option<f64> {
49 match self {
50 Self::Number(value) => Some(*value),
51 _ => None,
52 }
53 }
54
55 pub fn as_i64(&self) -> Option<i64> {
56 match self {
57 Self::Number(value) => {
58 let n = *value;
59 if n >= i64::MIN as f64 && n <= i64::MAX as f64 && (n as i64) as f64 == n {
61 Some(n as i64)
62 } else {
63 None
64 }
65 }
66 _ => None,
67 }
68 }
69
70 pub fn as_u64(&self) -> Option<u64> {
71 match self {
72 Self::Number(value) => {
73 let n = *value;
74 if n >= 0.0 && n <= u64::MAX as f64 && (n as u64) as f64 == n {
75 Some(n as u64)
76 } else {
77 None
78 }
79 }
80 _ => None,
81 }
82 }
83
84 pub fn as_str(&self) -> Option<&str> {
85 match self {
86 Self::String(value) => Some(value.as_str()),
87 _ => None,
88 }
89 }
90
91 pub fn as_array(&self) -> Option<&[JsonValue]> {
92 match self {
93 Self::Array(values) => Some(values.as_slice()),
94 _ => None,
95 }
96 }
97
98 pub fn as_object(&self) -> Option<&[(String, JsonValue)]> {
99 match self {
100 Self::Object(fields) => Some(fields.as_slice()),
101 _ => None,
102 }
103 }
104
105 pub fn get(&self, key: &str) -> Option<&JsonValue> {
106 match self {
107 Self::Object(fields) => fields
108 .iter()
109 .find_map(|(field, value)| (field == key).then_some(value)),
110 _ => None,
111 }
112 }
113
114 pub fn required_str(&self, key: &str) -> Result<&str, AccessError> {
115 self.required_value(key, JsonValue::as_str, "string")
116 }
117
118 pub fn required_bool(&self, key: &str) -> Result<bool, AccessError> {
119 self.required_value(key, JsonValue::as_bool, "bool")
120 }
121
122 pub fn required_f64(&self, key: &str) -> Result<f64, AccessError> {
123 self.required_value(key, JsonValue::as_f64, "number")
124 }
125
126 pub fn required_array(&self, key: &str) -> Result<&[JsonValue], AccessError> {
127 self.required_value(key, JsonValue::as_array, "array")
128 }
129
130 pub fn required_object(&self, key: &str) -> Result<&[(String, JsonValue)], AccessError> {
131 self.required_value(key, JsonValue::as_object, "object")
132 }
133
134 pub fn optional_str(&self, key: &str) -> Result<Option<&str>, AccessError> {
135 self.optional_value(key, JsonValue::as_str, "string")
136 }
137
138 pub fn optional_bool(&self, key: &str) -> Result<Option<bool>, AccessError> {
139 self.optional_value(key, JsonValue::as_bool, "bool")
140 }
141
142 pub fn optional_f64(&self, key: &str) -> Result<Option<f64>, AccessError> {
143 self.optional_value(key, JsonValue::as_f64, "number")
144 }
145
146 pub fn optional_array(&self, key: &str) -> Result<Option<&[JsonValue]>, AccessError> {
147 self.optional_value(key, JsonValue::as_array, "array")
148 }
149
150 fn required_value<'a, T>(
151 &'a self,
152 key: &str,
153 accessor: impl FnOnce(&'a JsonValue) -> Option<T>,
154 expected: &'static str,
155 ) -> Result<T, AccessError> {
156 let value = self.object_field(key)?;
157 accessor(value).ok_or_else(|| AccessError::TypeMismatch {
158 field: key.into(),
159 expected,
160 })
161 }
162
163 fn optional_value<'a, T>(
164 &'a self,
165 key: &str,
166 accessor: impl FnOnce(&'a JsonValue) -> Option<T>,
167 expected: &'static str,
168 ) -> Result<Option<T>, AccessError> {
169 let Some(value) = self.object_field_optional(key)? else {
170 return Ok(None);
171 };
172
173 if value.is_null() {
174 return Ok(None);
175 }
176
177 accessor(value)
178 .map(Some)
179 .ok_or_else(|| AccessError::TypeMismatch {
180 field: key.into(),
181 expected,
182 })
183 }
184
185 fn object_field(&self, key: &str) -> Result<&JsonValue, AccessError> {
186 self.object_field_optional(key)?
187 .ok_or_else(|| AccessError::MissingField(key.into()))
188 }
189
190 fn object_field_optional(&self, key: &str) -> Result<Option<&JsonValue>, AccessError> {
191 match self {
192 Self::Object(_) => Ok(self.get(key)),
193 _ => Err(AccessError::NotAnObject),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use alloc::string::String;
201 use alloc::vec;
202
203 use super::JsonValue;
204 use crate::AccessError;
205
206 fn sample_object() -> JsonValue {
207 JsonValue::Object(vec![
208 ("name".into(), JsonValue::String("neco".into())),
209 ("enabled".into(), JsonValue::Bool(true)),
210 ("score".into(), JsonValue::Number(42.5)),
211 (
212 "items".into(),
213 JsonValue::Array(vec![JsonValue::Bool(false), JsonValue::Null]),
214 ),
215 (
216 "meta".into(),
217 JsonValue::Object(vec![("nested".into(), JsonValue::String("ok".into()))]),
218 ),
219 ("maybe".into(), JsonValue::Null),
220 ("dup".into(), JsonValue::String("first".into())),
221 ("dup".into(), JsonValue::String("second".into())),
222 ])
223 }
224
225 #[test]
226 fn is_methods_match_variants() {
227 assert!(JsonValue::Null.is_null());
228 assert!(JsonValue::Bool(true).is_bool());
229 assert!(JsonValue::Number(1.0).is_number());
230 assert!(JsonValue::String("x".into()).is_string());
231 assert!(JsonValue::Array(vec![]).is_array());
232 assert!(JsonValue::Object(vec![]).is_object());
233 }
234
235 #[test]
236 fn as_methods_return_expected_values() {
237 let value = JsonValue::Bool(true);
238 assert_eq!(value.as_bool(), Some(true));
239 assert_eq!(value.as_f64(), None);
240
241 let value = JsonValue::Number(3.25);
242 assert_eq!(value.as_f64(), Some(3.25));
243 assert_eq!(value.as_str(), None);
244
245 let value = JsonValue::String("cat".into());
246 assert_eq!(value.as_str(), Some("cat"));
247 assert_eq!(value.as_array(), None);
248
249 let array = vec![JsonValue::Null];
250 let value = JsonValue::Array(array.clone());
251 assert_eq!(value.as_array(), Some(array.as_slice()));
252 assert_eq!(value.as_object(), None);
253
254 let object = vec![("k".into(), JsonValue::Bool(false))];
255 let value = JsonValue::Object(object.clone());
256 assert_eq!(value.as_object(), Some(object.as_slice()));
257 assert_eq!(value.as_bool(), None);
258 }
259
260 #[test]
261 fn get_reads_object_field_and_keeps_first_duplicate() {
262 let object = sample_object();
263
264 assert_eq!(object.get("name"), Some(&JsonValue::String("neco".into())));
265 assert_eq!(object.get("dup"), Some(&JsonValue::String("first".into())));
266 assert_eq!(object.get("missing"), None);
267 assert_eq!(JsonValue::Bool(true).get("name"), None);
268 }
269
270 #[test]
271 fn required_accessors_cover_success_and_errors() {
272 let object = sample_object();
273
274 assert_eq!(object.required_str("name"), Ok("neco"));
275 assert_eq!(object.required_bool("enabled"), Ok(true));
276 assert_eq!(object.required_f64("score"), Ok(42.5));
277 assert_eq!(
278 object.required_array("items"),
279 Ok([JsonValue::Bool(false), JsonValue::Null].as_slice())
280 );
281 assert_eq!(
282 object.required_object("meta"),
283 Ok([("nested".into(), JsonValue::String("ok".into()))].as_slice())
284 );
285
286 assert_eq!(
287 object.required_str("missing"),
288 Err(AccessError::MissingField(String::from("missing")))
289 );
290 assert_eq!(
291 object.required_bool("name"),
292 Err(AccessError::TypeMismatch {
293 field: String::from("name"),
294 expected: "bool",
295 })
296 );
297 assert_eq!(
298 JsonValue::Null.required_str("name"),
299 Err(AccessError::NotAnObject)
300 );
301 }
302
303 #[test]
304 fn optional_accessors_cover_success_none_and_errors() {
305 let object = sample_object();
306
307 assert_eq!(object.optional_str("name"), Ok(Some("neco")));
308 assert_eq!(object.optional_bool("enabled"), Ok(Some(true)));
309 assert_eq!(object.optional_f64("score"), Ok(Some(42.5)));
310 assert_eq!(
311 object.optional_array("items"),
312 Ok(Some([JsonValue::Bool(false), JsonValue::Null].as_slice()))
313 );
314
315 assert_eq!(object.optional_str("missing"), Ok(None));
316 assert_eq!(object.optional_str("maybe"), Ok(None));
317 assert_eq!(
318 object.optional_bool("name"),
319 Err(AccessError::TypeMismatch {
320 field: String::from("name"),
321 expected: "bool",
322 })
323 );
324 assert_eq!(
325 JsonValue::Null.optional_str("name"),
326 Err(AccessError::NotAnObject)
327 );
328 }
329}