Skip to main content

webui_state/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4extern crate serde_json;
5
6use serde_json::Value;
7
8// Finds a value in a JSON object by a dotted path.
9pub fn find_value_by_dotted_path(path: &str, state: &Value) -> Option<Value> {
10    let mut current_value: &Value = state;
11
12    for part in path.split('.') {
13        match current_value {
14            Value::Object(map) => {
15                current_value = map.get(part)?;
16            }
17            Value::Array(arr) if part == "length" => {
18                return Some(Value::Number(serde_json::Number::from(arr.len())));
19            }
20            Value::String(s) if part == "length" => {
21                return Some(Value::Number(serde_json::Number::from(s.len())));
22            }
23            _ => return None,
24        }
25    }
26
27    Some(current_value.clone())
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use webui_test_utils::test_json;
34
35    #[test]
36    fn test_find_value_by_dotted_path() {
37        // Create a JSON object
38        let data = test_json!({
39            "name": {
40                "first": "John",
41                "last": "Doe"
42            },
43            "favorite": {
44                "categories": {
45                    "movies": ["The Matrix", "The Godfather"],
46                    "music": ["Jazz", "Blues"]
47                }
48            },
49            "age": 30
50        });
51
52        // Test a successful path
53        let value = find_value_by_dotted_path("name.first", &data);
54        assert_eq!(
55            value,
56            Some(Value::String("John".to_string())),
57            "Failed to get string."
58        );
59
60        // Test a path that leads to single string
61        let value = find_value_by_dotted_path("age", &data);
62        assert_eq!(
63            value,
64            Some(Value::Number(serde_json::Number::from(30))),
65            "Failed to get number."
66        );
67
68        // Test a path that leads to an array
69        let value = find_value_by_dotted_path("favorite.categories.music", &data);
70        assert_eq!(
71            value,
72            Some(Value::Array(vec![
73                Value::String("Jazz".to_string()),
74                Value::String("Blues".to_string())
75            ])),
76            "Failed to get array."
77        );
78
79        // Test a non-existent path
80        let value = find_value_by_dotted_path("name.middle", &data);
81        assert_eq!(value, None, "Failed to handle non-existent path.");
82
83        // Test a path that leads to a non-object value
84        let value = find_value_by_dotted_path("age.years", &data);
85        assert_eq!(
86            value, None,
87            "Failed to handle path that leads to a non-object value."
88        );
89
90        // Get length of array.
91        let value = find_value_by_dotted_path("favorite.categories.music.length", &data);
92        assert_eq!(
93            value,
94            Some(Value::Number(serde_json::Number::from(2))),
95            "Failed to get length of array."
96        );
97    }
98
99    #[test]
100    fn test_nested_nested_path() {
101        let data = test_json!({
102            "user": {
103                "profile": {
104                    "nameObj": {
105                        "first": "John"
106                    }
107                }
108            }
109        });
110        let value = find_value_by_dotted_path("user.profile.nameObj.first", &data);
111        assert_eq!(value, Some(Value::String("John".to_string())));
112    }
113
114    #[test]
115    fn test_empty_string_path() {
116        let data = test_json!({ "key": "value" });
117        let value = find_value_by_dotted_path("", &data);
118        assert_eq!(value, None);
119    }
120
121    #[test]
122    fn test_path_with_spaces() {
123        let data = test_json!({
124            "user": {
125                "profile": {
126                    "name": "Alice"
127                }
128            }
129        });
130        let value = find_value_by_dotted_path("user.profile.non existent", &data);
131        assert_eq!(value, None);
132    }
133
134    #[test]
135    fn test_numeric_string_token() {
136        let data = test_json!({ "numeric_string": "123" });
137        let value = find_value_by_dotted_path("numeric_string", &data);
138        assert_eq!(value, Some(Value::String("123".to_string())));
139    }
140
141    #[test]
142    fn test_length_on_empty_list() {
143        let data = test_json!({ "empty_list": [] });
144        let value = find_value_by_dotted_path("empty_list.length", &data);
145        assert_eq!(value, Some(Value::Number(serde_json::Number::from(0))));
146    }
147
148    #[test]
149    fn test_length_on_nonempty_list() {
150        let data = test_json!({ "items": [1, 2, 3] });
151        let value = find_value_by_dotted_path("items.length", &data);
152        assert_eq!(value, Some(Value::Number(serde_json::Number::from(3))));
153    }
154
155    #[test]
156    fn test_length_named_property() {
157        let data = test_json!({
158            "nestedlength": {
159                "length": "some value"
160            }
161        });
162        let value = find_value_by_dotted_path("nestedlength.length", &data);
163        assert_eq!(value, Some(Value::String("some value".to_string())));
164    }
165
166    #[test]
167    fn test_length_on_nested_list() {
168        let data = test_json!({
169            "nested": {
170                "data": ["a", "b"]
171            }
172        });
173        let value = find_value_by_dotted_path("nested.data.length", &data);
174        assert_eq!(value, Some(Value::Number(serde_json::Number::from(2))));
175    }
176
177    #[test]
178    fn test_nonexistent_path_with_length() {
179        let data = test_json!({ "existing": "value" });
180        let value = find_value_by_dotted_path("non_existent.length", &data);
181        assert_eq!(value, None);
182    }
183
184    #[test]
185    fn test_length_on_string() {
186        let data = test_json!({ "str_val": "hello" });
187        let value = find_value_by_dotted_path("str_val.length", &data);
188        assert_eq!(value, Some(Value::Number(serde_json::Number::from(5))));
189    }
190
191    #[test]
192    fn test_length_on_string_matches_nodejs() {
193        // NodeJS: 'string_value.length' on "test" → 4
194        let data = test_json!({ "string_value": "test" });
195        let value = find_value_by_dotted_path("string_value.length", &data);
196        assert_eq!(value, Some(Value::Number(serde_json::Number::from(4))));
197    }
198
199    #[test]
200    fn test_array_index_not_resolved() {
201        let data = test_json!({
202            "foo": {
203                "bar": [10, 20, 30]
204            }
205        });
206        let value = find_value_by_dotted_path("foo.bar.0", &data);
207        assert_eq!(value, None);
208    }
209
210    #[test]
211    fn test_negative_array_index_not_resolved() {
212        let data = test_json!({
213            "foo": {
214                "bar": [10, 20, 30]
215            }
216        });
217        let value = find_value_by_dotted_path("foo.bar.-1", &data);
218        assert_eq!(value, None);
219    }
220
221    #[test]
222    fn test_numeric_property_on_object() {
223        let data = test_json!({
224            "foo": {
225                "barObj": {
226                    "0": "not an array"
227                }
228            }
229        });
230        let value = find_value_by_dotted_path("foo.barObj.0", &data);
231        assert_eq!(value, Some(Value::String("not an array".to_string())));
232    }
233
234    #[test]
235    fn test_property_value_zero() {
236        let data = test_json!({
237            "foo": {
238                "zero": 0
239            }
240        });
241        let value = find_value_by_dotted_path("foo.zero", &data);
242        assert_eq!(value, Some(Value::Number(serde_json::Number::from(0))));
243    }
244
245    #[test]
246    fn test_property_value_false() {
247        let data = test_json!({
248            "foo": {
249                "isFalse": false
250            }
251        });
252        let value = find_value_by_dotted_path("foo.isFalse", &data);
253        assert_eq!(value, Some(Value::Bool(false)));
254    }
255
256    #[test]
257    fn test_property_value_empty_string() {
258        let data = test_json!({
259            "foo": {
260                "empty": ""
261            }
262        });
263        let value = find_value_by_dotted_path("foo.empty", &data);
264        assert_eq!(value, Some(Value::String("".to_string())));
265    }
266
267    #[test]
268    fn test_deeply_nested_property() {
269        let data = test_json!({
270            "a": {
271                "b": {
272                    "c": {
273                        "d": {
274                            "e": {
275                                "f": 42
276                            }
277                        }
278                    }
279                }
280            }
281        });
282        let value = find_value_by_dotted_path("a.b.c.d.e.f", &data);
283        assert_eq!(value, Some(Value::Number(serde_json::Number::from(42))));
284    }
285
286    #[test]
287    fn test_property_with_special_char() {
288        let data = test_json!({
289            "foo": {
290                "$bar": 123
291            }
292        });
293        let value = find_value_by_dotted_path("foo.$bar", &data);
294        assert_eq!(value, Some(Value::Number(serde_json::Number::from(123))));
295    }
296
297    #[test]
298    fn test_property_named_tostring() {
299        let data = test_json!({
300            "foo": {
301                "toString": "stringify"
302            }
303        });
304        let value = find_value_by_dotted_path("foo.toString", &data);
305        assert_eq!(value, Some(Value::String("stringify".to_string())));
306    }
307
308    #[test]
309    fn test_intermediate_property_not_found() {
310        let data = test_json!({
311            "foo": {
312                "bar": "baz"
313            }
314        });
315        let value = find_value_by_dotted_path("foo.undefinedMid.baz", &data);
316        assert_eq!(value, None);
317    }
318
319    #[test]
320    fn test_string_in_deeply_nested_path() {
321        let data = test_json!({
322            "first": {
323                "second": {
324                    "third": {
325                        "data": "deep_string"
326                    }
327                }
328            }
329        });
330        let value = find_value_by_dotted_path("first.second.third.data", &data);
331        assert_eq!(value, Some(Value::String("deep_string".to_string())));
332    }
333
334    #[test]
335    fn test_double_value_directly() {
336        let data = test_json!({ "double_value": 3.14159 });
337        let value = find_value_by_dotted_path("double_value", &data);
338        assert_eq!(
339            value,
340            Some(Value::Number(
341                serde_json::Number::from_f64(3.14159).unwrap()
342            ))
343        );
344    }
345}