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