dpp/util/json_value/
mod.rs

1use std::convert::TryInto;
2
3use anyhow::{anyhow, bail};
4
5use serde::de::DeserializeOwned;
6use serde_json::{Number, Value as JsonValue};
7
8use crate::{
9    errors::ProtocolError,
10    identifier::{self},
11};
12
13use super::json_path::{JsonPath, JsonPathLiteral, JsonPathStep};
14
15mod insert_with_path;
16use insert_with_path::*;
17
18mod remove_path;
19use remove_path::*;
20
21const PROPERTY_CONTENT_MEDIA_TYPE: &str = "contentMediaType";
22const PROPERTY_PROTOCOL_VERSION: &str = "protocolVersion";
23
24/// JsonValueExt contains a set of helper methods that simplify work with JsonValue
25pub trait JsonValueExt {
26    /// assumes the Json Value is a map and tries to remove the given property
27    fn remove(&mut self, property_name: &str) -> Result<JsonValue, anyhow::Error>;
28    /// assumes the Json Value is a map and tries to remove the given property and deserialize into the provided type
29    fn remove_into<K: DeserializeOwned>(&mut self, property_name: &str)
30        -> Result<K, anyhow::Error>;
31    /// assumes the Json Value is a map and tries to insert the given value under given property
32    fn insert(&mut self, property_name: String, value: JsonValue) -> Result<(), anyhow::Error>;
33    /// assumes the Json Value is an array and tries to add value to the array
34    fn push(&mut self, value: JsonValue) -> Result<(), anyhow::Error>;
35    fn get_string(&self, property_name: &str) -> Result<&str, anyhow::Error>;
36    fn get_i64(&self, property_name: &str) -> Result<i64, anyhow::Error>;
37    fn get_f64(&self, property_name: &str) -> Result<f64, anyhow::Error>;
38    fn get_u8(&self, property_name: &str) -> Result<u8, anyhow::Error>;
39    fn get_u32(&self, property_name: &str) -> Result<u32, anyhow::Error>;
40    fn get_u64(&self, property_name: &str) -> Result<u64, anyhow::Error>;
41    fn get_bytes(&self, property_name: &str) -> Result<Vec<u8>, anyhow::Error>;
42    /// returns the the mutable JsonValue from provided path. The path is dot-separated string. i.e `properties.id`
43    fn get_value_mut(&mut self, string_path: &str) -> Result<&mut JsonValue, anyhow::Error>;
44    /// returns the the JsonValue from provided path. The path is dot-separated string. i.e `properties[0].id`
45    fn get_value(&self, string_path: &str) -> Result<&JsonValue, anyhow::Error>;
46    /// return  the JsonValue from from provided path. The path is a slice of [`JsonPathStep`]
47    fn get_value_by_path(&self, path: &[JsonPathStep]) -> Result<&JsonValue, anyhow::Error>;
48    /// return  the mutable JsonValue from from provided path. The path is a slice of [`JsonPathStep`]
49    fn get_value_by_path_mut(
50        &mut self,
51        path: &[JsonPathStep],
52    ) -> Result<&mut JsonValue, anyhow::Error>;
53
54    /// assumes that the JsonValue is a Map and tries to remove the u32
55    fn remove_u32(&mut self, property_name: &str) -> Result<u32, anyhow::Error>;
56
57    fn add_protocol_version(
58        &mut self,
59        property_name: &str,
60        protocol_version: u32,
61    ) -> Result<(), ProtocolError>;
62
63    /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id`. If parents don't
64    /// exists they will be created
65    fn insert_with_path(&mut self, path: &str, value: JsonValue) -> Result<(), anyhow::Error>;
66
67    /// Removes data from given path and tries deserialize it into provided type
68    fn remove_value_at_path_into<K: DeserializeOwned>(
69        &mut self,
70        property_name: &str,
71    ) -> Result<K, anyhow::Error>;
72    fn get_bool(&self, property_name: &str) -> Result<bool, anyhow::Error>;
73}
74
75impl JsonValueExt for JsonValue {
76    fn push(&mut self, value: JsonValue) -> Result<(), anyhow::Error> {
77        match self.as_array_mut() {
78            Some(map) => {
79                map.push(value);
80                Ok(())
81            }
82            None => bail!("data isn't an array: '{:?}'", self),
83        }
84    }
85
86    fn insert(&mut self, property_name: String, value: JsonValue) -> Result<(), anyhow::Error> {
87        match self.as_object_mut() {
88            Some(map) => {
89                map.insert(property_name, value);
90                Ok(())
91            }
92            None => bail!(
93                "getting property '{}' failed: the data isn't a map: '{:?}'",
94                self,
95                property_name
96            ),
97        }
98    }
99
100    fn remove_into<K: DeserializeOwned>(
101        &mut self,
102        property_name: &str,
103    ) -> Result<K, anyhow::Error> {
104        match self.as_object_mut() {
105            Some(map) => {
106                if let Some(data) = map.remove(property_name) {
107                    serde_json::from_value(data)
108                        .map_err(|err| anyhow!("unable convert data: {}`", err))
109                } else {
110                    bail!(
111                        "the property '{}' doesn't exist in {:?}",
112                        property_name,
113                        self
114                    )
115                }
116            }
117            None => bail!("the property '{}' isn't a map: '{:?}'", property_name, self),
118        }
119    }
120
121    fn remove(&mut self, property_name: &str) -> Result<JsonValue, anyhow::Error> {
122        match self.as_object_mut() {
123            Some(map) => map.remove(property_name).ok_or_else(|| {
124                anyhow!(
125                    "the property '{}' doesn't exists in '{:?}'",
126                    property_name,
127                    self
128                )
129            }),
130            None => bail!("the property '{}' isn't a map: '{:?}'", property_name, self),
131        }
132    }
133
134    fn get_string(&self, property_name: &str) -> Result<&str, anyhow::Error> {
135        let property_value = self.get(property_name).ok_or_else(|| {
136            anyhow!(
137                "the property '{}' doesn't exist in {:?}",
138                property_name,
139                self
140            )
141        })?;
142
143        if let JsonValue::String(s) = property_value {
144            return Ok(s);
145        }
146        bail!(
147            "getting property '{}' failed: {:?} isn't a String",
148            property_name,
149            property_value
150        );
151    }
152
153    fn get_u8(&self, property_name: &str) -> Result<u8, anyhow::Error> {
154        let property_value = self.get(property_name).ok_or_else(|| {
155            anyhow!(
156                "the property '{}' doesn't exist in '{:?}'",
157                property_name,
158                self
159            )
160        })?;
161
162        if let JsonValue::Number(s) = property_value {
163            return s
164                .as_u64()
165                .ok_or_else(|| anyhow!("unable convert {} to u64", s))?
166                .try_into()
167                .map_err(|e| anyhow!("unable convert {} to u8: {}", s, e));
168        }
169        bail!(
170            "getting property '{}' failed: {:?} isn't a number",
171            property_name,
172            property_value
173        );
174    }
175
176    fn get_u32(&self, property_name: &str) -> Result<u32, anyhow::Error> {
177        let property_value = self.get(property_name).ok_or_else(|| {
178            anyhow!(
179                "the property '{}' doesn't exist in '{:?}'",
180                property_name,
181                self
182            )
183        })?;
184
185        if let JsonValue::Number(s) = property_value {
186            return s
187                .as_u64()
188                .ok_or_else(|| anyhow!("unable convert {} to u64", s))?
189                .try_into()
190                .map_err(|e| anyhow!("unable convert {} to u32: {}", s, e));
191        }
192        bail!(
193            "getting property '{}' failed: {:?} isn't a number",
194            property_name,
195            property_value
196        );
197    }
198
199    fn get_u64(&self, property_name: &str) -> Result<u64, anyhow::Error> {
200        let property_value = self.get(property_name).ok_or_else(|| {
201            anyhow!(
202                "the property '{}' doesn't exist in '{:?}'",
203                property_name,
204                self
205            )
206        })?;
207
208        if let JsonValue::Number(s) = property_value {
209            return s
210                .as_u64()
211                .ok_or_else(|| anyhow!("unable convert {} to u64", s));
212        }
213        bail!(
214            "getting property '{}' failed: {:?} isn't a number",
215            property_name,
216            property_value
217        );
218    }
219
220    fn get_i64(&self, property_name: &str) -> Result<i64, anyhow::Error> {
221        let property_value = self.get(property_name).ok_or_else(|| {
222            anyhow!(
223                "the property '{}' doesn't exist in '{:?}'",
224                property_name,
225                self
226            )
227        })?;
228
229        if let JsonValue::Number(s) = property_value {
230            return s
231                .as_i64()
232                .ok_or_else(|| anyhow!("unable convert {} to i64", s));
233        }
234        bail!(
235            "getting property '{}' failed: {:?} isn't a number",
236            property_name,
237            property_value
238        );
239    }
240
241    fn get_f64(&self, property_name: &str) -> Result<f64, anyhow::Error> {
242        let property_value = self.get(property_name).ok_or_else(|| {
243            anyhow!(
244                "the property '{}' doesn't exist in '{:?}'",
245                property_name,
246                self
247            )
248        })?;
249
250        if let JsonValue::Number(s) = property_value {
251            return s
252                .as_f64()
253                .ok_or_else(|| anyhow!("unable convert {} to f64", s));
254        }
255        bail!(
256            "getting property '{}' failed: {:?} isn't a number",
257            property_name,
258            property_value
259        );
260    }
261
262    // TODO this method has an additional allocation which should be avoided
263    fn get_bytes(&self, property_name: &str) -> Result<Vec<u8>, anyhow::Error> {
264        let property_value = self.get(property_name).ok_or_else(|| {
265            anyhow!(
266                "the property '{}' doesn't exist in '{:?}'",
267                property_name,
268                self
269            )
270        })?;
271
272        serde_json::from_value(property_value.clone())
273            .map_err(|e| anyhow!("getting property '{}' failed: {}", property_name, e))
274    }
275
276    /// returns the value from the JsonValue based on the path: i.e "root.data[0].id"
277    fn get_value_mut(&mut self, string_path: &str) -> Result<&mut JsonValue, anyhow::Error> {
278        let path_literal: JsonPathLiteral = string_path.into();
279        let path: JsonPath = path_literal.try_into().unwrap();
280        get_value_from_json_path_mut(&path, self)
281            .ok_or_else(|| anyhow!("the property '{}' not found", string_path))
282    }
283
284    /// returns the value from the JsonValue based on the path: i.e "root.data[0].id"
285    fn get_value(&self, string_path: &str) -> Result<&JsonValue, anyhow::Error> {
286        let path_literal: JsonPathLiteral = string_path.into();
287        let path: JsonPath = path_literal.try_into().unwrap();
288        get_value_from_json_path(&path, self)
289            .ok_or_else(|| anyhow!("the property '{}' not found", string_path))
290    }
291
292    /// returns the value from the JsonValue based on the path: i.e "root.data[0].id"
293    fn get_value_by_path(&self, path: &[JsonPathStep]) -> Result<&JsonValue, anyhow::Error> {
294        get_value_from_json_path(path, self)
295            .ok_or_else(|| anyhow!("the property '{:?}' not found", path))
296    }
297
298    fn get_value_by_path_mut(
299        &mut self,
300        path: &[JsonPathStep],
301    ) -> Result<&mut JsonValue, anyhow::Error> {
302        get_value_from_json_path_mut(path, self)
303            .ok_or_else(|| anyhow!("the property '{:?}' not found", path))
304    }
305
306    fn add_protocol_version<'a>(
307        &mut self,
308        property_name: &str,
309        protocol_version: u32,
310    ) -> Result<(), ProtocolError> {
311        match self {
312            JsonValue::Object(ref mut m) => {
313                m.insert(
314                    String::from(property_name),
315                    JsonValue::Number(Number::from(protocol_version)),
316                );
317            }
318            _ => return Err(anyhow!("The '{:?}' isn't a map", self).into()),
319        }
320
321        Ok(())
322    }
323
324    fn remove_u32(&mut self, property_name: &str) -> Result<u32, anyhow::Error> {
325        match self {
326            JsonValue::Object(ref mut m) => match m.remove(property_name) {
327                Some(JsonValue::Number(number)) => Ok(number.as_u64().ok_or_else(|| {
328                    anyhow!("unable to convert '{}' into unsigned integer", number)
329                })? as u32),
330                _ => {
331                    bail!("Unable to find '{}' in '{}'", property_name, self)
332                }
333            },
334            _ => bail!("the Json Value isn't a map: {:?}", self),
335        }
336    }
337
338    /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id`
339    fn insert_with_path(
340        &mut self,
341        string_path: &str,
342        value: JsonValue,
343    ) -> Result<(), anyhow::Error> {
344        let path_literal: JsonPathLiteral = string_path.into();
345        let path: JsonPath = path_literal.try_into().unwrap();
346        insert_with_path(self, &path, value)
347    }
348
349    /// Removes the value under given path and tries to deserialize it into provided type
350    fn remove_value_at_path_into<K: DeserializeOwned>(
351        &mut self,
352        path: &str,
353    ) -> Result<K, anyhow::Error> {
354        let path_literal: JsonPathLiteral = path.into();
355        let json_path: JsonPath = path_literal.try_into().unwrap();
356
357        let data = remove_path(&json_path, self)
358            .ok_or_else(|| anyhow!("the '{path}' doesn't exists in '{self:#?}'"))?;
359
360        serde_json::from_value(data).map_err(|err| anyhow!("unable convert data: {}`", err))
361    }
362
363    fn get_bool(&self, property_name: &str) -> Result<bool, anyhow::Error> {
364        let property_value = self.get(property_name).ok_or_else(|| {
365            anyhow!(
366                "the property '{}' doesn't exist in '{:?}'",
367                property_name,
368                self
369            )
370        })?;
371
372        if let JsonValue::Bool(s) = property_value {
373            return Ok(*s);
374        }
375        bail!(
376            "getting property '{}' failed: {:?} isn't a boolean",
377            property_name,
378            property_value
379        );
380    }
381}
382
383fn identifier_filter(value: &JsonValue) -> bool {
384    if let JsonValue::Object(object) = value {
385        if let Some(JsonValue::String(media_type)) = object.get(PROPERTY_CONTENT_MEDIA_TYPE) {
386            return media_type == identifier::MEDIA_TYPE;
387        }
388    }
389    false
390}
391
392/// returns the value from the JsonValue based on the path: i.e "root.data[0].id"
393pub fn get_value_mut<'a>(string_path: &str, value: &'a mut JsonValue) -> Option<&'a mut JsonValue> {
394    let path_literal: JsonPathLiteral = string_path.into();
395    let path: JsonPath = path_literal.try_into().unwrap();
396    get_value_from_json_path_mut(&path, value)
397}
398
399/// returns the value from the JsonValue based on the JsonPath
400pub fn get_value_from_json_path_mut<'a>(
401    path: &[JsonPathStep],
402    value: &'a mut JsonValue,
403) -> Option<&'a mut JsonValue> {
404    let mut last_ptr: &mut JsonValue = value;
405
406    for step in path {
407        match step {
408            JsonPathStep::Index(index) => {
409                last_ptr = last_ptr.get_mut(index)?;
410            }
411
412            JsonPathStep::Key(key) => {
413                last_ptr = last_ptr.get_mut(key)?;
414            }
415        }
416    }
417    Some(last_ptr)
418}
419
420/// returns the value from the JsonValue based on the JsonPath
421pub fn get_value_from_json_path<'a>(
422    path: &[JsonPathStep],
423    value: &'a JsonValue,
424) -> Option<&'a JsonValue> {
425    let mut last_ptr: &JsonValue = value;
426
427    for step in path {
428        match step {
429            JsonPathStep::Index(index) => {
430                last_ptr = last_ptr.get(index)?;
431            }
432            JsonPathStep::Key(key) => {
433                last_ptr = last_ptr.get(key)?;
434            }
435        }
436    }
437    Some(last_ptr)
438}
439
440#[cfg(test)]
441mod test {
442    use serde_json::json;
443
444    use super::*;
445
446    #[test]
447    fn insert_with_parents() {
448        let mut document = json!({
449            "root" :  {
450                "from" : {
451                    "id": "123",
452                    "message": "text_message",
453                },
454            }
455        });
456
457        document
458            .insert_with_path("root.to.new_field", json!("new_value"))
459            .expect("no errors");
460        document
461            .insert_with_path("root.array[0].new_field", json!("new_value"))
462            .expect("no errors");
463
464        assert_eq!(document["root"]["from"]["id"], json!("123"));
465        assert_eq!(document["root"]["from"]["message"], json!("text_message"));
466        assert_eq!(document["root"]["to"]["new_field"], json!("new_value"));
467        assert_eq!(
468            document["root"]["array"][0]["new_field"],
469            json!("new_value")
470        );
471    }
472
473    // ------- New coverage tests -------
474
475    // push
476
477    #[test]
478    fn push_adds_to_array() {
479        let mut arr = json!([1, 2, 3]);
480        arr.push(json!(4)).expect("should push");
481        assert_eq!(arr, json!([1, 2, 3, 4]));
482    }
483
484    #[test]
485    fn push_on_non_array_fails() {
486        let mut obj = json!({"a": 1});
487        assert!(obj.push(json!(1)).is_err());
488    }
489
490    // insert
491
492    #[test]
493    fn insert_adds_key_to_object() {
494        let mut obj = json!({"existing": 1});
495        JsonValueExt::insert(&mut obj, "new_key".to_string(), json!("new_value"))
496            .expect("should insert");
497        assert_eq!(obj["new_key"], json!("new_value"));
498        assert_eq!(obj["existing"], json!(1));
499    }
500
501    #[test]
502    fn insert_on_non_object_fails() {
503        let mut arr = json!([1, 2]);
504        assert!(JsonValueExt::insert(&mut arr, "key".to_string(), json!(1)).is_err());
505    }
506
507    // remove
508
509    #[test]
510    fn remove_existing_property() {
511        let mut obj = json!({"a": 1, "b": 2});
512        let removed = JsonValueExt::remove(&mut obj, "a").expect("should remove");
513        assert_eq!(removed, json!(1));
514        assert!(obj.get("a").is_none());
515    }
516
517    #[test]
518    fn remove_nonexistent_property_fails() {
519        let mut obj = json!({"a": 1});
520        assert!(JsonValueExt::remove(&mut obj, "missing").is_err());
521    }
522
523    #[test]
524    fn remove_on_non_object_fails() {
525        let mut val = json!("string");
526        assert!(JsonValueExt::remove(&mut val, "key").is_err());
527    }
528
529    // remove_into
530
531    #[test]
532    fn remove_into_deserializes_value() {
533        let mut obj = json!({"count": 42});
534        let count: u64 = obj
535            .remove_into("count")
536            .expect("should remove and deserialize");
537        assert_eq!(count, 42);
538    }
539
540    #[test]
541    fn remove_into_missing_property_fails() {
542        let mut obj = json!({"a": 1});
543        let result: Result<u64, _> = obj.remove_into("missing");
544        assert!(result.is_err());
545    }
546
547    #[test]
548    fn remove_into_on_non_object_fails() {
549        let mut val = json!(123);
550        let result: Result<u64, _> = val.remove_into("key");
551        assert!(result.is_err());
552    }
553
554    // get_string
555
556    #[test]
557    fn get_string_returns_string_value() {
558        let obj = json!({"name": "Alice"});
559        let result = obj.get_string("name").expect("should get string");
560        assert_eq!(result, "Alice");
561    }
562
563    #[test]
564    fn get_string_missing_property_fails() {
565        let obj = json!({"a": 1});
566        assert!(obj.get_string("missing").is_err());
567    }
568
569    #[test]
570    fn get_string_non_string_value_fails() {
571        let obj = json!({"num": 42});
572        assert!(obj.get_string("num").is_err());
573    }
574
575    // get_u8
576
577    #[test]
578    fn get_u8_returns_u8_value() {
579        let obj = json!({"val": 200});
580        let result = obj.get_u8("val").expect("should get u8");
581        assert_eq!(result, 200);
582    }
583
584    #[test]
585    fn get_u8_too_large_fails() {
586        let obj = json!({"val": 300});
587        assert!(obj.get_u8("val").is_err());
588    }
589
590    #[test]
591    fn get_u8_missing_property_fails() {
592        let obj = json!({});
593        assert!(obj.get_u8("val").is_err());
594    }
595
596    #[test]
597    fn get_u8_non_number_fails() {
598        let obj = json!({"val": "text"});
599        assert!(obj.get_u8("val").is_err());
600    }
601
602    // get_u32
603
604    #[test]
605    fn get_u32_returns_u32_value() {
606        let obj = json!({"val": 100000});
607        let result = obj.get_u32("val").expect("should get u32");
608        assert_eq!(result, 100_000);
609    }
610
611    #[test]
612    fn get_u32_missing_property_fails() {
613        let obj = json!({});
614        assert!(obj.get_u32("val").is_err());
615    }
616
617    #[test]
618    fn get_u32_non_number_fails() {
619        let obj = json!({"val": true});
620        assert!(obj.get_u32("val").is_err());
621    }
622
623    // get_u64
624
625    #[test]
626    fn get_u64_returns_u64_value() {
627        let obj = json!({"val": 9999999999u64});
628        let result = obj.get_u64("val").expect("should get u64");
629        assert_eq!(result, 9_999_999_999);
630    }
631
632    #[test]
633    fn get_u64_missing_property_fails() {
634        let obj = json!({});
635        assert!(obj.get_u64("val").is_err());
636    }
637
638    #[test]
639    fn get_u64_non_number_fails() {
640        let obj = json!({"val": "text"});
641        assert!(obj.get_u64("val").is_err());
642    }
643
644    // get_i64
645
646    #[test]
647    fn get_i64_returns_i64_value() {
648        let obj = json!({"val": -42});
649        let result = obj.get_i64("val").expect("should get i64");
650        assert_eq!(result, -42);
651    }
652
653    #[test]
654    fn get_i64_positive_value() {
655        let obj = json!({"val": 100});
656        let result = obj.get_i64("val").expect("should get i64");
657        assert_eq!(result, 100);
658    }
659
660    #[test]
661    fn get_i64_missing_property_fails() {
662        let obj = json!({});
663        assert!(obj.get_i64("val").is_err());
664    }
665
666    #[test]
667    fn get_i64_non_number_fails() {
668        let obj = json!({"val": "text"});
669        assert!(obj.get_i64("val").is_err());
670    }
671
672    // get_f64
673
674    #[test]
675    fn get_f64_returns_f64_value() {
676        let obj = json!({"val": 3.14});
677        let result = obj.get_f64("val").expect("should get f64");
678        assert!((result - 3.14).abs() < f64::EPSILON);
679    }
680
681    #[test]
682    fn get_f64_integer_value() {
683        let obj = json!({"val": 42});
684        let result = obj.get_f64("val").expect("should get f64 from integer");
685        assert!((result - 42.0).abs() < f64::EPSILON);
686    }
687
688    #[test]
689    fn get_f64_missing_property_fails() {
690        let obj = json!({});
691        assert!(obj.get_f64("val").is_err());
692    }
693
694    #[test]
695    fn get_f64_non_number_fails() {
696        let obj = json!({"val": "text"});
697        assert!(obj.get_f64("val").is_err());
698    }
699
700    // get_bytes
701
702    #[test]
703    fn get_bytes_from_array() {
704        let obj = json!({"data": [1, 2, 3, 4, 5]});
705        let result = obj.get_bytes("data").expect("should get bytes");
706        assert_eq!(result, vec![1, 2, 3, 4, 5]);
707    }
708
709    #[test]
710    fn get_bytes_missing_property_fails() {
711        let obj = json!({});
712        assert!(obj.get_bytes("data").is_err());
713    }
714
715    // get_bool
716
717    #[test]
718    fn get_bool_true() {
719        let obj = json!({"flag": true});
720        let result = obj.get_bool("flag").expect("should get bool");
721        assert!(result);
722    }
723
724    #[test]
725    fn get_bool_false() {
726        let obj = json!({"flag": false});
727        let result = obj.get_bool("flag").expect("should get bool");
728        assert!(!result);
729    }
730
731    #[test]
732    fn get_bool_missing_property_fails() {
733        let obj = json!({});
734        assert!(obj.get_bool("flag").is_err());
735    }
736
737    #[test]
738    fn get_bool_non_bool_fails() {
739        let obj = json!({"flag": 1});
740        assert!(obj.get_bool("flag").is_err());
741    }
742
743    // get_value and get_value_mut
744
745    #[test]
746    fn get_value_navigates_nested_path() {
747        let obj = json!({
748            "a": {
749                "b": {
750                    "c": "deep_value"
751                }
752            }
753        });
754
755        let result = obj.get_value("a.b.c").expect("should find nested value");
756        assert_eq!(result, &json!("deep_value"));
757    }
758
759    #[test]
760    fn get_value_with_array_index() {
761        let obj = json!({
762            "items": [10, 20, 30]
763        });
764
765        let result = obj
766            .get_value("items[1]")
767            .expect("should find array element");
768        assert_eq!(result, &json!(20));
769    }
770
771    #[test]
772    fn get_value_missing_path_fails() {
773        let obj = json!({"a": 1});
774        assert!(obj.get_value("a.b.c").is_err());
775    }
776
777    #[test]
778    fn get_value_mut_modifies_nested_value() {
779        let mut obj = json!({
780            "a": {
781                "b": "old"
782            }
783        });
784
785        let val = obj.get_value_mut("a.b").expect("should find value");
786        *val = json!("new");
787
788        assert_eq!(obj["a"]["b"], json!("new"));
789    }
790
791    // get_value_by_path and get_value_by_path_mut
792
793    #[test]
794    fn get_value_by_path_with_steps() {
795        let obj = json!({
796            "root": {
797                "items": [1, 2, 3]
798            }
799        });
800
801        let path = vec![
802            JsonPathStep::Key("root".to_string()),
803            JsonPathStep::Key("items".to_string()),
804            JsonPathStep::Index(2),
805        ];
806
807        let result = obj.get_value_by_path(&path).expect("should find value");
808        assert_eq!(result, &json!(3));
809    }
810
811    #[test]
812    fn get_value_by_path_missing_fails() {
813        let obj = json!({"a": 1});
814        let path = vec![JsonPathStep::Key("nonexistent".to_string())];
815        assert!(obj.get_value_by_path(&path).is_err());
816    }
817
818    #[test]
819    fn get_value_by_path_mut_modifies_value() {
820        let mut obj = json!({
821            "list": ["a", "b", "c"]
822        });
823
824        let path = vec![
825            JsonPathStep::Key("list".to_string()),
826            JsonPathStep::Index(1),
827        ];
828
829        let val = obj.get_value_by_path_mut(&path).expect("should find value");
830        *val = json!("modified");
831
832        assert_eq!(obj["list"][1], json!("modified"));
833    }
834
835    // remove_u32
836
837    #[test]
838    fn remove_u32_removes_and_returns_value() {
839        let mut obj = json!({"version": 42});
840        let result = obj.remove_u32("version").expect("should remove u32");
841        assert_eq!(result, 42);
842        assert!(obj.get("version").is_none());
843    }
844
845    #[test]
846    fn remove_u32_missing_property_fails() {
847        let mut obj = json!({"a": 1});
848        assert!(obj.remove_u32("missing").is_err());
849    }
850
851    #[test]
852    fn remove_u32_non_number_fails() {
853        let mut obj = json!({"val": "text"});
854        assert!(obj.remove_u32("val").is_err());
855    }
856
857    #[test]
858    fn remove_u32_on_non_object_fails() {
859        let mut val = json!([1, 2, 3]);
860        assert!(val.remove_u32("key").is_err());
861    }
862
863    // add_protocol_version
864
865    #[test]
866    fn add_protocol_version_inserts_number() {
867        let mut obj = json!({});
868        obj.add_protocol_version("protocolVersion", 5)
869            .expect("should add protocol version");
870
871        assert_eq!(obj["protocolVersion"], json!(5));
872    }
873
874    #[test]
875    fn add_protocol_version_on_non_object_fails() {
876        let mut val = json!([1, 2]);
877        assert!(val.add_protocol_version("protocolVersion", 1).is_err());
878    }
879
880    // remove_value_at_path_into
881
882    #[test]
883    fn remove_value_at_path_into_removes_and_deserializes() {
884        let mut obj = json!({
885            "config": {
886                "timeout": 30
887            }
888        });
889
890        let timeout: u64 = obj
891            .remove_value_at_path_into("config.timeout")
892            .expect("should remove and deserialize");
893        assert_eq!(timeout, 30);
894        assert!(obj["config"].get("timeout").is_none());
895    }
896
897    #[test]
898    fn remove_value_at_path_into_missing_path_fails() {
899        let mut obj = json!({"a": 1});
900        let result: Result<u64, _> = obj.remove_value_at_path_into("a.b.c");
901        assert!(result.is_err());
902    }
903
904    // Free functions: get_value_mut, get_value_from_json_path, get_value_from_json_path_mut
905
906    #[test]
907    fn free_get_value_mut_works() {
908        let mut obj = json!({"x": {"y": 10}});
909        let val = get_value_mut("x.y", &mut obj).expect("should find value");
910        *val = json!(20);
911        assert_eq!(obj["x"]["y"], json!(20));
912    }
913
914    #[test]
915    fn get_value_from_json_path_navigates_keys_and_indices() {
916        let obj = json!({
917            "data": [
918                {"id": "first"},
919                {"id": "second"}
920            ]
921        });
922
923        let path = vec![
924            JsonPathStep::Key("data".to_string()),
925            JsonPathStep::Index(1),
926            JsonPathStep::Key("id".to_string()),
927        ];
928
929        let result = get_value_from_json_path(&path, &obj).expect("should find");
930        assert_eq!(result, &json!("second"));
931    }
932
933    #[test]
934    fn get_value_from_json_path_returns_none_for_missing() {
935        let obj = json!({"a": 1});
936        let path = vec![JsonPathStep::Key("nonexistent".to_string())];
937        assert!(get_value_from_json_path(&path, &obj).is_none());
938    }
939
940    #[test]
941    fn get_value_from_json_path_mut_modifies_value() {
942        let mut obj = json!({"items": [100, 200, 300]});
943
944        let path = vec![
945            JsonPathStep::Key("items".to_string()),
946            JsonPathStep::Index(0),
947        ];
948
949        let val = get_value_from_json_path_mut(&path, &mut obj).expect("should find");
950        *val = json!(999);
951        assert_eq!(obj["items"][0], json!(999));
952    }
953
954    #[test]
955    fn get_value_from_json_path_empty_path_returns_root() {
956        let obj = json!({"a": 1});
957        let path: Vec<JsonPathStep> = vec![];
958        let result = get_value_from_json_path(&path, &obj).expect("should return root");
959        assert_eq!(result, &json!({"a": 1}));
960    }
961
962    // insert_with_path via trait
963
964    #[test]
965    fn insert_with_path_creates_deeply_nested_structure() {
966        let mut obj = json!({});
967        obj.insert_with_path("a.b.c", json!("deep"))
968            .expect("should insert");
969        assert_eq!(obj["a"]["b"]["c"], json!("deep"));
970    }
971
972    #[test]
973    fn insert_with_path_into_existing_structure() {
974        let mut obj = json!({"a": {"b": 1}});
975        obj.insert_with_path("a.c", json!(2))
976            .expect("should insert sibling");
977        assert_eq!(obj["a"]["c"], json!(2));
978        assert_eq!(obj["a"]["b"], json!(1));
979    }
980}