Skip to main content

jsonlogic/operators/
mod.rs

1mod test_helper;
2
3mod addition;
4mod all;
5mod and;
6mod cat;
7mod division;
8mod double_negation;
9mod equality;
10mod filter;
11mod greater_equal_than;
12mod greater_than;
13mod if_else;
14mod is_in;
15mod less_equal_than;
16mod less_than;
17mod log;
18mod logic;
19mod map;
20mod max;
21mod merge;
22mod min;
23mod missing;
24mod missing_some;
25mod modulo;
26mod multiplication;
27mod negation;
28mod none;
29mod not_equal;
30mod or;
31mod reduce;
32mod some;
33mod strict_equality;
34mod strict_not_equal;
35mod substr;
36mod subtraction;
37mod variable;
38
39use serde_json::Value;
40
41use super::expression::Expression;
42use super::Data;
43
44/// Represents a JsonLogic operator.
45#[derive(Debug, PartialEq, Copy, Clone)]
46pub enum Operator {
47    /// Tests abstract equality as specified in
48    /// https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison, with type
49    /// coercion. Requires two arguments.
50    Equal,
51    /// Tests strict equality. Requires two arguments.
52    StrictEqual,
53    /// Tests not-equal, with type coercion.
54    NotEqual,
55    /// Tests strict not-equal.
56    StrictNotEqual,
57    /// Retrieve data from the provided data object.
58    ///
59    /// If the first argument is null, the data object is returned as is.
60    Variable,
61    /// Logical negation (“not”). Takes just one argument.
62    Negation,
63    /// Double negation, or “cast to a boolean.” Takes a single argument.
64    DoubleNegation,
65    /// The if statement typically takes 3 arguments: a condition (if), what to do if it’s true
66    /// (then), and what to do if it’s false (else). If can also take more than 3 arguments, and
67    /// will pair up arguments like if/then elseif/then elseif/then else.
68    If,
69    /// Takes an arbitrary number of arguments. Returns the first truthy argument or the last
70    /// argument.
71    Or,
72    /// Takes an arbitrary number of arguments. Returns the first falsy argument or the last
73    /// argument.
74    And,
75    /// Less than. Takes exactly 2 arguments, otherwise returns `false`.
76    LessThan,
77    /// Less or equal than. Takes exactly 2 arguments, otherwise returns `false`.
78    LessEqualThan,
79    /// Greater than. Takes exactly 2 arguments, otherwise returns `false`.
80    GreaterThan,
81    /// Greater or equal than. Takes exactly 2 arguments, otherwise returns `false`.
82    GreaterEqualThan,
83    /// Takes an array of data keys to search for (same format as `var`).Returns an array of any
84    /// keys that are missing from the data object, or an empty array.
85    ///
86    /// Can also receive 1 argument that is an array of keys, which typically happens if it's
87    /// actually acting on the output of another command (like 'if' or 'merge').
88    /// See https://github.com/jwadhams/json-logic-js/blob/a15f528919346f2ec7d82bd4fc91c41481546c01/logic.js#L145
89    Missing,
90    /// Takes a minimum number of data keys that are required, and an array of keys to search for
91    /// (same format as `var` or `missing`). Returns an empty array if the minimum is met, or an
92    /// array of the missing keys otherwise.
93    MissingSome,
94    /// Return the minimum from a list of values. Matches the javascript `Math.min` implementation.
95    Min,
96    /// Return the maximum from a list of values. Matches the javascript `Math.max` implementation.
97    Max,
98    /// +, takes an arbitrary number of arguments and sums them up. If just one argument is passed,
99    /// it will be cast to a number. Returns `Value::Null` if one argument cannot be coerced into a
100    /// number.
101    Addition,
102    /// -, if just one argument is passed, it gets negated.
103    Subtraction,
104    /// *, takes an arbitrary number of arguments and multiplicates them.
105    Multiplication,
106    /// /
107    Division,
108    /// %, Finds the remainder after the first argument is divided by the second argument.
109    Modulo,
110    /// Expects two arguments. Tests either for substring or whether an array contains an element.
111    ///
112    /// If the second argument is an array, tests that the first argument is a member of the array.
113    ///
114    /// If the second argument is a string, tests that the first argument is a substring.
115    In,
116    /// Concatenate all the supplied arguments. Note that this is not a join or implode operation,
117    /// there is no "glue" string.
118    Cat,
119    /// Gets a portion of a string. Takes two to three arguments.
120    ///
121    /// The first argument is a string. Any other value will be coerced into a string.
122    ///
123    /// The second argument is a number (or will be coerced to a number, default to 0) and is the
124    /// start position to return everything beginning at that index. Give a negative start position
125    /// to work from the end of the string and then return the rest.
126    ///
127    /// The third argument limits the length of the returned substring. Give a negative index to
128    /// stop that many characters before the end.
129    Substr,
130    /// Logs the first value to console, then passes it through unmodified.
131    Log,
132    /// Takes one or more arrays, and merges them into one array. If arguments aren’t arrays, they
133    /// get cast to arrays.
134    Merge,
135    /// You can use `map` to perform an action on every member of an array. Note, that inside the
136    /// logic being used to map, var operations are relative to the array element being worked on.
137    Map,
138    /// You can use `filter` to keep only elements of the array that pass a test. Note, that inside
139    /// the logic being used to map, var operations are relative to the array element being worked
140    /// on.
141    ///
142    /// Also note, the returned array will have contiguous indexes starting at zero (typical for
143    /// JavaScript, Python and Ruby) it will not preserve the source indexes (making it unlike
144    /// PHP’s array_filter).
145    Filter,
146    /// You can use `reduce` to combine all the elements in an array into a single value, like adding
147    /// up a list of numbers. Note, that inside the logic being used to reduce, var operations only
148    /// have access to an object like:
149    ///
150    /// ```ignore
151    /// {
152    ///     "current" : // this element of the array,
153    ///     "accumulator" : // progress so far, or the initial value
154    /// }
155    /// ```
156    Reduce,
157    /// Takes an array as the first argument and a condition as the second argument. Returns `true`
158    /// if the condition evaluates to a truthy value for each element of the first parameter.
159    ///
160    /// `var` operations inside the second argument expression are relative to the array element
161    /// being tested.
162    All,
163    /// Takes an array as the first argument and a condition as the second argument. Returns `true`
164    /// if the condition evaluates to a truthy value for at least one element of the first
165    /// parameter.
166    ///
167    /// `var` operations inside the second argument expression are relative to the array element
168    /// being tested.
169    Some,
170    /// Takes an array as the first argument and a condition as the second argument. Returns `true`
171    /// if the condition evaluates to a falsy value for each element of the first parameter.
172    ///
173    /// `var` operations inside the second argument expression are relative to the array element
174    /// being tested.
175    None,
176}
177
178impl Operator {
179    /// Returns the Operator matching the given string representation. Returns None if the given
180    /// string matches no known operator.
181    pub fn from_str(s: &str) -> Option<Operator> {
182        match s {
183            "==" => Some(Operator::Equal),
184            "===" => Some(Operator::StrictEqual),
185            "!=" => Some(Operator::NotEqual),
186            "!==" => Some(Operator::StrictNotEqual),
187            "var" => Some(Operator::Variable),
188            "!" => Some(Operator::Negation),
189            "!!" => Some(Operator::DoubleNegation),
190            "if" => Some(Operator::If),
191            "or" => Some(Operator::Or),
192            "and" => Some(Operator::And),
193            "<" => Some(Operator::LessThan),
194            "<=" => Some(Operator::LessEqualThan),
195            ">" => Some(Operator::GreaterThan),
196            ">=" => Some(Operator::GreaterEqualThan),
197            "missing" => Some(Operator::Missing),
198            "missing_some" => Some(Operator::MissingSome),
199            "min" => Some(Operator::Min),
200            "max" => Some(Operator::Max),
201            "+" => Some(Operator::Addition),
202            "-" => Some(Operator::Subtraction),
203            "*" => Some(Operator::Multiplication),
204            "/" => Some(Operator::Division),
205            "%" => Some(Operator::Modulo),
206            "in" => Some(Operator::In),
207            "cat" => Some(Operator::Cat),
208            "substr" => Some(Operator::Substr),
209            "log" => Some(Operator::Log),
210            "merge" => Some(Operator::Merge),
211            "map" => Some(Operator::Map),
212            "filter" => Some(Operator::Filter),
213            "reduce" => Some(Operator::Reduce),
214            "all" => Some(Operator::All),
215            "some" => Some(Operator::Some),
216            "none" => Some(Operator::None),
217            _ => None,
218        }
219    }
220
221    pub fn compute(self, args: &[Expression], data: &Data) -> Value {
222        let compute_fn = match self {
223            Operator::Addition => addition::compute,
224            Operator::All => all::compute,
225            Operator::And => and::compute,
226            Operator::Cat => cat::compute,
227            Operator::Division => division::compute,
228            Operator::DoubleNegation => double_negation::compute,
229            Operator::Equal => equality::compute,
230            Operator::Filter => filter::compute,
231            Operator::GreaterEqualThan => greater_equal_than::compute,
232            Operator::GreaterThan => greater_than::compute,
233            Operator::If => if_else::compute,
234            Operator::In => is_in::compute,
235            Operator::LessEqualThan => less_equal_than::compute,
236            Operator::LessThan => less_than::compute,
237            Operator::Log => log::compute,
238            Operator::Max => max::compute,
239            Operator::Merge => merge::compute,
240            Operator::Min => min::compute,
241            Operator::MissingSome => missing_some::compute,
242            Operator::Missing => missing::compute,
243            Operator::Map => map::compute,
244            Operator::Modulo => modulo::compute,
245            Operator::Multiplication => multiplication::compute,
246            Operator::Negation => negation::compute,
247            Operator::None => none::compute,
248            Operator::NotEqual => not_equal::compute,
249            Operator::Or => or::compute,
250            Operator::Reduce => reduce::compute,
251            Operator::Some => some::compute,
252            Operator::StrictEqual => strict_equality::compute,
253            Operator::StrictNotEqual => strict_not_equal::compute,
254            Operator::Substr => substr::compute,
255            Operator::Subtraction => subtraction::compute,
256            Operator::Variable => variable::compute,
257        };
258
259        compute_fn(args, data)
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[allow(clippy::cognitive_complexity)]
268    #[test]
269    fn from_str() {
270        assert_eq!(Operator::from_str("=="), Some(Operator::Equal));
271        assert_eq!(Operator::from_str("!="), Some(Operator::NotEqual));
272        assert_eq!(Operator::from_str("==="), Some(Operator::StrictEqual));
273        assert_eq!(Operator::from_str("!=="), Some(Operator::StrictNotEqual));
274        assert_eq!(Operator::from_str("var"), Some(Operator::Variable));
275        assert_eq!(Operator::from_str("!"), Some(Operator::Negation));
276        assert_eq!(Operator::from_str("!!"), Some(Operator::DoubleNegation));
277        assert_eq!(Operator::from_str("if"), Some(Operator::If));
278        assert_eq!(Operator::from_str("or"), Some(Operator::Or));
279        assert_eq!(Operator::from_str("and"), Some(Operator::And));
280        assert_eq!(Operator::from_str("<"), Some(Operator::LessThan));
281        assert_eq!(Operator::from_str("<="), Some(Operator::LessEqualThan));
282        assert_eq!(Operator::from_str(">"), Some(Operator::GreaterThan));
283        assert_eq!(Operator::from_str(">="), Some(Operator::GreaterEqualThan));
284        assert_eq!(Operator::from_str("missing"), Some(Operator::Missing));
285        assert_eq!(
286            Operator::from_str("missing_some"),
287            Some(Operator::MissingSome)
288        );
289        assert_eq!(Operator::from_str("min"), Some(Operator::Min));
290        assert_eq!(Operator::from_str("max"), Some(Operator::Max));
291        assert_eq!(Operator::from_str("+"), Some(Operator::Addition));
292        assert_eq!(Operator::from_str("-"), Some(Operator::Subtraction));
293        assert_eq!(Operator::from_str("*"), Some(Operator::Multiplication));
294        assert_eq!(Operator::from_str("/"), Some(Operator::Division));
295        assert_eq!(Operator::from_str("%"), Some(Operator::Modulo));
296        assert_eq!(Operator::from_str("in"), Some(Operator::In));
297        assert_eq!(Operator::from_str("cat"), Some(Operator::Cat));
298        assert_eq!(Operator::from_str("substr"), Some(Operator::Substr));
299        assert_eq!(Operator::from_str("log"), Some(Operator::Log));
300        assert_eq!(Operator::from_str("merge"), Some(Operator::Merge));
301        assert_eq!(Operator::from_str("map"), Some(Operator::Map));
302        assert_eq!(Operator::from_str("filter"), Some(Operator::Filter));
303        assert_eq!(Operator::from_str("reduce"), Some(Operator::Reduce));
304        assert_eq!(Operator::from_str("all"), Some(Operator::All));
305        assert_eq!(Operator::from_str("none"), Some(Operator::None));
306        assert_eq!(Operator::from_str("some"), Some(Operator::Some));
307    }
308}