platform_value/pointer.rs
1use crate::{Value, ValueMapHelper};
2use std::mem;
3
4fn parse_index(s: &str) -> Option<usize> {
5 if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
6 return None;
7 }
8 s.parse().ok()
9}
10
11impl Value {
12 /// Looks up a value by a Platform Value Pointer.
13 ///
14 /// Platform Value Pointer defines a string syntax for identifying a specific value
15 /// within a Platform Value document.
16 ///
17 /// A Pointer is a Unicode string with the reference tokens separated by `/`.
18 /// Inside tokens `/` is replaced by `~1` and `~` is replaced by `~0`. The
19 /// addressed value is returned and if there is no such value `None` is
20 /// returned.
21 ///
22 /// For more information read [RFC6901](https://tools.ietf.org/html/rfc6901).
23 ///
24 /// # Examples
25 ///
26 /// ```
27 /// # use platform_value::platform_value;
28 /// #
29 /// let data = platform_value!({
30 /// "x": {
31 /// "y": ["z", "zz"]
32 /// }
33 /// });
34 ///
35 /// assert_eq!(data.pointer("/x/y/1").unwrap(), &platform_value!("zz"));
36 /// assert_eq!(data.pointer("/a/b/c"), None);
37 /// ```
38 pub fn pointer(&self, pointer: &str) -> Option<&Value> {
39 if pointer.is_empty() {
40 return Some(self);
41 }
42 if !pointer.starts_with('/') {
43 return None;
44 }
45 pointer
46 .split('/')
47 .skip(1)
48 .map(|x| x.replace("~1", "/").replace("~0", "~"))
49 .try_fold(self, |target, token| match target {
50 Value::Map(map) => map.get_optional_key(&token),
51 Value::Array(list) => parse_index(&token).and_then(|x| list.get(x)),
52 _ => None,
53 })
54 }
55
56 /// Looks up a value by a Platform Value Pointer and returns a mutable reference to
57 /// that value.
58 ///
59 /// Platform Value Pointer defines a string syntax for identifying a specific value
60 /// within a Platform Value document.
61 ///
62 /// A Pointer is a Unicode string with the reference tokens separated by `/`.
63 /// Inside tokens `/` is replaced by `~1` and `~` is replaced by `~0`. The
64 /// addressed value is returned and if there is no such value `None` is
65 /// returned.
66 ///
67 /// For more information read [RFC6901](https://tools.ietf.org/html/rfc6901).
68 ///
69 /// # Example of Use
70 ///
71 /// ```
72 /// use platform_value::Value;
73 ///
74 /// use platform_value::platform_value;
75 /// let mut value: Value = platform_value!({"x": 1.0, "y": 2.0});
76 ///
77 /// // Check value using read-only pointer
78 /// assert_eq!(value.pointer("/x"), Some(&1.0.into()));
79 /// // Change value with direct assignment
80 /// *value.pointer_mut("/x").unwrap() = 1.5.into();
81 /// // Check that new value was written
82 /// assert_eq!(value.pointer("/x"), Some(&1.5.into()));
83 /// // Or change the value only if it exists
84 /// value.pointer_mut("/x").map(|v| *v = 1.5.into());
85 ///
86 /// // "Steal" ownership of a value. Can replace with any valid Value.
87 /// let old_x = value.pointer_mut("/x").map(Value::take).unwrap();
88 /// assert_eq!(old_x, 1.5);
89 /// assert_eq!(value.pointer("/x").unwrap(), &Value::Null);
90 /// ```
91 pub fn pointer_mut(&mut self, pointer: &str) -> Option<&mut Value> {
92 if pointer.is_empty() {
93 return Some(self);
94 }
95 if !pointer.starts_with('/') {
96 return None;
97 }
98 pointer
99 .split('/')
100 .skip(1)
101 .map(|x| x.replace("~1", "/").replace("~0", "~"))
102 .try_fold(self, |target, token| match target {
103 Value::Map(map) => map.get_optional_key_mut(&token),
104 Value::Array(list) => parse_index(&token).and_then(move |x| list.get_mut(x)),
105 _ => None,
106 })
107 }
108
109 /// Takes the value out of the `Value`, leaving a `Null` in its place.
110 ///
111 /// ```
112 /// # use platform_value::platform_value;
113 /// #
114 /// let mut v = platform_value!({ "x": "y" });
115 /// assert_eq!(v["x"].take(), platform_value!("y"));
116 /// assert_eq!(v, platform_value!({ "x": null }));
117 /// ```
118 pub fn take(&mut self) -> Value {
119 mem::replace(self, Value::Null)
120 }
121}