gq_core/query/
query_arguments.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Display, Formatter},
4};
5
6use super::{
7    apply::InternalError,
8    context::{Context, JsonPath},
9    QueryKey,
10};
11use derive_getters::Getters;
12use derive_more::Constructor;
13use regex::Regex;
14use serde_json::{Number, Value};
15use thiserror::Error;
16
17#[derive(Debug, Clone, Error)]
18pub enum Error<'a> {
19    #[error("types '{value_type}' and '{operation_value_type}' are not comparable at '{context}'")]
20    IncomparableTypes {
21        value_type: String,
22        operation_value_type: String,
23        context: JsonPath<'a>,
24    },
25    #[error(
26        "operation '{operation_type}' is not compatible with value type '{value_type}' at '{context}'"
27    )]
28    IncompatibleOperation {
29        value_type: String,
30        operation_type: String,
31        context: JsonPath<'a>,
32    },
33    #[error("cannot conver number '{0}' to f64 at {1}")]
34    NumberConversionError(Number, JsonPath<'a>),
35    #[error("{error} while processing arguments at '{context}'")]
36    InsideArguments {
37        error: Box<Self>,
38        context: JsonPath<'a>,
39    },
40    #[error("{0}")]
41    InternalError(InternalError<'a>),
42}
43
44impl<'a> From<InternalError<'a>> for Error<'a> {
45    fn from(internal_error: InternalError<'a>) -> Self {
46        Self::InternalError(internal_error)
47    }
48}
49
50// TODO: This trait and implementations are very messy, re-structure them (in other modules?)
51pub trait ValueType {
52    fn value_type(&self) -> String;
53}
54
55impl ValueType for Value {
56    fn value_type(&self) -> String {
57        match self {
58            Value::String(_) => "string".to_string(),
59            Value::Number(_) => "number".to_string(),
60            Value::Bool(_) => "bool".to_string(),
61            Value::Null => "null".to_string(),
62            Value::Array(_) => "array".to_string(),
63            Value::Object(_) => "object".to_string(),
64        }
65    }
66}
67
68impl ValueType for QueryArgumentValue {
69    fn value_type(&self) -> String {
70        match self {
71            QueryArgumentValue::String(_) => "string".to_string(),
72            QueryArgumentValue::Number(_) => "number".to_string(),
73            QueryArgumentValue::Bool(_) => "bool".to_string(),
74            QueryArgumentValue::Null => "null".to_string(),
75        }
76    }
77}
78
79impl ValueType for f64 {
80    fn value_type(&self) -> String {
81        "number".to_string()
82    }
83}
84
85impl ValueType for Regex {
86    fn value_type(&self) -> String {
87        "regex".to_string()
88    }
89}
90
91impl ValueType for QueryArgumentOperation {
92    fn value_type(&self) -> String {
93        match self {
94            QueryArgumentOperation::Equal(value) => value.value_type(),
95            QueryArgumentOperation::NotEqual(value) => value.value_type(),
96            QueryArgumentOperation::Greater(value) => value.value_type(),
97            QueryArgumentOperation::GreaterEqual(value) => value.value_type(),
98            QueryArgumentOperation::Less(value) => value.value_type(),
99            QueryArgumentOperation::LessEqual(value) => value.value_type(),
100            QueryArgumentOperation::Match(value) => value.value_type(),
101            QueryArgumentOperation::NotMatch(value) => value.value_type(),
102        }
103    }
104}
105
106pub trait OperationType {
107    fn operation_type(&self) -> String;
108}
109
110impl OperationType for QueryArgumentOperation {
111    fn operation_type(&self) -> String {
112        match self {
113            QueryArgumentOperation::Equal(_) => "=".to_string(),
114            QueryArgumentOperation::NotEqual(_) => "!=".to_string(),
115            QueryArgumentOperation::Greater(_) => ">".to_string(),
116            QueryArgumentOperation::GreaterEqual(_) => ">=".to_string(),
117            QueryArgumentOperation::Less(_) => "<".to_string(),
118            QueryArgumentOperation::LessEqual(_) => "<=".to_string(),
119            QueryArgumentOperation::Match(_) => "~".to_string(),
120            QueryArgumentOperation::NotMatch(_) => "!~".to_string(),
121        }
122    }
123}
124
125#[derive(Debug, Clone)]
126pub enum QueryArgumentValue {
127    String(String),
128    Number(f64),
129    Bool(bool),
130    Null,
131}
132
133impl Display for QueryArgumentValue {
134    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
135        match self {
136            QueryArgumentValue::String(value) => write!(f, "\"{value}\""),
137            QueryArgumentValue::Number(value) => write!(f, "{value}"),
138            QueryArgumentValue::Bool(value) => write!(f, "{value}"),
139            QueryArgumentValue::Null => write!(f, "null"),
140        }
141    }
142}
143
144#[derive(Debug, Clone)]
145pub enum QueryArgumentOperation {
146    Equal(QueryArgumentValue),
147    NotEqual(QueryArgumentValue),
148    Greater(f64),
149    GreaterEqual(f64),
150    Less(f64),
151    LessEqual(f64),
152    Match(Regex),
153    NotMatch(Regex),
154}
155
156impl Display for QueryArgumentOperation {
157    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
158        // TODO: create a pretty format method or use this in Query::pretty_format
159        match self {
160            QueryArgumentOperation::Equal(value) => write!(f, "={value}"),
161            QueryArgumentOperation::NotEqual(value) => write!(f, "!={value}"),
162            QueryArgumentOperation::Greater(value) => write!(f, ">{value}"),
163            QueryArgumentOperation::GreaterEqual(value) => write!(f, ">={value}"),
164            QueryArgumentOperation::Less(value) => write!(f, "<{value}"),
165            QueryArgumentOperation::LessEqual(value) => write!(f, "<={value}"),
166            QueryArgumentOperation::Match(regex) => write!(f, "~\"{regex}\""),
167            QueryArgumentOperation::NotMatch(regex) => write!(f, "!~\"{regex}\""),
168        }
169    }
170}
171
172impl<'a> QueryArgumentOperation {
173    fn satisfies(&self, value: &Value, context: &Context<'a>) -> Result<bool, Error<'a>> {
174        match self {
175            QueryArgumentOperation::Equal(operation_value) => {
176                self.satisfies_equal(operation_value, value, context)
177            }
178            QueryArgumentOperation::NotEqual(operation_value) => self
179                .satisfies_equal(operation_value, value, context)
180                .map(|result| !result),
181            QueryArgumentOperation::Greater(operation_value) => {
182                self.satisfies_greater(*operation_value, value, context)
183            }
184            QueryArgumentOperation::GreaterEqual(operation_value) => self
185                .satisfies_less(*operation_value, value, context)
186                .map(|result| !result),
187            QueryArgumentOperation::Less(operation_value) => {
188                self.satisfies_less(*operation_value, value, context)
189            }
190            QueryArgumentOperation::LessEqual(operation_value) => self
191                .satisfies_greater(*operation_value, value, context)
192                .map(|result| !result),
193            QueryArgumentOperation::Match(operation_value) => {
194                self.satisfies_match(operation_value, value, context)
195            }
196            QueryArgumentOperation::NotMatch(operation_value) => self
197                .satisfies_match(operation_value, value, context)
198                .map(|result| !result),
199        }
200    }
201
202    fn satisfies_op_array<F>(array: &[Value], satisfies_op: F, context: &Context<'a>) -> bool
203    where
204        F: Fn(&Value, &Context<'a>) -> Result<bool, Error<'a>>,
205    {
206        array
207            .iter()
208            .enumerate()
209            .map(|(index, item)| (context.push_index(index), item))
210            .map(|(item_context, item)| satisfies_op(item, &item_context))
211            .any(|result| {
212                result
213                    .map_err(|error| {
214                        log::warn!("{error}");
215                    })
216                    .unwrap_or(false)
217            })
218    }
219
220    fn satisfies_equal(
221        &self,
222        operation_value: &QueryArgumentValue,
223        value: &Value,
224        context: &Context<'a>,
225    ) -> Result<bool, Error<'a>> {
226        match (operation_value, value) {
227            (QueryArgumentValue::String(operation_value), Value::String(value)) => {
228                Ok(operation_value == value)
229            }
230            (QueryArgumentValue::Number(operation_value), Value::Number(value)) => {
231                value
232                    .as_f64()
233                    .map(|value| value == *operation_value)
234                    // TODO: improve number value conversion\
235                    .ok_or(Error::NumberConversionError(
236                        value.clone(),
237                        context.path().clone(),
238                    ))
239            }
240            (QueryArgumentValue::Bool(operation_value), Value::Bool(value)) => {
241                Ok(operation_value == value)
242            }
243            (QueryArgumentValue::Null, Value::Null) => Ok(true),
244            (QueryArgumentValue::Null, _) => Ok(false),
245            (_, Value::Null) => Ok(false),
246            (_, Value::Array(array)) => {
247                let satisfies_op = |item: &Value, context: &Context<'a>| {
248                    self.satisfies_equal(operation_value, item, context)
249                };
250                Ok(Self::satisfies_op_array(array, satisfies_op, context))
251            }
252            _ => Err(self.incomparable_types_error(operation_value, value, context)),
253        }
254    }
255    fn satisfies_greater(
256        &self,
257        operation_value: f64,
258        value: &Value,
259        context: &Context<'a>,
260    ) -> Result<bool, Error<'a>> {
261        match value {
262            Value::Number(value) => value.as_f64().map(|value| value > operation_value).ok_or(
263                Error::NumberConversionError(value.clone(), context.path().clone()),
264            ),
265            Value::Array(array) => {
266                let satisfies_op = |item: &Value, context: &Context<'a>| {
267                    self.satisfies_greater(operation_value, item, context)
268                };
269                Ok(Self::satisfies_op_array(array, satisfies_op, context))
270            }
271            _ => Err(self.incompatible_operation_error(value, context)),
272        }
273    }
274
275    fn satisfies_less(
276        &self,
277        operation_value: f64,
278        value: &Value,
279        context: &Context<'a>,
280    ) -> Result<bool, Error<'a>> {
281        match value {
282            Value::Number(value) => value.as_f64().map(|value| value < operation_value).ok_or(
283                Error::NumberConversionError(value.clone(), context.path().clone()),
284            ),
285            Value::Array(array) => {
286                let satisfies_op = |item: &Value, context: &Context<'a>| {
287                    self.satisfies_less(operation_value, item, context)
288                };
289                Ok(Self::satisfies_op_array(array, satisfies_op, context))
290            }
291            _ => Err(self.incompatible_operation_error(value, context)),
292        }
293    }
294
295    fn satisfies_match(
296        &self,
297        operation_value: &Regex,
298        value: &Value,
299        context: &Context<'a>,
300    ) -> Result<bool, Error<'a>> {
301        match value {
302            Value::String(value) => Ok(operation_value.is_match(value)),
303            Value::Array(array) => {
304                let satisfies_op = |item: &Value, context: &Context<'a>| {
305                    self.satisfies_match(operation_value, item, context)
306                };
307                Ok(Self::satisfies_op_array(array, satisfies_op, context))
308            }
309            _ => Err(self.incompatible_operation_error(value, context)),
310        }
311    }
312
313    fn incomparable_types_error<T: ValueType, U: ValueType>(
314        &self,
315        operation_value: &T,
316        value: &U,
317        context: &Context<'a>,
318    ) -> Error<'a> {
319        let value_type = value.value_type();
320        let operation_value_type = operation_value.value_type();
321        Error::IncomparableTypes {
322            value_type,
323            operation_value_type,
324            context: context.path().clone(),
325        }
326    }
327    fn incompatible_operation_error<T: ValueType>(
328        &self,
329        value: &T,
330        context: &Context<'a>,
331    ) -> Error<'a> {
332        let value_type = value.value_type();
333        let operation_type = self.operation_type();
334        Error::IncompatibleOperation {
335            value_type,
336            operation_type,
337            context: context.path().clone(),
338        }
339    }
340}
341
342#[derive(Debug, Clone, Constructor, Getters)]
343pub struct QueryArgument {
344    key: QueryKey,
345    operation: QueryArgumentOperation,
346}
347
348impl Display for QueryArgument {
349    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
350        let key = self.key();
351        let operation = self.operation();
352        write!(f, "{key}{operation}")
353    }
354}
355
356#[derive(Debug, Clone, Constructor, Default)]
357pub struct QueryArguments(pub Vec<QueryArgument>);
358
359impl Display for QueryArguments {
360    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
361        let arguments = self
362            .0
363            .iter()
364            .map(ToString::to_string)
365            .collect::<Vec<_>>()
366            .join(", ");
367        arguments.fmt(f)
368    }
369}
370
371impl QueryArguments {
372    //TODO: improve method naming
373    pub fn satisfies(&self, value: &Value, context: &Context) -> bool {
374        self.0.iter().all(|argument| {
375            argument
376                .satisfies(value, context)
377                .map_err(|error| {
378                    let argument_error = Error::InsideArguments {
379                        // TODO: include the argument.to_string() here?
380                        error: Box::new(error),
381                        context: context.path().clone(),
382                    };
383                    log::warn!("{argument_error}");
384                })
385                .unwrap_or(false)
386        })
387    }
388}
389
390impl<'a> QueryArgument {
391    const DEFAULT_INSPECTED_VALUE: Cow<'static, Value> = Cow::Owned(Value::Null);
392
393    fn satisfies(&'a self, value: &Value, context: &Context<'a>) -> Result<bool, Error<'a>> {
394        let argument_key = self.key();
395
396        let inspected_value = match argument_key.inspect(value, context) {
397            Ok(value) => value,
398            // TODO: only return null value for the KeyNotFound error?
399            // TODO: the query inspection should not use InternalError, it is too generic
400            Err(error @ InternalError::KeyNotFound(_)) => {
401                log::info!("{error}, using null value");
402                Self::DEFAULT_INSPECTED_VALUE
403            }
404            Err(error) => return Err(error.into()),
405        };
406
407        let inspected_context = context.push_query_key(argument_key);
408        self.operation
409            .satisfies(&inspected_value, &inspected_context)
410    }
411}