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 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}