jsonlogic_rs/op/
logic.rs

1//! Boolean Logic Operations
2
3use serde_json::Value;
4
5use crate::error::Error;
6use crate::value::{Evaluated, Parsed};
7use crate::NULL;
8
9/// Implement the "if" operator
10///
11/// The base case works like: [condition, true, false]
12/// However, it can lso work like:
13///     [condition, true, condition2, true2, false2]
14///     for an if/elseif/else type of operation
15pub fn if_(data: &Value, args: &Vec<&Value>) -> Result<Value, Error> {
16    // Special case incorrect arguments. These are not defined in the
17    // specification, but they are defined in the test cases.
18    match args.len() {
19        0 => {
20            return Ok(NULL);
21        }
22        // It's not totally clear to me why this would be the behavior,
23        // rather than returning NULL regardless of how the single argument
24        // evaluates, but this is I can gather is the expected behavior
25        // from the tests.
26        1 => {
27            let parsed = Parsed::from_value(args[0])?;
28            let evaluated = parsed.evaluate(&data)?;
29            return Ok(evaluated.into());
30        }
31        _ => {}
32    }
33
34    args.into_iter()
35        .enumerate()
36        // Our accumulator is:
37        //  - last conditional evaluation value,
38        //  - whether that evaluation is truthy,
39        //  - whether we know we should return without further evaluation
40        .fold(Ok((NULL, false, false)), |last_res, (i, val)| {
41            let (last_eval, was_truthy, should_return) = last_res?;
42            // We hit a final value already
43            if should_return {
44                Ok((last_eval, was_truthy, should_return))
45            }
46            // Potential false-value, initial evaluation, or else-if clause
47            else if i % 2 == 0 {
48                let parsed = Parsed::from_value(val)?;
49                let eval = parsed.evaluate(data)?;
50                let is_truthy = match eval {
51                    Evaluated::New(ref v) => truthy(v),
52                    Evaluated::Raw(v) => truthy(v),
53                };
54                // We're not sure we're the return value, so don't
55                // force a return.
56                Ok((eval.into(), is_truthy, false))
57            }
58            // We're a possible true-value
59            else {
60                // If there was a previous evaluation and it was truthy,
61                // return, and indicate we're a final value.
62                if was_truthy {
63                    let parsed = Parsed::from_value(val)?;
64                    let t_eval = parsed.evaluate(data)?;
65                    Ok((Value::from(t_eval), true, true))
66                } else {
67                    // Return a null for the last eval to handle cases
68                    // where there is an incorrect number of arguments.
69                    Ok((NULL, was_truthy, should_return))
70                }
71            }
72        })
73        .map(|rv| rv.0)
74}
75
76/// Perform short-circuiting or evaluation
77pub fn or(data: &Value, args: &Vec<&Value>) -> Result<Value, Error> {
78    enum OrResult {
79        Uninitialized,
80        Truthy(Value),
81        Current(Value),
82    }
83
84    let eval =
85        args.into_iter()
86            .fold(Ok(OrResult::Uninitialized), |last_res, current| {
87                let last_eval = last_res?;
88
89                // if we've found a truthy value, don't evaluate anything else
90                if let OrResult::Truthy(_) = last_eval {
91                    return Ok(last_eval);
92                }
93
94                let parsed = Parsed::from_value(current)?;
95                let evaluated = parsed.evaluate(data)?;
96
97                if truthy_from_evaluated(&evaluated) {
98                    return Ok(OrResult::Truthy(evaluated.into()));
99                }
100
101                Ok(OrResult::Current(evaluated.into()))
102            })?;
103
104    match eval {
105        OrResult::Truthy(v) => Ok(v),
106        OrResult::Current(v) => Ok(v),
107        _ => Err(Error::UnexpectedError(
108            "Or operation had no values to operate on".into(),
109        )),
110    }
111}
112
113/// Perform short-circuiting and evaluation
114pub fn and(data: &Value, args: &Vec<&Value>) -> Result<Value, Error> {
115    enum AndResult {
116        Uninitialized,
117        Falsey(Value),
118        Current(Value),
119    }
120
121    let eval =
122        args.into_iter()
123            .fold(Ok(AndResult::Uninitialized), |last_res, current| {
124                let last_eval = last_res?;
125
126                if let AndResult::Falsey(_) = last_eval {
127                    return Ok(last_eval);
128                }
129
130                let parsed = Parsed::from_value(current)?;
131                let evaluated = parsed.evaluate(data)?;
132
133                if !truthy_from_evaluated(&evaluated) {
134                    return Ok(AndResult::Falsey(evaluated.into()));
135                }
136
137                Ok(AndResult::Current(evaluated.into()))
138            })?;
139
140    match eval {
141        AndResult::Falsey(v) => Ok(v),
142        AndResult::Current(v) => Ok(v),
143        _ => Err(Error::UnexpectedError(
144            "And operation had no values to operate on".into(),
145        )),
146    }
147}
148
149pub fn truthy_from_evaluated(evaluated: &Evaluated) -> bool {
150    match evaluated {
151        Evaluated::New(ref v) => truthy(v),
152        Evaluated::Raw(v) => truthy(v),
153    }
154}
155
156/// Return whether a value is "truthy" by the JSONLogic spec
157///
158/// The spec (http://jsonlogic.com/truthy) defines truthy values that
159/// diverge slightly from raw JavaScript. This ensures a matching
160/// interpretation.
161///
162/// In general, the spec specifies that values are truthy or falsey
163/// depending on their containing something, e.g. non-zero integers,
164/// non-zero length strings, and non-zero length arrays are truthy.
165/// This does not apply to objects, which are always truthy.
166pub fn truthy(val: &Value) -> bool {
167    match val {
168        Value::Null => false,
169        Value::Bool(v) => *v,
170        Value::Number(v) => v
171            .as_f64()
172            .map(|v_num| if v_num == 0.0 { false } else { true })
173            .unwrap_or(false),
174        Value::String(v) => {
175            if v == "" {
176                false
177            } else {
178                true
179            }
180        }
181        Value::Array(v) => {
182            if v.len() == 0 {
183                false
184            } else {
185                true
186            }
187        }
188        Value::Object(_) => true,
189    }
190}
191
192#[cfg(test)]
193mod test_truthy {
194    use super::*;
195    use serde_json::json;
196
197    #[test]
198    fn test_truthy() {
199        let trues = [
200            json!(true),
201            json!([1]),
202            json!([1, 2]),
203            json!({}),
204            json!({"a": 1}),
205            json!(1),
206            json!(-1),
207            json!("foo"),
208        ];
209
210        let falses = [json!(false), json!([]), json!(""), json!(0), json!(null)];
211
212        trues.iter().for_each(|v| assert!(truthy(&v)));
213        falses.iter().for_each(|v| assert!(!truthy(&v)));
214    }
215}