| 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 | |