liquid_lib/stdlib/filters/
math.rs

1use std::convert::TryInto;
2
3use liquid_core::Expression;
4use liquid_core::Result;
5use liquid_core::Runtime;
6use liquid_core::{
7    Display_filter, Filter, FilterParameters, FilterReflection, FromFilterParameters, ParseFilter,
8};
9use liquid_core::{Value, ValueView};
10
11use crate::{invalid_argument, invalid_input};
12
13#[derive(Clone, ParseFilter, FilterReflection)]
14#[filter(
15    name = "abs",
16    description = "Returns the absolute value of a number.",
17    parsed(AbsFilter)
18)]
19pub struct Abs;
20
21#[derive(Debug, Default, Display_filter)]
22#[name = "abs"]
23struct AbsFilter;
24
25impl Filter for AbsFilter {
26    fn evaluate(&self, input: &dyn ValueView, _runtime: &dyn Runtime) -> Result<Value> {
27        let input = input
28            .as_scalar()
29            .ok_or_else(|| invalid_input("Number expected"))?;
30        input
31            .to_integer()
32            .map(|i| Value::scalar(i.abs()))
33            .or_else(|| input.to_float().map(|i| Value::scalar(i.abs())))
34            .ok_or_else(|| invalid_input("Number expected"))
35    }
36}
37
38#[derive(Debug, FilterParameters)]
39struct AtLeastArgs {
40    #[parameter(description = "The lower limit of the input.")]
41    min: Expression,
42}
43
44#[derive(Clone, ParseFilter, FilterReflection)]
45#[filter(
46    name = "at_least",
47    description = "Limits a number to a minimum value.",
48    parameters(AtLeastArgs),
49    parsed(AtLeastFilter)
50)]
51pub struct AtLeast;
52
53#[derive(Debug, FromFilterParameters, Display_filter)]
54#[name = "at_least"]
55struct AtLeastFilter {
56    #[parameters]
57    args: AtLeastArgs,
58}
59
60impl Filter for AtLeastFilter {
61    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
62        let args = self.args.evaluate(runtime)?;
63
64        let input = input
65            .as_scalar()
66            .ok_or_else(|| invalid_input("Number expected"))?;
67
68        let min = args
69            .min
70            .as_scalar()
71            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
72
73        let result = input
74            .to_integer()
75            .and_then(|i| min.to_integer().map(|min| Value::scalar(i.max(min))))
76            .or_else(|| {
77                input
78                    .to_float()
79                    .and_then(|i| min.to_float().map(|min| Value::scalar(i.max(min))))
80            })
81            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
82
83        Ok(result)
84    }
85}
86
87#[derive(Debug, FilterParameters)]
88struct AtMostArgs {
89    #[parameter(description = "The upper limit of the input.")]
90    max: Expression,
91}
92
93#[derive(Clone, ParseFilter, FilterReflection)]
94#[filter(
95    name = "at_most",
96    description = "Limits a number to a maximum value.",
97    parameters(AtMostArgs),
98    parsed(AtMostFilter)
99)]
100pub struct AtMost;
101
102#[derive(Debug, FromFilterParameters, Display_filter)]
103#[name = "at_most"]
104struct AtMostFilter {
105    #[parameters]
106    args: AtMostArgs,
107}
108
109impl Filter for AtMostFilter {
110    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
111        let args = self.args.evaluate(runtime)?;
112
113        let input = input
114            .as_scalar()
115            .ok_or_else(|| invalid_input("Number expected"))?;
116
117        let max = args
118            .max
119            .as_scalar()
120            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
121
122        let result = input
123            .to_integer()
124            .and_then(|i| max.to_integer().map(|max| Value::scalar(i.min(max))))
125            .or_else(|| {
126                input
127                    .to_float()
128                    .and_then(|i| max.to_float().map(|max| Value::scalar(i.min(max))))
129            })
130            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
131
132        Ok(result)
133    }
134}
135
136#[derive(Debug, FilterParameters)]
137struct PlusArgs {
138    #[parameter(description = "The number to sum to the input.")]
139    operand: Expression,
140}
141
142#[derive(Clone, ParseFilter, FilterReflection)]
143#[filter(
144    name = "plus",
145    description = "Sums a number with the given operand.",
146    parameters(PlusArgs),
147    parsed(PlusFilter)
148)]
149pub struct Plus;
150
151#[derive(Debug, FromFilterParameters, Display_filter)]
152#[name = "plus"]
153struct PlusFilter {
154    #[parameters]
155    args: PlusArgs,
156}
157
158impl Filter for PlusFilter {
159    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
160        let args = self.args.evaluate(runtime)?;
161
162        let input = input
163            .as_scalar()
164            .ok_or_else(|| invalid_input("Number expected"))?;
165
166        let operand = args
167            .operand
168            .as_scalar()
169            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
170
171        let result = input
172            .to_integer()
173            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i + o)))
174            .or_else(|| {
175                input
176                    .to_float()
177                    .and_then(|i| operand.to_float().map(|o| Value::scalar(i + o)))
178            })
179            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
180
181        Ok(result)
182    }
183}
184
185#[derive(Debug, FilterParameters)]
186struct MinusArgs {
187    #[parameter(description = "The number to subtract to the input.")]
188    operand: Expression,
189}
190
191#[derive(Clone, ParseFilter, FilterReflection)]
192#[filter(
193    name = "minus",
194    description = "Subtracts the given operand from a number.",
195    parameters(MinusArgs),
196    parsed(MinusFilter)
197)]
198pub struct Minus;
199
200#[derive(Debug, FromFilterParameters, Display_filter)]
201#[name = "minus"]
202struct MinusFilter {
203    #[parameters]
204    args: MinusArgs,
205}
206
207impl Filter for MinusFilter {
208    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
209        let args = self.args.evaluate(runtime)?;
210
211        let input = input
212            .as_scalar()
213            .ok_or_else(|| invalid_input("Number expected"))?;
214
215        let operand = args
216            .operand
217            .as_scalar()
218            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
219
220        let result = input
221            .to_integer()
222            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i - o)))
223            .or_else(|| {
224                input
225                    .to_float()
226                    .and_then(|i| operand.to_float().map(|o| Value::scalar(i - o)))
227            })
228            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
229
230        Ok(result)
231    }
232}
233
234#[derive(Debug, FilterParameters)]
235struct TimesArgs {
236    #[parameter(description = "The number to multiply the input by.")]
237    operand: Expression,
238}
239
240#[derive(Clone, ParseFilter, FilterReflection)]
241#[filter(
242    name = "times",
243    description = "Multiplies a number by the given operand.",
244    parameters(TimesArgs),
245    parsed(TimesFilter)
246)]
247pub struct Times;
248
249#[derive(Debug, FromFilterParameters, Display_filter)]
250#[name = "times"]
251struct TimesFilter {
252    #[parameters]
253    args: TimesArgs,
254}
255
256impl Filter for TimesFilter {
257    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
258        let args = self.args.evaluate(runtime)?;
259
260        let input = input
261            .as_scalar()
262            .ok_or_else(|| invalid_input("Number expected"))?;
263
264        let operand = args
265            .operand
266            .as_scalar()
267            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
268
269        let result = input
270            .to_integer()
271            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i * o)))
272            .or_else(|| {
273                input
274                    .to_float()
275                    .and_then(|i| operand.to_float().map(|o| Value::scalar(i * o)))
276            })
277            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
278
279        Ok(result)
280    }
281}
282
283#[derive(Debug, FilterParameters)]
284struct DividedByArgs {
285    #[parameter(description = "The number to divide the input by.")]
286    operand: Expression,
287}
288
289#[derive(Clone, ParseFilter, FilterReflection)]
290#[filter(
291    name = "divided_by",
292    description = "Divides a number by the given operand.",
293    parameters(DividedByArgs),
294    parsed(DividedByFilter)
295)]
296pub struct DividedBy;
297
298#[derive(Debug, FromFilterParameters, Display_filter)]
299#[name = "divided_by"]
300struct DividedByFilter {
301    #[parameters]
302    args: DividedByArgs,
303}
304
305impl Filter for DividedByFilter {
306    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
307        let args = self.args.evaluate(runtime)?;
308
309        let input = input
310            .as_scalar()
311            .ok_or_else(|| invalid_input("Number expected"))?;
312
313        let operand = args
314            .operand
315            .as_scalar()
316            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
317
318        if let Some(o) = operand.to_integer() {
319            if o == 0 {
320                return Err(invalid_argument("operand", "Can't divide by zero"));
321            }
322        } else if let Some(o) = operand.to_float() {
323            if o == 0.0 {
324                return Err(invalid_argument("operand", "Can't divide by zero"));
325            }
326        }
327
328        let result = input
329            .to_integer()
330            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i / o)))
331            .or_else(|| {
332                input
333                    .to_float()
334                    .and_then(|i| operand.to_float().map(|o| Value::scalar(i / o)))
335            })
336            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
337
338        Ok(result)
339    }
340}
341
342#[derive(Debug, FilterParameters)]
343struct ModuloArgs {
344    #[parameter(description = "The modulo of the input. Must be a number.")]
345    operand: Expression,
346}
347
348#[derive(Clone, ParseFilter, FilterReflection)]
349#[filter(
350    name = "modulo",
351    description = "The remainder of a division operation of a number by the given operand.",
352    parameters(ModuloArgs),
353    parsed(ModuloFilter)
354)]
355pub struct Modulo;
356
357#[derive(Debug, FromFilterParameters, Display_filter)]
358#[name = "modulo"]
359struct ModuloFilter {
360    #[parameters]
361    args: ModuloArgs,
362}
363
364impl Filter for ModuloFilter {
365    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
366        let args = self.args.evaluate(runtime)?;
367
368        let input = input
369            .as_scalar()
370            .ok_or_else(|| invalid_input("Number expected"))?;
371
372        let operand = args
373            .operand
374            .as_scalar()
375            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
376
377        if let Some(o) = operand.to_integer() {
378            if o == 0 {
379                return Err(invalid_argument("operand", "Can't divide by zero"));
380            }
381        } else if let Some(o) = operand.to_float() {
382            if o == 0.0 {
383                return Err(invalid_argument("operand", "Can't divide by zero"));
384            }
385        }
386
387        let result = input
388            .to_integer()
389            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i % o)))
390            .or_else(|| {
391                input
392                    .to_float()
393                    .and_then(|i| operand.to_float().map(|o| Value::scalar(i % o)))
394            })
395            .ok_or_else(|| invalid_argument("operand", "Number expected"))?;
396
397        Ok(result)
398    }
399}
400
401#[derive(Debug, FilterParameters)]
402struct RoundArgs {
403    #[parameter(
404        description = "Number of decimal places. Defaults to 0 (nearest integer).",
405        arg_type = "integer"
406    )]
407    decimal_places: Option<Expression>,
408}
409
410#[derive(Clone, ParseFilter, FilterReflection)]
411#[filter(
412    name = "round",
413    description = "Rounds an input number to the nearest integer or, if a number is specified as an argument, to that number of decimal places.",
414    parameters(RoundArgs),
415    parsed(RoundFilter)
416)]
417pub struct Round;
418
419#[derive(Debug, FromFilterParameters, Display_filter)]
420#[name = "round"]
421struct RoundFilter {
422    #[parameters]
423    args: RoundArgs,
424}
425
426impl Filter for RoundFilter {
427    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
428        let args = self.args.evaluate(runtime)?;
429
430        let n = args.decimal_places.unwrap_or(0);
431
432        let input = input
433            .as_scalar()
434            .and_then(|s| s.to_float())
435            .ok_or_else(|| invalid_input("Number expected"))?;
436
437        match n.cmp(&0) {
438            std::cmp::Ordering::Equal => Ok(Value::scalar(input.round() as i64)),
439            std::cmp::Ordering::Less => Ok(Value::scalar(input.round() as i64)),
440            _ => {
441                let multiplier = 10.0_f64.powi(
442                    n.try_into()
443                        .map_err(|_| invalid_input("decimal-places was too large"))?,
444                );
445                Ok(Value::scalar((input * multiplier).round() / multiplier))
446            }
447        }
448    }
449}
450
451#[derive(Clone, ParseFilter, FilterReflection)]
452#[filter(
453    name = "ceil",
454    description = "Rounds the input up to the nearest whole number.",
455    parsed(CeilFilter)
456)]
457pub struct Ceil;
458
459#[derive(Debug, Default, Display_filter)]
460#[name = "ceil"]
461struct CeilFilter;
462
463impl Filter for CeilFilter {
464    fn evaluate(&self, input: &dyn ValueView, _runtime: &dyn Runtime) -> Result<Value> {
465        let n = input
466            .as_scalar()
467            .and_then(|s| s.to_float())
468            .ok_or_else(|| invalid_input("Number expected"))?;
469        Ok(Value::scalar(n.ceil() as i64))
470    }
471}
472
473#[derive(Clone, ParseFilter, FilterReflection)]
474#[filter(
475    name = "floor",
476    description = "Rounds a number down to the nearest whole number.",
477    parsed(FloorFilter)
478)]
479pub struct Floor;
480
481#[derive(Debug, Default, Display_filter)]
482#[name = "floor"]
483struct FloorFilter;
484
485impl Filter for FloorFilter {
486    fn evaluate(&self, input: &dyn ValueView, _runtime: &dyn Runtime) -> Result<Value> {
487        let n = input
488            .as_scalar()
489            .and_then(|s| s.to_float())
490            .ok_or_else(|| invalid_input("Number expected"))?;
491        Ok(Value::scalar(n.floor() as i64))
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498
499    #[test]
500    fn unit_abs() {
501        assert_eq!(
502            liquid_core::call_filter!(Abs, -1f64).unwrap(),
503            Value::scalar(1f64)
504        );
505    }
506
507    #[test]
508    fn unit_abs_positive_in_string() {
509        assert_eq!(
510            liquid_core::call_filter!(Abs, "42").unwrap(),
511            Value::scalar(42f64)
512        );
513    }
514
515    #[test]
516    fn unit_abs_not_number_or_string() {
517        liquid_core::call_filter!(Abs, true).unwrap_err();
518    }
519
520    #[test]
521    fn unit_abs_one_argument() {
522        liquid_core::call_filter!(Abs, -1f64, 0f64).unwrap_err();
523    }
524
525    #[test]
526    fn unit_abs_shopify_liquid() {
527        // Three tests from https://shopify.github.io/liquid/filters/abs/
528        assert_eq!(
529            liquid_core::call_filter!(Abs, -17f64).unwrap(),
530            Value::scalar(17f64)
531        );
532        assert_eq!(
533            liquid_core::call_filter!(Abs, 4f64).unwrap(),
534            Value::scalar(4f64)
535        );
536        assert_eq!(
537            liquid_core::call_filter!(Abs, "-19.86").unwrap(),
538            Value::scalar(19.86f64)
539        );
540    }
541    #[test]
542    fn unit_at_least() {
543        assert_eq!(
544            liquid_core::call_filter!(AtLeast, 4f64, 5f64).unwrap(),
545            Value::scalar(5f64)
546        );
547        assert_eq!(
548            liquid_core::call_filter!(AtLeast, 4f64, 3f64).unwrap(),
549            Value::scalar(4f64)
550        );
551        assert_eq!(
552            liquid_core::call_filter!(AtLeast, 21.5, 2.25).unwrap(),
553            Value::scalar(21.5)
554        );
555        assert_eq!(
556            liquid_core::call_filter!(AtLeast, 21.5, 42.25).unwrap(),
557            Value::scalar(42.25)
558        );
559    }
560    #[test]
561    fn unit_at_most() {
562        assert_eq!(
563            liquid_core::call_filter!(AtMost, 4f64, 5f64).unwrap(),
564            Value::scalar(4f64)
565        );
566        assert_eq!(
567            liquid_core::call_filter!(AtMost, 4f64, 3f64).unwrap(),
568            Value::scalar(3f64)
569        );
570        assert_eq!(
571            liquid_core::call_filter!(AtMost, 21.5, 2.25).unwrap(),
572            Value::scalar(2.25)
573        );
574        assert_eq!(
575            liquid_core::call_filter!(AtMost, 21.5, 42.25).unwrap(),
576            Value::scalar(21.5)
577        );
578    }
579    #[test]
580    fn unit_plus() {
581        assert_eq!(
582            liquid_core::call_filter!(Plus, 2f64, 1f64).unwrap(),
583            Value::scalar(3f64)
584        );
585        assert_eq!(
586            liquid_core::call_filter!(Plus, 21.5, 2.25).unwrap(),
587            Value::scalar(23.75)
588        );
589    }
590
591    #[test]
592    fn unit_minus() {
593        assert_eq!(
594            liquid_core::call_filter!(Minus, 2f64, 1f64).unwrap(),
595            Value::scalar(1f64)
596        );
597        assert_eq!(
598            liquid_core::call_filter!(Minus, 21.5, 1.25).unwrap(),
599            Value::scalar(20.25)
600        );
601    }
602
603    #[test]
604    fn unit_times() {
605        assert_eq!(
606            liquid_core::call_filter!(Times, 2f64, 3f64).unwrap(),
607            Value::scalar(6f64)
608        );
609        assert_eq!(
610            liquid_core::call_filter!(Times, 8.5, 0.5).unwrap(),
611            Value::scalar(4.25)
612        );
613        liquid_core::call_filter!(Times, true, 8.5).unwrap_err();
614        liquid_core::call_filter!(Times, 2.5, true).unwrap_err();
615        liquid_core::call_filter!(Times, 2.5).unwrap_err();
616    }
617
618    #[test]
619    fn unit_modulo() {
620        assert_eq!(
621            liquid_core::call_filter!(Modulo, 3_f64, 2_f64).unwrap(),
622            Value::scalar(1_f64)
623        );
624        assert_eq!(
625            liquid_core::call_filter!(Modulo, 3_f64, 3.0).unwrap(),
626            Value::scalar(0_f64)
627        );
628        assert_eq!(
629            liquid_core::call_filter!(Modulo, 24_f64, 7_f64).unwrap(),
630            Value::scalar(3_f64)
631        );
632        assert_eq!(
633            liquid_core::call_filter!(Modulo, 183.357, 12_f64).unwrap(),
634            Value::scalar(3.3569999999999993)
635        );
636    }
637
638    #[test]
639    fn unit_divided_by() {
640        assert_eq!(
641            liquid_core::call_filter!(DividedBy, 4f64, 2f64).unwrap(),
642            Value::scalar(2f64)
643        );
644        assert_eq!(
645            liquid_core::call_filter!(DividedBy, 5f64, 2f64).unwrap(),
646            Value::scalar(2.5f64)
647        );
648        liquid_core::call_filter!(DividedBy, true, 8.5).unwrap_err();
649        liquid_core::call_filter!(DividedBy, 2.5, true).unwrap_err();
650        liquid_core::call_filter!(DividedBy, 2.5).unwrap_err();
651    }
652
653    #[test]
654    fn unit_ceil() {
655        assert_eq!(
656            liquid_core::call_filter!(Ceil, 1.1f64).unwrap(),
657            Value::scalar(2f64)
658        );
659        assert_eq!(
660            liquid_core::call_filter!(Ceil, 1f64).unwrap(),
661            Value::scalar(1f64)
662        );
663        liquid_core::call_filter!(Ceil, true).unwrap_err();
664    }
665
666    #[test]
667    fn unit_floor() {
668        assert_eq!(
669            liquid_core::call_filter!(Floor, 1.1f64).unwrap(),
670            Value::scalar(1f64)
671        );
672        assert_eq!(
673            liquid_core::call_filter!(Floor, 1f64).unwrap(),
674            Value::scalar(1f64)
675        );
676        liquid_core::call_filter!(Floor, true).unwrap_err();
677    }
678
679    #[test]
680    fn unit_round() {
681        assert_eq!(
682            liquid_core::call_filter!(Round, 1.1f64).unwrap(),
683            Value::scalar(1i64)
684        );
685        assert_eq!(
686            liquid_core::call_filter!(Round, 1.5f64).unwrap(),
687            Value::scalar(2i64)
688        );
689        assert_eq!(
690            liquid_core::call_filter!(Round, 2f64).unwrap(),
691            Value::scalar(2i64)
692        );
693        liquid_core::call_filter!(Round, true).unwrap_err();
694    }
695
696    #[test]
697    fn unit_round_precision() {
698        assert_eq!(
699            liquid_core::call_filter!(Round, 1.1f64, 0i64).unwrap(),
700            Value::scalar(1f64)
701        );
702        assert_eq!(
703            liquid_core::call_filter!(Round, 1.5f64, 1i64).unwrap(),
704            Value::scalar(1.5f64)
705        );
706        assert_eq!(
707            liquid_core::call_filter!(Round, 1.23456f64, 3i64).unwrap(),
708            Value::scalar(1.235f64)
709        );
710    }
711}