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