| 1 | use crate::parser::model::{FnArg, TestFunction}; | 
| 2 | use crate::query::queryable::Queryable; | 
|---|
| 3 | use crate::query::state::{Data, Pointer, State}; | 
|---|
| 4 | use crate::query::Query; | 
|---|
| 5 | use regex::Regex; | 
|---|
| 6 | use std::borrow::Cow; | 
|---|
| 7 |  | 
|---|
| 8 | impl TestFunction { | 
|---|
| 9 | pub fn apply<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { | 
|---|
| 10 | match self { | 
|---|
| 11 | TestFunction::Length(arg: &Box) => length(state:arg.process(state)), | 
|---|
| 12 | TestFunction::Count(arg: &FnArg) => count(state:arg.process(state)), | 
|---|
| 13 | TestFunction::Match(lhs: &FnArg, rhs: &FnArg) => { | 
|---|
| 14 | regex(lhs.process(state.clone()), rhs.process(state), substr:false) | 
|---|
| 15 | } | 
|---|
| 16 | TestFunction::Search(lhs: &FnArg, rhs: &FnArg) => { | 
|---|
| 17 | regex(lhs.process(state.clone()), rhs.process(state), substr:true) | 
|---|
| 18 | } | 
|---|
| 19 | TestFunction::Custom(name: &String, args: &Vec) => custom(name, args, state), | 
|---|
| 20 | TestFunction::Value(arg: &FnArg) => value(state:arg.process(state)), | 
|---|
| 21 | _ => State::nothing(state.root), | 
|---|
| 22 | } | 
|---|
| 23 | } | 
|---|
| 24 | } | 
|---|
| 25 |  | 
|---|
| 26 | impl Query for FnArg { | 
|---|
| 27 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { | 
|---|
| 28 | match self { | 
|---|
| 29 | FnArg::Literal(lit: &Literal) => lit.process(state:step), | 
|---|
| 30 | FnArg::Test(test: &Box) => test.process(state:step), | 
|---|
| 31 | FnArg::Filter(filter: &Filter) => filter.process(state:step), | 
|---|
| 32 | } | 
|---|
| 33 | } | 
|---|
| 34 | } | 
|---|
| 35 |  | 
|---|
| 36 | impl Query for TestFunction { | 
|---|
| 37 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { | 
|---|
| 38 | self.apply(state:step) | 
|---|
| 39 | } | 
|---|
| 40 | } | 
|---|
| 41 |  | 
|---|
| 42 | fn custom<'a, T: Queryable>(name: &str, args: &Vec<FnArg>, state: State<'a, T>) -> State<'a, T> { | 
|---|
| 43 | let args: Vec> = argsimpl Iterator > | 
|---|
| 44 | .into_iter() | 
|---|
| 45 | .map(|v: &FnArg| v.process(state.clone())) | 
|---|
| 46 | .flat_map(|v: State<'_, T>| match v.data { | 
|---|
| 47 | Data::Value(v: T) => vec![Cow::Owned(v)], | 
|---|
| 48 | Data::Ref(Pointer { inner: &T, .. }) => vec![Cow::Borrowed(inner)], | 
|---|
| 49 | Data::Refs(v: Vec>) => v.into_iter().map(|v: Pointer<'_, T>| Cow::Borrowed(v.inner)).collect(), | 
|---|
| 50 | _ => vec![], | 
|---|
| 51 | }) | 
|---|
| 52 | .collect::<Vec<_>>(); | 
|---|
| 53 |  | 
|---|
| 54 | State::data( | 
|---|
| 55 | state.root, | 
|---|
| 56 | Data::Value(Queryable::extension_custom(name, args)), | 
|---|
| 57 | ) | 
|---|
| 58 | } | 
|---|
| 59 |  | 
|---|
| 60 | /// Returns the length/size of the object. | 
|---|
| 61 | /// | 
|---|
| 62 | /// # Returns | 
|---|
| 63 | /// | 
|---|
| 64 | /// Returns a `Progress` enum containing either: | 
|---|
| 65 | /// - `Progress::Data` with a vector of references to self and the query path for strings/arrays/objects | 
|---|
| 66 | /// - `Progress::Nothing` for other types | 
|---|
| 67 | /// | 
|---|
| 68 | /// The returned length follows JSON path length() function semantics based on the type: | 
|---|
| 69 | /// - String type: Number of Unicode scalar values | 
|---|
| 70 | /// - Array type: Number of elements | 
|---|
| 71 | /// - Object type: Number of members | 
|---|
| 72 | /// - Other types: Nothing | 
|---|
| 73 | fn length<T: Queryable>(state: State<T>) -> State<T> { | 
|---|
| 74 | let from_item: impl Fn(&T) -> State<'_, T> = |item: &T| { | 
|---|
| 75 | if let Some(v: &str) = item.as_str() { | 
|---|
| 76 | State::i64(i:v.chars().count() as i64, state.root) | 
|---|
| 77 | } else if let Some(items: &Vec) = item.as_array() { | 
|---|
| 78 | State::i64(i:items.len() as i64, state.root) | 
|---|
| 79 | } else if let Some(items: Vec<(&String, &T)>) = item.as_object() { | 
|---|
| 80 | State::i64(i:items.len() as i64, state.root) | 
|---|
| 81 | } else { | 
|---|
| 82 | State::nothing(state.root) | 
|---|
| 83 | } | 
|---|
| 84 | }; | 
|---|
| 85 |  | 
|---|
| 86 | match state.data { | 
|---|
| 87 | Data::Ref(Pointer { inner: &T, .. }) => from_item(item:inner), | 
|---|
| 88 | Data::Refs(items: Vec>) => State::i64(i:items.len() as i64, state.root), | 
|---|
| 89 | Data::Value(item: T) => from_item(&item), | 
|---|
| 90 | Data::Nothing => State::nothing(state.root), | 
|---|
| 91 | } | 
|---|
| 92 | } | 
|---|
| 93 |  | 
|---|
| 94 | /// The count() function extension provides a way | 
|---|
| 95 | /// to obtain the number of nodes in a nodelist | 
|---|
| 96 | /// and make that available for further processing in the filter expression | 
|---|
| 97 | fn count<T: Queryable>(state: State<T>) -> State<T> { | 
|---|
| 98 | let to_state: impl Fn(i64) -> State<'_, …> = |count: i64| State::i64(i:count, state.root); | 
|---|
| 99 |  | 
|---|
| 100 | match state.data { | 
|---|
| 101 | Data::Ref(..) | Data::Value(..) => to_state(count:1), | 
|---|
| 102 | Data::Refs(items: Vec>) => to_state(count:items.len() as i64), | 
|---|
| 103 | Data::Nothing => State::nothing(state.root), | 
|---|
| 104 | } | 
|---|
| 105 | } | 
|---|
| 106 | /// The match() function extension provides | 
|---|
| 107 | /// a way to check whether (the entirety of; see Section 2.4.7) | 
|---|
| 108 | /// a given string matches a given regular expression, | 
|---|
| 109 | /// which is in the form described in [RFC9485]. | 
|---|
| 110 | /// | 
|---|
| 111 | /// Its arguments are instances of ValueType | 
|---|
| 112 | /// (possibly taken from a singular query, | 
|---|
| 113 | /// as for the first argument in the example above). | 
|---|
| 114 | /// If the first argument is not a string | 
|---|
| 115 | /// or the second argument is not a string conforming to [RFC9485], | 
|---|
| 116 | /// the result is LogicalFalse. Otherwise, the string that is the first argument is matched against | 
|---|
| 117 | /// the I-Regexp contained in the string that is the second argument; the result is LogicalTrue | 
|---|
| 118 | /// if the string matches the I-Regexp and is LogicalFalse otherwise. | 
|---|
| 119 | fn regex<'a, T: Queryable>(lhs: State<'a, T>, rhs: State<'a, T>, substr: bool) -> State<'a, T> { | 
|---|
| 120 | let to_state: impl Fn(bool) -> State<'_, …> = |b: bool| State::bool(b, lhs.root); | 
|---|
| 121 | let regex: impl Fn(&str, Regex) -> bool = |v: &str, r: Regex| { | 
|---|
| 122 | if substr { | 
|---|
| 123 | r.find(haystack:v).is_some() | 
|---|
| 124 | } else { | 
|---|
| 125 | r.is_match(haystack:v) | 
|---|
| 126 | } | 
|---|
| 127 | }; | 
|---|
| 128 | let to_str: impl Fn(State<'a, T>) -> … = |s: State<'a, T>| match s.data { | 
|---|
| 129 | Data::Value(v: T) => v.as_str().map(|s: &str| s.to_string()), | 
|---|
| 130 | Data::Ref(Pointer { inner: &T, .. }) => inner.as_str().map(|s: &str| s.to_string()), | 
|---|
| 131 | _ => None, | 
|---|
| 132 | }; | 
|---|
| 133 |  | 
|---|
| 134 | match (to_str(lhs), to_str(rhs)) { | 
|---|
| 135 | (Some(lhs: String), Some(rhs: String)) => Regex::new(&prepare_regex(rhs, substr)) | 
|---|
| 136 | .map(|re| to_state(regex(&lhs, re))) | 
|---|
| 137 | .unwrap_or(default:to_state(false)), | 
|---|
| 138 | _ => to_state(false), | 
|---|
| 139 | } | 
|---|
| 140 | } | 
|---|
| 141 |  | 
|---|
| 142 | fn prepare_regex(pattern: String, substring: bool) -> String { | 
|---|
| 143 | let pattern: String = if !substring { | 
|---|
| 144 | let pattern: String = if pattern.starts_with( '^') { | 
|---|
| 145 | pattern | 
|---|
| 146 | } else { | 
|---|
| 147 | format!( "^{} ", pattern) | 
|---|
| 148 | }; | 
|---|
| 149 | let pattern: String = if pattern.ends_with( '$') { | 
|---|
| 150 | pattern | 
|---|
| 151 | } else { | 
|---|
| 152 | format!( "{} $", pattern) | 
|---|
| 153 | }; | 
|---|
| 154 | pattern | 
|---|
| 155 | } else { | 
|---|
| 156 | pattern.to_string() | 
|---|
| 157 | }; | 
|---|
| 158 | let pattern: String = if pattern.contains( "\\\\ ") { | 
|---|
| 159 | pattern.replace(from: "\\\\ ", to: "\\ ") | 
|---|
| 160 | } else { | 
|---|
| 161 | pattern.to_string() | 
|---|
| 162 | }; | 
|---|
| 163 |  | 
|---|
| 164 | pattern.trim_matches(|c: char| c == '\' '|| c == '"').to_string() | 
|---|
| 165 | } | 
|---|
| 166 |  | 
|---|
| 167 | fn value<T: Queryable>(state: State<T>) -> State<T> { | 
|---|
| 168 | match state.data { | 
|---|
| 169 | Data::Ref(..) | Data::Value(..) => state, | 
|---|
| 170 | Data::Refs(items: Vec>) if items.len() == 1 => { | 
|---|
| 171 | State::data(state.root, Data::Ref(items[0].clone())) | 
|---|
| 172 | } | 
|---|
| 173 | _ => State::nothing(state.root), | 
|---|
| 174 | } | 
|---|
| 175 | } | 
|---|
| 176 |  | 
|---|
| 177 | #[ cfg(test)] | 
|---|
| 178 | mod tests { | 
|---|
| 179 | use crate::parser::model::Segment; | 
|---|
| 180 | use crate::parser::model::Selector; | 
|---|
| 181 | use crate::parser::model::Test; | 
|---|
| 182 | use crate::parser::model::TestFunction; | 
|---|
| 183 | use crate::query::state::{Data, Pointer, State}; | 
|---|
| 184 | use crate::query::test_function::{regex, FnArg}; | 
|---|
| 185 | use crate::query::Query; | 
|---|
| 186 | use crate::{arg, q_segment, segment, selector, test, test_fn}; | 
|---|
| 187 | use serde_json::json; | 
|---|
| 188 |  | 
|---|
| 189 | #[ test] | 
|---|
| 190 | fn test_len() { | 
|---|
| 191 | let json = json!({ "array": [1,2,3]}); | 
|---|
| 192 | let state = State::root(&json); | 
|---|
| 193 |  | 
|---|
| 194 | let query = test_fn!(length arg!(t test!(@ segment!(selector!(array))))); | 
|---|
| 195 | let res = query.process(state); | 
|---|
| 196 |  | 
|---|
| 197 | assert_eq!(res.ok_val(), Some(json!(3))); | 
|---|
| 198 | } | 
|---|
| 199 |  | 
|---|
| 200 | #[ test] | 
|---|
| 201 | fn test_match_1() { | 
|---|
| 202 | let json = json!({ "a": "abc sdgfudsf", "b": "abc.*"}); | 
|---|
| 203 | let state = State::root(&json); | 
|---|
| 204 |  | 
|---|
| 205 | let query = test_fn!(match | 
|---|
| 206 | arg!(t test!(@ segment!(selector!(a)))), | 
|---|
| 207 | arg!(t test!(@ segment!(selector!(b)))) | 
|---|
| 208 | ); | 
|---|
| 209 | let res = query.process(state); | 
|---|
| 210 |  | 
|---|
| 211 | assert_eq!(res.ok_val(), Some(json!(true))); | 
|---|
| 212 | } | 
|---|
| 213 |  | 
|---|
| 214 | #[ test] | 
|---|
| 215 | fn test_count_1() { | 
|---|
| 216 | let json = json!({ "array": [1,2,3]}); | 
|---|
| 217 | let state = State::root(&json); | 
|---|
| 218 |  | 
|---|
| 219 | let query = test_fn!(count arg!(t test!(@ segment!(selector!(array))))); | 
|---|
| 220 | let res = query.process(state); | 
|---|
| 221 |  | 
|---|
| 222 | assert_eq!(res.ok_val(), Some(json!(1))); | 
|---|
| 223 | } | 
|---|
| 224 |  | 
|---|
| 225 | #[ test] | 
|---|
| 226 | fn test_search() { | 
|---|
| 227 | let json = json!( "123"); | 
|---|
| 228 | let state = State::root(&json); | 
|---|
| 229 | let reg = State::str( "[a-z]+", &json); | 
|---|
| 230 |  | 
|---|
| 231 | let res = regex(state, reg, true); | 
|---|
| 232 |  | 
|---|
| 233 | assert_eq!(res.ok_val(), Some(json!(false))); | 
|---|
| 234 | } | 
|---|
| 235 |  | 
|---|
| 236 | #[ test] | 
|---|
| 237 | fn test_match() { | 
|---|
| 238 | let json = json!( "bbab"); | 
|---|
| 239 | let state = State::root(&json); | 
|---|
| 240 | let reg = State::str( "^b.?b$", &json); | 
|---|
| 241 |  | 
|---|
| 242 | let res = regex(state, reg, false); | 
|---|
| 243 |  | 
|---|
| 244 | assert_eq!(res.ok_val(), Some(json!(false))); | 
|---|
| 245 | } | 
|---|
| 246 | } | 
|---|
| 247 |  | 
|---|