1use super::*;
5
6pub fn matches(tree: &serde_json::Value, paths_pattern: &str) -> Result<bool> {
8 for single_alternative in split_alternatives(paths_pattern) {
9 if match_single(tree, single_alternative)? {
10 return Ok(true);
11 }
12 }
13 Ok(false)
14}
15
16pub fn match_single(tree: &serde_json::Value, path: &str) -> Result<bool> {
19 match tree {
20 serde_json::Value::Object(map) => {
21 let (property_name, path_tail_opt) = split_head_tail(path)?;
22 let path_tail = match path_tail_opt {
23 None => return Ok(map.contains_key(property_name)),
24 Some(path_tail) => path_tail,
25 };
26
27 let property_val_opt = map.get(property_name);
28 match property_val_opt {
29 Some(subtree) => match_single(subtree, path_tail),
30 None => Ok(false),
31 }
32 }
33 _ => Ok(false),
36 }
37}
38
39pub fn split_alternatives(paths_pattern: &str) -> Vec<&str> {
47 paths_pattern
48 .split_terminator(',') .map(|item| item.trim()) .collect()
51}
52
53pub fn split_head_tail(path: &str) -> Result<(&str, Option<&str>)> {
61 if !path.starts_with('.') {
62 bail!("Path must start with '.' but it's: {}", path);
63 }
64 let path = &path[1..];
65
66 let dot_idx_opt = path.find('.');
67 let tuple = match dot_idx_opt {
68 None => (path, None),
69 Some(dot_idx) => {
70 let (path_head, path_tail) = path.split_at(dot_idx);
71 (path_head, Some(path_tail))
72 }
73 };
74 Ok(tuple)
75}
76
77#[cfg(test)]
78mod tests {
79 use serde_json::json;
80
81 use super::*;
82
83 fn sample_json_object() -> serde_json::Value {
84 json!({
85 "name": "John Doe",
86 "age": 43,
87 "phones": [
88 "+42 1234567",
89 "+44 2345678"
90 ],
91 "address": {
92 "country": "Germany",
93 "city": "Berlin",
94 "zip": 1234,
95 "street": {
96 "name": "Some Street",
97 "number": "1"
98 }
99 }
100 })
101 }
102
103 #[test]
104 fn path_matches() -> Result<()> {
105 let obj = sample_json_object();
106
107 assert!(matches(&obj, "invalidpath").is_err());
108 assert_eq!(matches(&obj, ".notpresent").ok(), Some(false));
109 assert_eq!(matches(&obj, ".name").ok(), Some(true));
110 assert_eq!(matches(&obj, ".a").ok(), Some(false));
111 assert_eq!(matches(&obj, ".age").ok(), Some(true));
112 assert_eq!(matches(&obj, ".ageover").ok(), Some(false));
113 assert_eq!(matches(&obj, ".phones").ok(), Some(true));
114 assert_eq!(matches(&obj, ".phones.first").ok(), Some(false));
115 assert_eq!(matches(&obj, ".address").ok(), Some(true));
116 assert_eq!(matches(&obj, ".address.country").ok(), Some(true));
117 assert_eq!(matches(&obj, ".address.city").ok(), Some(true));
118 assert_eq!(matches(&obj, ".address.street").ok(), Some(true));
119 assert_eq!(matches(&obj, ".address.street.name").ok(), Some(true));
120 assert_eq!(matches(&obj, ".address.street.number").ok(), Some(true));
121 assert_eq!(matches(&obj, ".address.street.none").ok(), Some(false));
122 assert_eq!(matches(&obj, ".address.phone").ok(), Some(false));
123
124 assert!(matches(&obj, ".none , invalid , .ageover ").is_err());
125 assert_eq!(matches(&obj, ".none , .fake , .ageover ").ok(), Some(false));
126 assert_eq!(matches(&obj, ".none , .fake , .ageover , .age").ok(), Some(true));
127 assert_eq!(matches(&obj, ".addr , .address.street.num, .xxx").ok(), Some(false));
128 assert_eq!(matches(&obj, ".addr , .address.street.number, .xxx").ok(), Some(true));
129 assert_eq!(matches(&obj, ".ad , .address.street.numbers, .xxx").ok(), Some(false));
130
131 Ok(())
132 }
133}