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}
122
123#[cfg(test)]
124mod tests {
125    use crate::{platform_value, Value};
126
127    // ---------------------------------------------------------------
128    // parse_index (module-private helper)
129    // ---------------------------------------------------------------
130
131    #[test]
132    fn parse_index_valid_number() {
133        assert_eq!(super::parse_index("0"), Some(0));
134        assert_eq!(super::parse_index("1"), Some(1));
135        assert_eq!(super::parse_index("42"), Some(42));
136    }
137
138    #[test]
139    fn parse_index_leading_plus_returns_none() {
140        assert_eq!(super::parse_index("+1"), None);
141    }
142
143    #[test]
144    fn parse_index_leading_zero_returns_none() {
145        assert_eq!(super::parse_index("01"), None);
146        assert_eq!(super::parse_index("007"), None);
147    }
148
149    #[test]
150    fn parse_index_single_zero_is_valid() {
151        assert_eq!(super::parse_index("0"), Some(0));
152    }
153
154    #[test]
155    fn parse_index_non_numeric_returns_none() {
156        assert_eq!(super::parse_index("abc"), None);
157        assert_eq!(super::parse_index(""), None);
158    }
159
160    // ---------------------------------------------------------------
161    // pointer() — read-only access
162    // ---------------------------------------------------------------
163
164    #[test]
165    fn pointer_empty_string_returns_self() {
166        let data = platform_value!({"a": 1});
167        assert_eq!(data.pointer(""), Some(&data));
168    }
169
170    #[test]
171    fn pointer_no_leading_slash_returns_none() {
172        let data = platform_value!({"a": 1});
173        assert_eq!(data.pointer("a"), None);
174        assert_eq!(data.pointer("a/b"), None);
175    }
176
177    #[test]
178    fn pointer_simple_key_lookup() {
179        let data = platform_value!({"a": 1, "b": 2});
180        assert_eq!(data.pointer("/a"), Some(&platform_value!(1)));
181        assert_eq!(data.pointer("/b"), Some(&platform_value!(2)));
182    }
183
184    #[test]
185    fn pointer_nested_key_lookup() {
186        let data = platform_value!({"x": {"y": {"z": 42}}});
187        assert_eq!(data.pointer("/x/y/z"), Some(&platform_value!(42)));
188    }
189
190    #[test]
191    fn pointer_missing_path_returns_none() {
192        let data = platform_value!({"a": 1});
193        assert_eq!(data.pointer("/b"), None);
194        assert_eq!(data.pointer("/a/b/c"), None);
195    }
196
197    #[test]
198    fn pointer_array_index() {
199        let data = platform_value!({"arr": [10, 20, 30]});
200        assert_eq!(data.pointer("/arr/0"), Some(&platform_value!(10)));
201        assert_eq!(data.pointer("/arr/1"), Some(&platform_value!(20)));
202        assert_eq!(data.pointer("/arr/2"), Some(&platform_value!(30)));
203    }
204
205    #[test]
206    fn pointer_array_out_of_bounds_returns_none() {
207        let data = platform_value!({"arr": [10]});
208        assert_eq!(data.pointer("/arr/5"), None);
209    }
210
211    #[test]
212    fn pointer_tilde_escape_tilde_zero_becomes_tilde() {
213        // Key contains a literal ~ character, encoded as ~0
214        let data = platform_value!({"a~b": 1});
215        assert_eq!(data.pointer("/a~0b"), Some(&platform_value!(1)));
216    }
217
218    #[test]
219    fn pointer_tilde_escape_tilde_one_becomes_slash() {
220        // Key contains a literal / character, encoded as ~1
221        let data = platform_value!({"a/b": 1});
222        assert_eq!(data.pointer("/a~1b"), Some(&platform_value!(1)));
223    }
224
225    #[test]
226    fn pointer_combined_tilde_escapes() {
227        let data = platform_value!({"~/key": 99});
228        // ~ is ~0, / is ~1, so "~/key" is encoded as "~0~1key"
229        assert_eq!(data.pointer("/~0~1key"), Some(&platform_value!(99)));
230    }
231
232    #[test]
233    fn pointer_scalar_value_returns_none_for_child() {
234        let data = platform_value!(42);
235        assert_eq!(data.pointer("/anything"), None);
236    }
237
238    #[test]
239    fn pointer_nested_array_in_map() {
240        let data = platform_value!({
241            "x": {
242                "y": ["z", "zz"]
243            }
244        });
245        assert_eq!(data.pointer("/x/y/0"), Some(&platform_value!("z")));
246        assert_eq!(data.pointer("/x/y/1"), Some(&platform_value!("zz")));
247    }
248
249    #[test]
250    fn pointer_root_is_array() {
251        let data = platform_value!(["a", "b", "c"]);
252        assert_eq!(data.pointer("/0"), Some(&platform_value!("a")));
253        assert_eq!(data.pointer("/2"), Some(&platform_value!("c")));
254    }
255
256    #[test]
257    fn pointer_leading_zero_index_rejected() {
258        let data = platform_value!({"arr": [10, 20, 30]});
259        // "01" has a leading zero (and len > 1), so parse_index returns None
260        assert_eq!(data.pointer("/arr/01"), None);
261    }
262
263    // ---------------------------------------------------------------
264    // pointer_mut() — mutable access
265    // ---------------------------------------------------------------
266
267    #[test]
268    fn pointer_mut_empty_string_returns_self() {
269        let mut data = platform_value!({"a": 1});
270        let reference = data.pointer_mut("");
271        assert!(reference.is_some());
272    }
273
274    #[test]
275    fn pointer_mut_no_leading_slash_returns_none() {
276        let mut data = platform_value!({"a": 1});
277        assert!(data.pointer_mut("a").is_none());
278    }
279
280    #[test]
281    fn pointer_mut_modify_nested_value() {
282        let mut data = platform_value!({"x": {"y": 1}});
283        *data.pointer_mut("/x/y").unwrap() = platform_value!(99);
284        assert_eq!(data.pointer("/x/y"), Some(&platform_value!(99)));
285    }
286
287    #[test]
288    fn pointer_mut_modify_array_element() {
289        let mut data = platform_value!({"arr": [10, 20, 30]});
290        *data.pointer_mut("/arr/1").unwrap() = platform_value!(999);
291        assert_eq!(data.pointer("/arr/1"), Some(&platform_value!(999)));
292    }
293
294    #[test]
295    fn pointer_mut_missing_path_returns_none() {
296        let mut data = platform_value!({"a": 1});
297        assert!(data.pointer_mut("/nonexistent").is_none());
298        assert!(data.pointer_mut("/a/b/c").is_none());
299    }
300
301    #[test]
302    fn pointer_mut_tilde_escapes() {
303        let mut data = platform_value!({"a/b": 1, "c~d": 2});
304        *data.pointer_mut("/a~1b").unwrap() = platform_value!(10);
305        *data.pointer_mut("/c~0d").unwrap() = platform_value!(20);
306        assert_eq!(data.pointer("/a~1b"), Some(&platform_value!(10)));
307        assert_eq!(data.pointer("/c~0d"), Some(&platform_value!(20)));
308    }
309
310    #[test]
311    fn pointer_mut_scalar_returns_none() {
312        let mut data = platform_value!("hello");
313        assert!(data.pointer_mut("/anything").is_none());
314    }
315
316    // ---------------------------------------------------------------
317    // take() — replace with Null and return old value
318    // ---------------------------------------------------------------
319
320    #[test]
321    fn take_replaces_with_null() {
322        let mut data = platform_value!({"x": "y"});
323        let taken = data.pointer_mut("/x").unwrap().take();
324        assert_eq!(taken, platform_value!("y"));
325        assert_eq!(data.pointer("/x"), Some(&Value::Null));
326    }
327
328    #[test]
329    fn take_on_integer() {
330        let mut val: Value = platform_value!(42);
331        let taken = val.take();
332        assert_eq!(taken, platform_value!(42));
333        assert_eq!(val, Value::Null);
334    }
335
336    #[test]
337    fn take_on_null_returns_null() {
338        let mut val = Value::Null;
339        let taken = val.take();
340        assert_eq!(taken, Value::Null);
341        assert_eq!(val, Value::Null);
342    }
343
344    #[test]
345    fn take_on_array() {
346        let mut val = platform_value!([1, 2, 3]);
347        let taken = val.take();
348        assert_eq!(taken, platform_value!([1, 2, 3]));
349        assert_eq!(val, Value::Null);
350    }
351
352    #[test]
353    fn take_nested_via_pointer_mut() {
354        let mut data = platform_value!({"a": {"b": "deep"}});
355        let taken = data.pointer_mut("/a/b").map(Value::take).unwrap();
356        assert_eq!(taken, platform_value!("deep"));
357        assert_eq!(data.pointer("/a/b"), Some(&Value::Null));
358    }
359}