1use std::collections::HashMap;
4
5use crate::ast::*;
6use crate::error::{Error, Result};
7use crate::value::{Value, html_escape, urlencode};
8
9pub type FilterFn =
12 Box<dyn Fn(Value, Vec<Value>) -> crate::error::Result<Value> + Send + Sync + 'static>;
13
14pub type LoaderFn = Box<dyn Fn(&str) -> crate::error::Result<String> + Send + Sync + 'static>;
41
42pub struct Engine {
72 pub strict: bool,
74 templates: HashMap<String, String>,
76 filters: HashMap<String, FilterFn>,
78 loader: Option<LoaderFn>,
80}
81
82impl Default for Engine {
83 fn default() -> Self {
84 Engine::new()
85 }
86}
87
88impl Engine {
89 pub fn new() -> Self {
90 Engine {
91 strict: false,
92 templates: HashMap::new(),
93 filters: HashMap::new(),
94 loader: None,
95 }
96 }
97
98 pub fn with_strict(mut self) -> Self {
100 self.strict = true;
101 self
102 }
103
104 pub fn register_filter<F>(mut self, name: impl Into<String>, f: F) -> Self
110 where
111 F: Fn(Value, Vec<Value>) -> crate::error::Result<Value> + Send + Sync + 'static,
112 {
113 self.filters.insert(name.into(), Box::new(f));
114 self
115 }
116
117 pub fn register_template(mut self, name: impl Into<String>, source: impl Into<String>) -> Self {
119 self.templates.insert(name.into(), source.into());
120 self
121 }
122
123 pub fn with_template_loader<F>(mut self, loader: F) -> Self
143 where
144 F: Fn(&str) -> crate::error::Result<String> + Send + Sync + 'static,
145 {
146 self.loader = Some(Box::new(loader));
147 self
148 }
149
150 pub fn render_name(&self, name: &str, context: HashMap<String, Value>) -> Result<String> {
169 let source = self.resolve_template(name)?;
170 self.render(&source, context)
171 }
172
173 pub(crate) fn resolve_template(&self, name: &str) -> Result<String> {
175 if let Some(src) = self.templates.get(name) {
176 return Ok(src.clone());
177 }
178 if let Some(ref loader) = self.loader {
179 return loader(name);
180 }
181 Err(Error::RenderError {
182 message: format!("Template '{}' not found in engine registry", name),
183 })
184 }
185
186 pub fn render(&self, source: &str, context: HashMap<String, Value>) -> Result<String> {
188 let tokens = crate::lexer::tokenize(source)?;
189 let template = crate::parser::parse(tokens)?;
190 let mut renderer = Renderer::new(self, context);
191 renderer.render_template(&template)
192 }
193
194 pub fn compile(&self, source: &str) -> Result<Template> {
196 let tokens = crate::lexer::tokenize(source)?;
197 crate::parser::parse(tokens)
198 }
199
200 pub fn render_template(
202 &self,
203 template: &Template,
204 context: HashMap<String, Value>,
205 ) -> Result<String> {
206 let mut renderer = Renderer::new(self, context);
207 renderer.render_template(template)
208 }
209}
210
211struct Renderer<'e> {
214 engine: &'e Engine,
215 scopes: Vec<HashMap<String, Value>>,
217 snippets: HashMap<String, SnippetBlock>,
219}
220
221impl<'e> Renderer<'e> {
222 fn new(engine: &'e Engine, root_context: HashMap<String, Value>) -> Self {
223 Renderer {
224 engine,
225 scopes: vec![root_context],
226 snippets: HashMap::new(),
227 }
228 }
229
230 fn lookup(&self, name: &str) -> Option<&Value> {
233 for scope in self.scopes.iter().rev() {
234 if let Some(v) = scope.get(name) {
235 return Some(v);
236 }
237 }
238 None
239 }
240
241 fn lookup_value(&self, name: &str) -> Result<Value> {
242 match self.lookup(name) {
243 Some(v) => Ok(v.clone()),
244 None if self.engine.strict => Err(Error::RenderError {
245 message: format!("Undefined variable '{}'", name),
246 }),
247 None => Ok(Value::Null),
248 }
249 }
250
251 fn push_scope(&mut self, scope: HashMap<String, Value>) {
252 self.scopes.push(scope);
253 }
254
255 fn pop_scope(&mut self) {
256 self.scopes.pop();
257 }
258
259 fn render_template(&mut self, template: &Template) -> Result<String> {
262 self.hoist_snippets(&template.nodes);
264 self.render_nodes(&template.nodes)
265 }
266
267 fn hoist_snippets(&mut self, nodes: &[Node]) {
268 for node in nodes {
269 if let Node::SnippetBlock(s) = node {
270 self.snippets.insert(s.name.clone(), s.clone());
271 }
272 }
273 }
274
275 fn render_nodes(&mut self, nodes: &[Node]) -> Result<String> {
276 let mut out = String::new();
277 for node in nodes {
278 out.push_str(&self.render_node(node)?);
279 }
280 Ok(out)
281 }
282
283 fn render_node(&mut self, node: &Node) -> Result<String> {
284 match node {
285 Node::RawText(t) => Ok(t.clone()),
286 Node::Comment(_) => Ok(String::new()),
287 Node::ExprTag(t) => {
288 let val = self.eval_expr(&t.expr)?;
289 Ok(val.html_escaped())
290 }
291 Node::HtmlTag(t) => {
292 let val = self.eval_expr(&t.expr)?;
293 Ok(val.to_display_string())
294 }
295 Node::IfBlock(b) => self.render_if(b),
296 Node::EachBlock(b) => self.render_each(b),
297 Node::SnippetBlock(_) => Ok(String::new()), Node::RawBlock(content) => Ok(content.clone()),
299 Node::RenderTag(t) => self.render_render_tag(t),
300 Node::ConstTag(t) => {
301 let val = self.eval_expr(&t.expr)?;
302 self.scopes.last_mut().unwrap().insert(t.name.clone(), val);
304 Ok(String::new())
305 }
306 Node::IncludeTag(t) => self.render_include(t),
307 Node::DebugTag(t) => self.render_debug(t),
308 }
309 }
310
311 fn render_if(&mut self, block: &IfBlock) -> Result<String> {
312 for branch in &block.branches {
313 let cond = self.eval_expr(&branch.condition)?;
314 if cond.is_truthy() {
315 return self.render_nodes(&branch.body);
316 }
317 }
318 if let Some(else_body) = &block.else_body {
319 return self.render_nodes(else_body);
320 }
321 Ok(String::new())
322 }
323
324 fn render_each(&mut self, block: &EachBlock) -> Result<String> {
325 let iterable = self.eval_expr(&block.iterable)?;
326 let items = match iterable {
327 Value::Array(arr) => arr,
328 Value::Null => Vec::new(),
329 other => {
330 return Err(Error::RenderError {
331 message: format!("{{#each}} expects an array, got {}", other.type_name()),
332 });
333 }
334 };
335
336 if items.is_empty() {
337 if let Some(else_body) = &block.else_body {
338 return self.render_nodes(else_body);
339 }
340 return Ok(String::new());
341 }
342
343 let mut out = String::new();
344 for (i, item) in items.iter().enumerate() {
345 let mut scope = HashMap::new();
346 match &block.pattern {
348 Pattern::Ident(name) => {
349 scope.insert(name.clone(), item.clone());
350 }
351 Pattern::Destructure(keys) => {
352 if let Value::Object(map) = item {
353 for key in keys {
354 let val = map.get(key).cloned().unwrap_or(Value::Null);
355 scope.insert(key.clone(), val);
356 }
357 } else {
358 return Err(Error::RenderError {
359 message: format!(
360 "Destructuring pattern requires an object, got {}",
361 item.type_name()
362 ),
363 });
364 }
365 }
366 }
367 if let Some(idx_name) = &block.index_binding {
368 scope.insert(idx_name.clone(), Value::Int(i as i64));
369 }
370 self.push_scope(scope);
371 out.push_str(&self.render_nodes(&block.body)?);
372 self.pop_scope();
373 }
374 Ok(out)
375 }
376
377 fn render_render_tag(&mut self, tag: &RenderTag) -> Result<String> {
378 let snippet = self
379 .snippets
380 .get(&tag.name)
381 .cloned()
382 .ok_or_else(|| Error::RenderError {
383 message: format!("Unknown snippet '{}'", tag.name),
384 })?;
385 if snippet.params.len() != tag.args.len() {
386 return Err(Error::RenderError {
387 message: format!(
388 "Snippet '{}' expects {} argument(s), got {}",
389 tag.name,
390 snippet.params.len(),
391 tag.args.len()
392 ),
393 });
394 }
395 let mut arg_values = Vec::with_capacity(tag.args.len());
397 for arg in &tag.args {
398 arg_values.push(self.eval_expr(arg)?);
399 }
400 let mut scope = HashMap::new();
401 for (name, val) in snippet.params.iter().zip(arg_values) {
402 scope.insert(name.clone(), val);
403 }
404 self.push_scope(scope);
405 let result = self.render_nodes(&snippet.body.clone());
406 self.pop_scope();
407 result
408 }
409
410 fn render_include(&mut self, tag: &IncludeTag) -> Result<String> {
411 let source = self.engine.resolve_template(&tag.path)?;
412 let ctx: HashMap<String, Value> = self
414 .scopes
415 .iter()
416 .flat_map(|s| s.iter().map(|(k, v)| (k.clone(), v.clone())))
417 .collect();
418 self.engine.render(&source, ctx)
419 }
420
421 fn render_debug(&self, tag: &DebugTag) -> Result<String> {
422 let _ = tag;
425 Ok(String::new())
426 }
427
428 fn eval_expr(&mut self, expr: &Expr) -> Result<Value> {
431 match expr {
432 Expr::Null => Ok(Value::Null),
433 Expr::Bool(b) => Ok(Value::Bool(*b)),
434 Expr::Int(i) => Ok(Value::Int(*i)),
435 Expr::Float(f) => Ok(Value::Float(*f)),
436 Expr::String(s) => Ok(Value::String(s.clone())),
437 Expr::Array(elements) => {
438 let mut arr = Vec::with_capacity(elements.len());
439 for e in elements {
440 arr.push(self.eval_expr(e)?);
441 }
442 Ok(Value::Array(arr))
443 }
444 Expr::Ident(name) => self.lookup_value(name),
445 Expr::MemberAccess { object, property } => {
446 let obj = self.eval_expr(object)?;
447 self.get_property(&obj, property)
448 }
449 Expr::IndexAccess { object, index } => {
450 let obj = self.eval_expr(object)?;
451 let idx = self.eval_expr(index)?;
452 self.get_index(&obj, &idx)
453 }
454 Expr::Filter { expr, filters } => {
455 let mut val = self.eval_expr(expr)?;
456 for f in filters {
457 let mut arg_vals = Vec::with_capacity(f.args.len());
458 for a in &f.args {
459 arg_vals.push(self.eval_expr(a)?);
460 }
461 val = if let Some(custom) = self.engine.filters.get(f.name.as_str()) {
462 custom(val, arg_vals)?
463 } else {
464 apply_filter(val, &f.name, arg_vals)?
465 };
466 }
467 Ok(val)
468 }
469 Expr::Ternary {
470 condition,
471 consequent,
472 alternate,
473 } => {
474 let cond = self.eval_expr(condition)?;
475 if cond.is_truthy() {
476 self.eval_expr(consequent)
477 } else {
478 self.eval_expr(alternate)
479 }
480 }
481 Expr::Binary { op, left, right } => self.eval_binary(op, left, right),
482 Expr::Unary { op, operand } => self.eval_unary(op, operand),
483 Expr::Test {
484 expr,
485 negated,
486 test_name,
487 } => {
488 let val = self.eval_expr(expr)?;
489 let result = eval_test(&val, test_name, self)?;
490 Ok(Value::Bool(if *negated { !result } else { result }))
491 }
492 Expr::Membership {
493 expr,
494 negated,
495 collection,
496 } => {
497 let val = self.eval_expr(expr)?;
498 let coll = self.eval_expr(collection)?;
499 let member = eval_membership(&val, &coll)?;
500 Ok(Value::Bool(if *negated { !member } else { member }))
501 }
502 }
503 }
504
505 fn get_property(&self, obj: &Value, prop: &str) -> Result<Value> {
506 match obj {
507 Value::Object(map) => Ok(map.get(prop).cloned().unwrap_or(Value::Null)).and_then(|v| {
508 if self.engine.strict
509 && !obj.is_null()
510 && let Value::Object(map) = obj
511 && !map.contains_key(prop)
512 {
513 return Err(Error::RenderError {
514 message: format!("Property '{}' not found on object", prop),
515 });
516 }
517 Ok(v)
518 }),
519 Value::Null => {
520 if self.engine.strict {
521 Err(Error::RenderError {
522 message: format!("Cannot access property '{}' on null", prop),
523 })
524 } else {
525 Ok(Value::Null)
526 }
527 }
528 other => {
529 if self.engine.strict {
530 Err(Error::RenderError {
531 message: format!(
532 "Cannot access property '{}' on {}",
533 prop,
534 other.type_name()
535 ),
536 })
537 } else {
538 Ok(Value::Null)
539 }
540 }
541 }
542 }
543
544 fn get_index(&self, obj: &Value, idx: &Value) -> Result<Value> {
545 match obj {
546 Value::Array(arr) => {
547 let i = match idx {
548 Value::Int(i) => *i,
549 other => {
550 return Err(Error::RenderError {
551 message: format!(
552 "Array index must be an integer, got {}",
553 other.type_name()
554 ),
555 });
556 }
557 };
558 let len = arr.len() as i64;
559 let i = if i < 0 { len + i } else { i };
560 if i < 0 || i >= len {
561 if self.engine.strict {
562 Err(Error::RenderError {
563 message: format!("Array index {} out of bounds (len {})", i, len),
564 })
565 } else {
566 Ok(Value::Null)
567 }
568 } else {
569 Ok(arr[i as usize].clone())
570 }
571 }
572 Value::Object(map) => {
573 let key = match idx {
574 Value::String(s) => s.clone(),
575 other => other.to_display_string(),
576 };
577 Ok(map.get(&key).cloned().unwrap_or(Value::Null))
578 }
579 Value::Null => {
580 if self.engine.strict {
581 Err(Error::RenderError {
582 message: "Cannot index into null".to_string(),
583 })
584 } else {
585 Ok(Value::Null)
586 }
587 }
588 other => Err(Error::RenderError {
589 message: format!("Cannot index into {}", other.type_name()),
590 }),
591 }
592 }
593
594 fn eval_binary(&mut self, op: &BinaryOp, left: &Expr, right: &Expr) -> Result<Value> {
595 match op {
597 BinaryOp::Or => {
598 let l = self.eval_expr(left)?;
599 if l.is_truthy() {
600 return Ok(l);
601 }
602 return self.eval_expr(right);
603 }
604 BinaryOp::And => {
605 let l = self.eval_expr(left)?;
606 if !l.is_truthy() {
607 return Ok(l);
608 }
609 return self.eval_expr(right);
610 }
611 BinaryOp::NullCoalesce => {
612 let l = self.eval_expr(left)?;
613 if !l.is_null() {
614 return Ok(l);
615 }
616 return self.eval_expr(right);
617 }
618 _ => {}
619 }
620
621 let l = self.eval_expr(left)?;
622 let r = self.eval_expr(right)?;
623
624 match op {
625 BinaryOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
626 BinaryOp::Neq => Ok(Value::Bool(!values_equal(&l, &r))),
627 BinaryOp::Lt => {
628 compare_values(&l, &r).map(|o| Value::Bool(o == std::cmp::Ordering::Less))
629 }
630 BinaryOp::Gt => {
631 compare_values(&l, &r).map(|o| Value::Bool(o == std::cmp::Ordering::Greater))
632 }
633 BinaryOp::Lte => {
634 compare_values(&l, &r).map(|o| Value::Bool(o != std::cmp::Ordering::Greater))
635 }
636 BinaryOp::Gte => {
637 compare_values(&l, &r).map(|o| Value::Bool(o != std::cmp::Ordering::Less))
638 }
639 BinaryOp::Add => match (&l, &r) {
640 (Value::String(_), _) | (_, Value::String(_)) => Ok(Value::String(
642 l.to_display_string() + &r.to_display_string(),
643 )),
644 _ => numeric_op(&l, &r, |a, b| a + b, |a, b| a + b),
646 },
647 BinaryOp::Sub => numeric_op(&l, &r, |a, b| a - b, |a, b| a - b),
648 BinaryOp::Mul => numeric_op(&l, &r, |a, b| a * b, |a, b| a * b),
649 BinaryOp::Div => {
650 let is_zero =
651 matches!(&r, Value::Int(0)) || matches!(&r, Value::Float(f) if *f == 0.0);
652 if is_zero {
653 Err(Error::RenderError {
654 message: "Division by zero".to_string(),
655 })
656 } else {
657 numeric_op(&l, &r, |a, b| a / b, |a, b| a / b)
658 }
659 }
660 BinaryOp::Mod => numeric_op(&l, &r, |a, b| a % b, |a, b| a % b),
661 BinaryOp::Or | BinaryOp::And | BinaryOp::NullCoalesce => unreachable!(),
662 }
663 }
664
665 fn eval_unary(&mut self, op: &UnaryOp, operand: &Expr) -> Result<Value> {
666 let val = self.eval_expr(operand)?;
667 match op {
668 UnaryOp::Not => Ok(Value::Bool(!val.is_truthy())),
669 UnaryOp::Neg => match val {
670 Value::Int(i) => Ok(Value::Int(-i)),
671 Value::Float(f) => Ok(Value::Float(-f)),
672 other => Err(Error::RenderError {
673 message: format!("Cannot negate {}", other.type_name()),
674 }),
675 },
676 }
677 }
678}
679
680fn eval_test(val: &Value, test_name: &str, renderer: &Renderer) -> Result<bool> {
683 match test_name {
684 "defined" => Ok(!matches!(val, Value::Null)),
685 "undefined" => Ok(matches!(val, Value::Null)),
686 "none" => Ok(matches!(val, Value::Null)),
687 "odd" => match val {
688 Value::Int(i) => Ok(i % 2 != 0),
689 other => Err(Error::RenderError {
690 message: format!("Test 'odd' requires a number, got {}", other.type_name()),
691 }),
692 },
693 "even" => match val {
694 Value::Int(i) => Ok(i % 2 == 0),
695 other => Err(Error::RenderError {
696 message: format!("Test 'even' requires a number, got {}", other.type_name()),
697 }),
698 },
699 "empty" => Ok(val.is_empty()),
700 "truthy" => Ok(val.is_truthy()),
701 "falsy" => Ok(!val.is_truthy()),
702 "string" => Ok(matches!(val, Value::String(_))),
703 "number" => Ok(matches!(val, Value::Int(_) | Value::Float(_))),
704 "iterable" => Ok(matches!(val, Value::Array(_))),
705 unknown => {
706 if renderer.engine.strict {
707 Err(Error::RenderError {
708 message: format!("Unknown test '{}'", unknown),
709 })
710 } else {
711 Ok(false)
712 }
713 }
714 }
715}
716
717fn eval_membership(val: &Value, collection: &Value) -> Result<bool> {
720 match collection {
721 Value::Array(arr) => Ok(arr.contains(val)),
722 Value::Object(map) => {
723 let key = val.to_display_string();
724 Ok(map.contains_key(&key))
725 }
726 Value::String(haystack) => {
727 let needle = val.to_display_string();
728 Ok(haystack.contains(&needle[..]))
729 }
730 other => Err(Error::RenderError {
731 message: format!(
732 "'in' operator requires an array, object, or string, got {}",
733 other.type_name()
734 ),
735 }),
736 }
737}
738
739fn values_equal(a: &Value, b: &Value) -> bool {
742 match (a, b) {
743 (Value::Null, Value::Null) => true,
744 (Value::Bool(x), Value::Bool(y)) => x == y,
745 (Value::Int(x), Value::Int(y)) => x == y,
746 (Value::Float(x), Value::Float(y)) => x == y,
747 (Value::Int(x), Value::Float(y)) => (*x as f64) == *y,
748 (Value::Float(x), Value::Int(y)) => *x == (*y as f64),
749 (Value::String(x), Value::String(y)) => x == y,
750 _ => false,
751 }
752}
753
754fn compare_values(a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
755 match (a, b) {
756 (Value::Int(x), Value::Int(y)) => Ok(x.cmp(y)),
757 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).ok_or(Error::RenderError {
758 message: "Cannot compare NaN".to_string(),
759 }),
760 (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y).ok_or(Error::RenderError {
761 message: "Cannot compare NaN".to_string(),
762 }),
763 (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)).ok_or(Error::RenderError {
764 message: "Cannot compare NaN".to_string(),
765 }),
766 (Value::String(x), Value::String(y)) => Ok(x.cmp(y)),
767 _ => Err(Error::RenderError {
768 message: format!("Cannot compare {} and {}", a.type_name(), b.type_name()),
769 }),
770 }
771}
772
773fn numeric_op(
774 l: &Value,
775 r: &Value,
776 int_op: impl Fn(i64, i64) -> i64,
777 float_op: impl Fn(f64, f64) -> f64,
778) -> Result<Value> {
779 match (l, r) {
780 (Value::Int(a), Value::Int(b)) => Ok(Value::Int(int_op(*a, *b))),
781 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(float_op(*a, *b))),
782 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(float_op(*a as f64, *b))),
783 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(float_op(*a, *b as f64))),
784 _ => Err(Error::RenderError {
785 message: format!(
786 "Arithmetic requires numbers, got {} and {}",
787 l.type_name(),
788 r.type_name()
789 ),
790 }),
791 }
792}
793
794fn apply_filter(val: Value, name: &str, args: Vec<Value>) -> Result<Value> {
797 match name {
798 "upper" => {
800 let s = require_string(&val, "upper")?;
801 Ok(Value::String(s.to_uppercase()))
802 }
803 "lower" => {
804 let s = require_string(&val, "lower")?;
805 Ok(Value::String(s.to_lowercase()))
806 }
807 "capitalize" => {
808 let s = require_string(&val, "capitalize")?;
809 let mut chars = s.chars();
810 let out = match chars.next() {
811 None => String::new(),
812 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
813 };
814 Ok(Value::String(out))
815 }
816 "trim" => {
817 let s = require_string(&val, "trim")?;
818 Ok(Value::String(s.trim().to_string()))
819 }
820 "truncate" => {
821 let s = require_string(&val, "truncate")?;
822 let len = require_int_arg(&args, 0, "truncate")? as usize;
823 if s.chars().count() <= len {
824 Ok(Value::String(s.to_string()))
825 } else {
826 let truncated: String = s.chars().take(len.saturating_sub(3)).collect();
827 Ok(Value::String(truncated + "..."))
828 }
829 }
830 "replace" => {
831 let s = require_string(&val, "replace")?;
832 let from = require_string_arg(&args, 0, "replace")?;
833 let to = require_string_arg(&args, 1, "replace")?;
834 Ok(Value::String(s.replace(&from[..], &to[..])))
835 }
836 "split" => {
837 let s = require_string(&val, "split")?;
838 let sep = require_string_arg(&args, 0, "split")?;
839 let parts: Vec<Value> = s
840 .split(&sep[..])
841 .map(|p| Value::String(p.to_string()))
842 .collect();
843 Ok(Value::Array(parts))
844 }
845
846 "sort" => {
848 let mut arr = require_array(val, "sort")?;
849 arr.sort_by(|a, b| compare_values(a, b).unwrap_or(std::cmp::Ordering::Equal));
850 Ok(Value::Array(arr))
851 }
852 "reverse" => match val {
853 Value::Array(mut arr) => {
854 arr.reverse();
855 Ok(Value::Array(arr))
856 }
857 Value::String(s) => Ok(Value::String(s.chars().rev().collect())),
858 other => Err(Error::RenderError {
859 message: format!(
860 "Filter 'reverse' expects array or string, got {}",
861 other.type_name()
862 ),
863 }),
864 },
865 "join" => {
866 let arr = require_array(val, "join")?;
867 let sep = if args.is_empty() {
868 String::new()
869 } else {
870 require_string_arg(&args, 0, "join")?.to_string()
871 };
872 let parts: Vec<String> = arr.iter().map(|v| v.to_display_string()).collect();
873 Ok(Value::String(parts.join(&sep)))
874 }
875 "first" => {
876 let arr = require_array(val, "first")?;
877 Ok(arr.into_iter().next().unwrap_or(Value::Null))
878 }
879 "last" => {
880 let arr = require_array(val, "last")?;
881 Ok(arr.into_iter().next_back().unwrap_or(Value::Null))
882 }
883 "length" => {
884 let len = val.length().ok_or_else(|| Error::RenderError {
885 message: format!(
886 "Filter 'length' expects string, array, or object, got {}",
887 val.type_name()
888 ),
889 })?;
890 Ok(Value::Int(len as i64))
891 }
892
893 "default" => {
895 if val.is_null() {
896 Ok(args.into_iter().next().unwrap_or(Value::Null))
897 } else {
898 Ok(val)
899 }
900 }
901 "json" => Ok(Value::String(val.to_json_string())),
902 "round" => {
903 let precision = if args.is_empty() {
904 0usize
905 } else {
906 require_int_arg(&args, 0, "round")? as usize
907 };
908 match val {
909 Value::Int(i) => Ok(Value::Int(i)),
910 Value::Float(f) => {
911 let factor = 10f64.powi(precision as i32);
912 Ok(Value::Float((f * factor).round() / factor))
913 }
914 other => Err(Error::RenderError {
915 message: format!("Filter 'round' expects a number, got {}", other.type_name()),
916 }),
917 }
918 }
919
920 "urlencode" => {
922 let s = require_string(&val, "urlencode")?;
923 Ok(Value::String(urlencode(s)))
924 }
925 "escape" => {
926 let s = val.to_display_string();
927 Ok(Value::String(html_escape(&s)))
928 }
929
930 unknown => Err(Error::RenderError {
931 message: format!("Unknown filter '{}'", unknown),
932 }),
933 }
934}
935
936fn require_string<'a>(val: &'a Value, filter: &str) -> Result<&'a str> {
939 match val {
940 Value::String(s) => Ok(s),
941 other => Err(Error::RenderError {
942 message: format!(
943 "Filter '{}' expects a string, got {}",
944 filter,
945 other.type_name()
946 ),
947 }),
948 }
949}
950
951fn require_array(val: Value, filter: &str) -> Result<Vec<Value>> {
952 match val {
953 Value::Array(arr) => Ok(arr),
954 other => Err(Error::RenderError {
955 message: format!(
956 "Filter '{}' expects an array, got {}",
957 filter,
958 other.type_name()
959 ),
960 }),
961 }
962}
963
964fn require_string_arg(args: &[Value], idx: usize, filter: &str) -> Result<String> {
965 args.get(idx)
966 .and_then(|v| {
967 if let Value::String(s) = v {
968 Some(s.clone())
969 } else {
970 None
971 }
972 })
973 .ok_or_else(|| Error::RenderError {
974 message: format!("Filter '{}' argument {} must be a string", filter, idx + 1),
975 })
976}
977
978fn require_int_arg(args: &[Value], idx: usize, filter: &str) -> Result<i64> {
979 match args.get(idx) {
980 Some(Value::Int(i)) => Ok(*i),
981 Some(Value::Float(f)) => Ok(*f as i64),
982 _ => Err(Error::RenderError {
983 message: format!("Filter '{}' argument {} must be a number", filter, idx + 1),
984 }),
985 }
986}