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