vrl/compiler/value/
arithmetic.rs

1#![deny(clippy::arithmetic_side_effects)]
2#![allow(clippy::cast_precision_loss, clippy::module_name_repetitions)]
3
4use std::ops::{Add, Mul, Rem};
5
6use crate::compiler::{
7    ExpressionError,
8    value::{Kind, VrlValueConvert},
9};
10use crate::value::{ObjectMap, Value};
11use bytes::{BufMut, Bytes, BytesMut};
12
13use super::ValueError;
14
15#[allow(clippy::missing_errors_doc)]
16pub trait VrlValueArithmetic: Sized {
17    /// Similar to [`std::ops::Mul`], but fallible (e.g. `TryMul`).
18    fn try_mul(self, rhs: Self) -> Result<Self, ValueError>;
19
20    /// Similar to [`std::ops::Div`], but fallible (e.g. `TryDiv`).
21    fn try_div(self, rhs: Self) -> Result<Self, ValueError>;
22
23    /// Similar to [`std::ops::Add`], but fallible (e.g. `TryAdd`).
24    fn try_add(self, rhs: Self) -> Result<Self, ValueError>;
25
26    /// Similar to [`std::ops::Sub`], but fallible (e.g. `TrySub`).
27    fn try_sub(self, rhs: Self) -> Result<Self, ValueError>;
28
29    /// Try to "OR" (`||`) two values types.
30    ///
31    /// If the lhs value is `null` or `false`, the rhs is evaluated and
32    /// returned. The rhs is a closure that can return an error, and thus this
33    /// method can return an error as well.
34    fn try_or(self, rhs: impl FnMut() -> Result<Self, ExpressionError>)
35    -> Result<Self, ValueError>;
36
37    /// Try to "AND" (`&&`) two values types.
38    ///
39    /// A lhs or rhs value of `Null` returns `false`.
40    fn try_and(self, rhs: Self) -> Result<Self, ValueError>;
41
42    /// Similar to [`std::ops::Rem`], but fallible (e.g. `TryRem`).
43    fn try_rem(self, rhs: Self) -> Result<Self, ValueError>;
44
45    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
46    fn try_gt(self, rhs: Self) -> Result<Self, ValueError>;
47
48    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
49    fn try_ge(self, rhs: Self) -> Result<Self, ValueError>;
50
51    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
52    fn try_lt(self, rhs: Self) -> Result<Self, ValueError>;
53
54    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
55    fn try_le(self, rhs: Self) -> Result<Self, ValueError>;
56
57    fn try_merge(self, rhs: Self) -> Result<Self, ValueError>;
58
59    /// Similar to [`std::cmp::Eq`], but does a lossless comparison for integers
60    /// and floats.
61    fn eq_lossy(&self, rhs: &Self) -> bool;
62}
63
64fn safe_sub(lhv: f64, rhv: f64) -> Option<Value> {
65    let result = lhv - rhv;
66    if result.is_nan() {
67        None
68    } else {
69        Some(Value::from_f64_or_zero(result))
70    }
71}
72
73impl VrlValueArithmetic for Value {
74    /// Similar to [`std::ops::Mul`], but fallible (e.g. `TryMul`).
75    fn try_mul(self, rhs: Self) -> Result<Self, ValueError> {
76        let err = || ValueError::Mul(self.kind(), rhs.kind());
77
78        // When multiplying a string by an integer, if the number is negative we set it to zero to
79        // return an empty string.
80        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
81        let as_usize = |num| if num < 0 { 0 } else { num as usize };
82
83        let value = match self {
84            Value::Integer(lhv) if rhs.is_bytes() => {
85                Bytes::from(rhs.try_bytes()?.repeat(as_usize(lhv))).into()
86            }
87            Value::Integer(lhv) if rhs.is_float() => {
88                Value::from_f64_or_zero(lhv as f64 * rhs.try_float()?)
89            }
90            Value::Integer(lhv) => {
91                let rhv_i64 = rhs.try_into_i64().map_err(|_| err())?;
92                i64::wrapping_mul(lhv, rhv_i64).into()
93            }
94            Value::Float(lhv) => {
95                let rhs = rhs.try_into_f64().map_err(|_| err())?;
96                lhv.mul(rhs).into()
97            }
98            Value::Bytes(lhv) if rhs.is_integer() => {
99                Bytes::from(lhv.repeat(as_usize(rhs.try_integer()?))).into()
100            }
101            _ => return Err(err()),
102        };
103
104        Ok(value)
105    }
106
107    /// Similar to [`std::ops::Div`], but fallible (e.g. `TryDiv`).
108    fn try_div(self, rhs: Self) -> Result<Self, ValueError> {
109        let err = || ValueError::Div(self.kind(), rhs.kind());
110
111        let rhv_f64 = rhs.try_into_f64().map_err(|_| err())?;
112
113        if rhv_f64 == 0.0 {
114            return Err(ValueError::DivideByZero);
115        }
116
117        let value = match self {
118            Value::Integer(lhv) => Value::from_f64_or_zero(lhv as f64 / rhv_f64),
119            Value::Float(lhv) => Value::from_f64_or_zero(lhv.into_inner() / rhv_f64),
120            _ => return Err(err()),
121        };
122
123        Ok(value)
124    }
125
126    /// Similar to [`std::ops::Add`], but fallible (e.g. `TryAdd`).
127    fn try_add(self, rhs: Self) -> Result<Self, ValueError> {
128        let value = match (self, rhs) {
129            (Value::Integer(lhs), Value::Float(rhs)) => Value::from_f64_or_zero(lhs as f64 + *rhs),
130            (Value::Integer(lhs), rhs) => {
131                let rhv_i64 = rhs
132                    .try_into_i64()
133                    .map_err(|_| ValueError::Add(Kind::integer(), rhs.kind()))?;
134                i64::wrapping_add(lhs, rhv_i64).into()
135            }
136            (Value::Float(lhs), rhs) => {
137                let rhs = rhs
138                    .try_into_f64()
139                    .map_err(|_| ValueError::Add(Kind::float(), rhs.kind()))?;
140                lhs.add(rhs).into()
141            }
142            (lhs @ Value::Bytes(_), Value::Null) => lhs,
143            (Value::Bytes(lhs), Value::Bytes(rhs)) => {
144                #[allow(clippy::arithmetic_side_effects)]
145                let mut value = BytesMut::with_capacity(lhs.len() + rhs.len());
146                value.put(lhs);
147                value.put(rhs);
148                value.freeze().into()
149            }
150            (Value::Null, rhs @ Value::Bytes(_)) => rhs,
151            (lhs, rhs) => return Err(ValueError::Add(lhs.kind(), rhs.kind())),
152        };
153
154        Ok(value)
155    }
156
157    /// Similar to [`std::ops::Sub`], but fallible (e.g. `TrySub`).
158    fn try_sub(self, rhs: Self) -> Result<Self, ValueError> {
159        let err = || ValueError::Sub(self.kind(), rhs.kind());
160
161        let value = match self {
162            Value::Integer(lhv) if rhs.is_float() => {
163                Value::from_f64_or_zero(lhv as f64 - rhs.try_float()?)
164            }
165            Value::Integer(lhv) => {
166                let rhv_i64 = rhs.try_into_i64().map_err(|_| err())?;
167                i64::wrapping_sub(lhv, rhv_i64).into()
168            }
169            Value::Float(lhs) => {
170                let rhs = rhs.try_into_f64().map_err(|_| err())?;
171                safe_sub(*lhs, rhs).ok_or_else(err)?
172            }
173            _ => return Err(err()),
174        };
175
176        Ok(value)
177    }
178
179    /// Try to "OR" (`||`) two values types.
180    ///
181    /// If the lhs value is `null` or `false`, the rhs is evaluated and
182    /// returned. The rhs is a closure that can return an error, and thus this
183    /// method can return an error as well.
184    fn try_or(
185        self,
186        mut rhs: impl FnMut() -> Result<Self, ExpressionError>,
187    ) -> Result<Self, ValueError> {
188        let err = ValueError::Or;
189
190        match self {
191            Value::Null | Value::Boolean(false) => rhs().map_err(err),
192            value => Ok(value),
193        }
194    }
195
196    /// Try to "AND" (`&&`) two values types.
197    ///
198    /// A lhs or rhs value of `Null` returns `false`.
199    fn try_and(self, rhs: Self) -> Result<Self, ValueError> {
200        let err = || ValueError::And(self.kind(), rhs.kind());
201
202        let value = match self {
203            Value::Null => false.into(),
204            Value::Boolean(left) => match rhs {
205                Value::Null => false.into(),
206                Value::Boolean(right) => (left && right).into(),
207                _ => return Err(err()),
208            },
209            _ => return Err(err()),
210        };
211
212        Ok(value)
213    }
214
215    /// Similar to [`std::ops::Rem`], but fallible (e.g. `TryRem`).
216    fn try_rem(self, rhs: Self) -> Result<Self, ValueError> {
217        let err = || ValueError::Rem(self.kind(), rhs.kind());
218
219        let rhv_f64 = rhs.try_into_f64().map_err(|_| err())?;
220
221        if rhv_f64 == 0.0 {
222            return Err(ValueError::DivideByZero);
223        }
224
225        let value = match self {
226            Value::Integer(lhv) if rhs.is_float() => {
227                Value::from_f64_or_zero(lhv as f64 % rhs.try_float()?)
228            }
229            Value::Integer(left) => {
230                let right = rhs.try_into_i64().map_err(|_| err())?;
231                i64::wrapping_rem(left, right).into()
232            }
233            Value::Float(left) => {
234                let right = rhs.try_into_f64().map_err(|_| err())?;
235                left.rem(right).into()
236            }
237            _ => return Err(err()),
238        };
239
240        Ok(value)
241    }
242
243    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
244    fn try_gt(self, rhs: Self) -> Result<Self, ValueError> {
245        let err = || ValueError::Rem(self.kind(), rhs.kind());
246
247        let value = match self {
248            Value::Integer(lhv) if rhs.is_float() => (lhv as f64 > rhs.try_float()?).into(),
249            Value::Integer(lhv) => (lhv > rhs.try_into_i64().map_err(|_| err())?).into(),
250            Value::Float(lhv) => (lhv.into_inner() > rhs.try_into_f64().map_err(|_| err())?).into(),
251            Value::Bytes(lhv) => (lhv > rhs.try_bytes()?).into(),
252            Value::Timestamp(lhv) => (lhv > rhs.try_timestamp()?).into(),
253            _ => return Err(err()),
254        };
255
256        Ok(value)
257    }
258
259    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
260    fn try_ge(self, rhs: Self) -> Result<Self, ValueError> {
261        let err = || ValueError::Ge(self.kind(), rhs.kind());
262
263        let value = match self {
264            Value::Integer(lhv) if rhs.is_float() => (lhv as f64 >= rhs.try_float()?).into(),
265            Value::Integer(lhv) => (lhv >= rhs.try_into_i64().map_err(|_| err())?).into(),
266            Value::Float(lhv) => {
267                (lhv.into_inner() >= rhs.try_into_f64().map_err(|_| err())?).into()
268            }
269            Value::Bytes(lhv) => (lhv >= rhs.try_bytes()?).into(),
270            Value::Timestamp(lhv) => (lhv >= rhs.try_timestamp()?).into(),
271            _ => return Err(err()),
272        };
273
274        Ok(value)
275    }
276
277    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
278    fn try_lt(self, rhs: Self) -> Result<Self, ValueError> {
279        let err = || ValueError::Ge(self.kind(), rhs.kind());
280
281        let value = match self {
282            Value::Integer(lhv) if rhs.is_float() => ((lhv as f64) < rhs.try_float()?).into(),
283            Value::Integer(lhv) => (lhv < rhs.try_into_i64().map_err(|_| err())?).into(),
284            Value::Float(lhv) => (lhv.into_inner() < rhs.try_into_f64().map_err(|_| err())?).into(),
285            Value::Bytes(lhv) => (lhv < rhs.try_bytes()?).into(),
286            Value::Timestamp(lhv) => (lhv < rhs.try_timestamp()?).into(),
287            _ => return Err(err()),
288        };
289
290        Ok(value)
291    }
292
293    /// Similar to [`std::cmp::Ord`], but fallible (e.g. `TryOrd`).
294    fn try_le(self, rhs: Self) -> Result<Self, ValueError> {
295        let err = || ValueError::Ge(self.kind(), rhs.kind());
296
297        let value = match self {
298            Value::Integer(lhv) if rhs.is_float() => (lhv as f64 <= rhs.try_float()?).into(),
299            Value::Integer(lhv) => (lhv <= rhs.try_into_i64().map_err(|_| err())?).into(),
300            Value::Float(lhv) => {
301                (lhv.into_inner() <= rhs.try_into_f64().map_err(|_| err())?).into()
302            }
303            Value::Bytes(lhv) => (lhv <= rhs.try_bytes()?).into(),
304            Value::Timestamp(lhv) => (lhv <= rhs.try_timestamp()?).into(),
305            _ => return Err(err()),
306        };
307
308        Ok(value)
309    }
310
311    fn try_merge(self, rhs: Self) -> Result<Self, ValueError> {
312        let err = || ValueError::Merge(self.kind(), rhs.kind());
313
314        let value = match (&self, &rhs) {
315            (Value::Object(lhv), Value::Object(right)) => lhv
316                .iter()
317                .chain(right.iter())
318                .map(|(k, v)| (k.clone(), v.clone()))
319                .collect::<ObjectMap>()
320                .into(),
321            _ => return Err(err()),
322        };
323
324        Ok(value)
325    }
326
327    /// Similar to [`std::cmp::Eq`], but does a lossless comparison for integers
328    /// and floats.
329    fn eq_lossy(&self, rhs: &Self) -> bool {
330        use Value::{Float, Integer};
331
332        match self {
333            Integer(lhv) => rhs
334                .try_into_f64()
335                .map(|rhv| *lhv as f64 == rhv)
336                .unwrap_or(false),
337
338            Float(lhv) => rhs
339                .try_into_f64()
340                .map(|rhv| lhv.into_inner() == rhv)
341                .unwrap_or(false),
342
343            _ => self == rhs,
344        }
345    }
346}