1extern crate serde_json;
5
6use serde_json::Value;
7
8pub 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 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 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 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 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 let value = find_value_by_dotted_path("name.middle", &data);
81 assert_eq!(value, None, "Failed to handle non-existent path.");
82
83 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 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 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}