1use std::borrow::{Borrow, Cow};
2use std::collections::{BTreeMap, VecDeque};
3use std::fmt;
4use std::rc::Rc;
5
6use serde_json::value::Value as Json;
7
8use crate::block::BlockContext;
9use crate::context::Context;
10use crate::error::RenderError;
11use crate::helpers::HelperDef;
12use crate::json::path::Path;
13use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
14use crate::output::{Output, StringOutput};
15use crate::registry::Registry;
16use crate::support;
17use crate::template::TemplateElement::*;
18use crate::template::{
19 BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
20 TemplateMapping,
21};
22use crate::{partial, RenderErrorReason};
23
24const HELPER_MISSING: &str = "helperMissing";
25const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
26
27#[derive(Clone, Debug)]
33pub struct RenderContext<'reg, 'rc> {
34 inner: Rc<RenderContextInner<'reg, 'rc>>,
35 blocks: VecDeque<BlockContext<'rc>>,
36 modified_context: Option<Rc<Context>>,
38}
39
40#[derive(Clone)]
41pub struct RenderContextInner<'reg: 'rc, 'rc> {
42 partials: BTreeMap<String, &'rc Template>,
43 partial_block_stack: VecDeque<&'reg Template>,
44 partial_block_depth: isize,
45 local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
46 current_template: Option<&'rc String>,
48 root_template: Option<&'reg String>,
50 disable_escape: bool,
51 indent_string: Option<&'reg String>,
52}
53
54impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
55 pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
57 let inner = Rc::new(RenderContextInner {
58 partials: BTreeMap::new(),
59 partial_block_stack: VecDeque::new(),
60 partial_block_depth: 0,
61 local_helpers: BTreeMap::new(),
62 current_template: None,
63 root_template,
64 disable_escape: false,
65 indent_string: None,
66 });
67
68 let mut blocks = VecDeque::with_capacity(5);
69 blocks.push_front(BlockContext::new());
70
71 let modified_context = None;
72 RenderContext {
73 inner,
74 blocks,
75 modified_context,
76 }
77 }
78
79 pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
80 let inner = self.inner.clone();
81
82 let mut blocks = VecDeque::with_capacity(2);
83 blocks.push_front(BlockContext::new());
84
85 let modified_context = self.modified_context.clone();
86
87 RenderContext {
88 inner,
89 blocks,
90 modified_context,
91 }
92 }
93
94 pub fn push_block(&mut self, block: BlockContext<'rc>) {
97 self.blocks.push_front(block);
98 }
99
100 pub fn pop_block(&mut self) {
103 self.blocks.pop_front();
104 }
105
106 pub(crate) fn clear_blocks(&mut self) {
107 self.blocks.clear();
108 }
109
110 pub fn block(&self) -> Option<&BlockContext<'rc>> {
112 self.blocks.front()
113 }
114
115 pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
118 self.blocks.front_mut()
119 }
120
121 fn inner(&self) -> &RenderContextInner<'reg, 'rc> {
122 self.inner.borrow()
123 }
124
125 fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> {
126 Rc::make_mut(&mut self.inner)
127 }
128
129 pub fn context(&self) -> Option<Rc<Context>> {
131 self.modified_context.clone()
132 }
133
134 pub fn set_context(&mut self, ctx: Context) {
138 self.modified_context = Some(Rc::new(ctx))
139 }
140
141 pub fn evaluate(
147 &self,
148 context: &'rc Context,
149 relative_path: &str,
150 ) -> Result<ScopedJson<'rc>, RenderError> {
151 let path = Path::parse(relative_path)?;
152 self.evaluate2(context, &path)
153 }
154
155 pub(crate) fn evaluate2(
156 &self,
157 context: &'rc Context,
158 path: &Path,
159 ) -> Result<ScopedJson<'rc>, RenderError> {
160 match path {
161 Path::Local((level, name, _)) => Ok(self
162 .get_local_var(*level, name)
163 .map(|v| ScopedJson::Derived(v.clone()))
164 .unwrap_or_else(|| ScopedJson::Missing)),
165 Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
166 }
167 }
168
169 pub fn get_partial(&self, name: &str) -> Option<&Template> {
171 if name == partial::PARTIAL_BLOCK {
172 return self
173 .inner()
174 .partial_block_stack
175 .get(self.inner().partial_block_depth as usize)
176 .copied();
177 }
178 self.inner().partials.get(name).copied()
179 }
180
181 pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
183 self.inner_mut().partials.insert(name, partial);
184 }
185
186 pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
187 self.inner_mut().partial_block_stack.push_front(partial);
188 }
189
190 pub(crate) fn pop_partial_block(&mut self) {
191 self.inner_mut().partial_block_stack.pop_front();
192 }
193
194 pub(crate) fn inc_partial_block_depth(&mut self) {
195 self.inner_mut().partial_block_depth += 1;
196 }
197
198 pub(crate) fn dec_partial_block_depth(&mut self) {
199 let depth = &mut self.inner_mut().partial_block_depth;
200 if *depth > 0 {
201 *depth -= 1;
202 }
203 }
204
205 pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) {
206 self.inner_mut().indent_string = indent;
207 }
208
209 #[inline]
210 pub(crate) fn get_indent_string(&self) -> Option<&'reg String> {
211 self.inner.indent_string
212 }
213
214 pub fn remove_partial(&mut self, name: &str) {
216 self.inner_mut().partials.remove(name);
217 }
218
219 fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
220 self.blocks
221 .get(level)
222 .and_then(|blk| blk.get_local_var(name))
223 }
224
225 pub fn is_current_template(&self, p: &str) -> bool {
227 self.inner()
228 .current_template
229 .map(|s| s == p)
230 .unwrap_or(false)
231 }
232
233 pub fn register_local_helper(
237 &mut self,
238 name: &str,
239 def: Box<dyn HelperDef + Send + Sync + 'rc>,
240 ) {
241 self.inner_mut()
242 .local_helpers
243 .insert(name.to_string(), def.into());
244 }
245
246 pub fn unregister_local_helper(&mut self, name: &str) {
248 self.inner_mut().local_helpers.remove(name);
249 }
250
251 pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
253 self.inner().local_helpers.get(name).cloned()
254 }
255
256 #[inline]
257 fn has_local_helper(&self, name: &str) -> bool {
258 self.inner.local_helpers.contains_key(name)
259 }
260
261 pub fn get_current_template_name(&self) -> Option<&'rc String> {
265 self.inner().current_template
266 }
267
268 pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
270 self.inner_mut().current_template = name;
271 }
272
273 pub fn get_root_template_name(&self) -> Option<&'reg String> {
276 self.inner().root_template
277 }
278
279 pub fn is_disable_escape(&self) -> bool {
281 self.inner().disable_escape
282 }
283
284 pub fn set_disable_escape(&mut self, disable: bool) {
287 self.inner_mut().disable_escape = disable
288 }
289}
290
291impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
293 f.debug_struct("RenderContextInner")
294 .field("partials", &self.partials)
295 .field("partial_block_stack", &self.partial_block_stack)
296 .field("partial_block_depth", &self.partial_block_depth)
297 .field("root_template", &self.root_template)
298 .field("current_template", &self.current_template)
299 .field("disable_escape", &self.disable_escape)
300 .finish()
301 }
302}
303
304#[derive(Debug, Clone)]
306pub struct Helper<'rc> {
307 name: Cow<'rc, str>,
308 params: Vec<PathAndJson<'rc>>,
309 hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
310 template: Option<&'rc Template>,
311 inverse: Option<&'rc Template>,
312 block_param: Option<&'rc BlockParam>,
313 block: bool,
314}
315
316impl<'reg: 'rc, 'rc> Helper<'rc> {
317 fn try_from_template(
318 ht: &'rc HelperTemplate,
319 registry: &'reg Registry<'reg>,
320 context: &'rc Context,
321 render_context: &mut RenderContext<'reg, 'rc>,
322 ) -> Result<Helper<'rc>, RenderError> {
323 let name = ht.name.expand_as_name(registry, context, render_context)?;
324 let mut pv = Vec::with_capacity(ht.params.len());
325 for p in &ht.params {
326 let r = p.expand(registry, context, render_context)?;
327 pv.push(r);
328 }
329
330 let mut hm = BTreeMap::new();
331 for (k, p) in &ht.hash {
332 let r = p.expand(registry, context, render_context)?;
333 hm.insert(k.as_ref(), r);
334 }
335
336 Ok(Helper {
337 name,
338 params: pv,
339 hash: hm,
340 template: ht.template.as_ref(),
341 inverse: ht.inverse.as_ref(),
342 block_param: ht.block_param.as_ref(),
343 block: ht.block,
344 })
345 }
346
347 pub fn name(&self) -> &str {
349 &self.name
350 }
351
352 pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
354 &self.params
355 }
356
357 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
376 self.params.get(idx)
377 }
378
379 pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
381 &self.hash
382 }
383
384 pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
403 self.hash.get(key)
404 }
405
406 pub fn template(&self) -> Option<&'rc Template> {
411 self.template
412 }
413
414 pub fn inverse(&self) -> Option<&'rc Template> {
416 self.inverse
417 }
418
419 pub fn is_block(&self) -> bool {
421 self.block
422 }
423
424 pub fn has_block_param(&self) -> bool {
426 self.block_param.is_some()
427 }
428
429 pub fn block_param(&self) -> Option<&'rc str> {
431 if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
432 Some(s)
433 } else {
434 None
435 }
436 }
437
438 pub fn block_param_pair(&self) -> Option<(&'rc str, &'rc str)> {
440 if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
441 self.block_param
442 {
443 Some((s1, s2))
444 } else {
445 None
446 }
447 }
448}
449
450#[derive(Debug)]
452pub struct Decorator<'rc> {
453 name: Cow<'rc, str>,
454 params: Vec<PathAndJson<'rc>>,
455 hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
456 template: Option<&'rc Template>,
457 indent: Option<&'rc String>,
458}
459
460impl<'reg: 'rc, 'rc> Decorator<'rc> {
461 fn try_from_template(
462 dt: &'rc DecoratorTemplate,
463 registry: &'reg Registry<'reg>,
464 context: &'rc Context,
465 render_context: &mut RenderContext<'reg, 'rc>,
466 ) -> Result<Decorator<'rc>, RenderError> {
467 let name = dt.name.expand_as_name(registry, context, render_context)?;
468
469 let mut pv = Vec::with_capacity(dt.params.len());
470 for p in &dt.params {
471 let r = p.expand(registry, context, render_context)?;
472 pv.push(r);
473 }
474
475 let mut hm = BTreeMap::new();
476 for (k, p) in &dt.hash {
477 let r = p.expand(registry, context, render_context)?;
478 hm.insert(k.as_ref(), r);
479 }
480
481 Ok(Decorator {
482 name,
483 params: pv,
484 hash: hm,
485 template: dt.template.as_ref(),
486 indent: dt.indent.as_ref(),
487 })
488 }
489
490 pub fn name(&self) -> &str {
492 self.name.as_ref()
493 }
494
495 pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
497 &self.params
498 }
499
500 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
502 self.params.get(idx)
503 }
504
505 pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
507 &self.hash
508 }
509
510 pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
512 self.hash.get(key)
513 }
514
515 pub fn template(&self) -> Option<&'rc Template> {
517 self.template
518 }
519
520 pub fn indent(&self) -> Option<&'rc String> {
521 self.indent
522 }
523}
524
525pub trait Renderable {
527 fn render<'reg: 'rc, 'rc>(
529 &'rc self,
530 registry: &'reg Registry<'reg>,
531 context: &'rc Context,
532 rc: &mut RenderContext<'reg, 'rc>,
533 out: &mut dyn Output,
534 ) -> Result<(), RenderError>;
535
536 fn renders<'reg: 'rc, 'rc>(
538 &'rc self,
539 registry: &'reg Registry<'reg>,
540 ctx: &'rc Context,
541 rc: &mut RenderContext<'reg, 'rc>,
542 ) -> Result<String, RenderError> {
543 let mut so = StringOutput::new();
544 self.render(registry, ctx, rc, &mut so)?;
545 so.into_string()
546 .map_err(|e| RenderErrorReason::from(e).into())
547 }
548}
549
550pub trait Evaluable {
552 fn eval<'reg: 'rc, 'rc>(
553 &'rc self,
554 registry: &'reg Registry<'reg>,
555 context: &'rc Context,
556 rc: &mut RenderContext<'reg, 'rc>,
557 ) -> Result<(), RenderError>;
558}
559
560#[inline]
561fn call_helper_for_value<'reg: 'rc, 'rc>(
562 hd: &dyn HelperDef,
563 ht: &Helper<'rc>,
564 r: &'reg Registry<'reg>,
565 ctx: &'rc Context,
566 rc: &mut RenderContext<'reg, 'rc>,
567) -> Result<PathAndJson<'rc>, RenderError> {
568 match hd.call_inner(ht, r, ctx, rc) {
569 Ok(result) => Ok(PathAndJson::new(None, result)),
570 Err(e) => {
571 if e.is_unimplemented() {
572 let mut so = StringOutput::new();
574
575 let disable_escape = rc.is_disable_escape();
578 rc.set_disable_escape(true);
579
580 hd.call(ht, r, ctx, rc, &mut so)?;
581 rc.set_disable_escape(disable_escape);
582
583 let string = so.into_string().map_err(RenderError::from)?;
584 Ok(PathAndJson::new(
585 None,
586 ScopedJson::Derived(Json::String(string)),
587 ))
588 } else {
589 Err(e)
590 }
591 }
592 }
593}
594
595impl Parameter {
596 pub fn expand_as_name<'reg: 'rc, 'rc>(
597 &'rc self,
598 registry: &'reg Registry<'reg>,
599 ctx: &'rc Context,
600 rc: &mut RenderContext<'reg, 'rc>,
601 ) -> Result<Cow<'rc, str>, RenderError> {
602 match self {
603 Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
604 Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
605 Parameter::Subexpression(_) => self
606 .expand(registry, ctx, rc)
607 .map(|v| v.value().render())
608 .map(Cow::Owned),
609 Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
610 }
611 }
612
613 pub fn expand<'reg: 'rc, 'rc>(
614 &'rc self,
615 registry: &'reg Registry<'reg>,
616 ctx: &'rc Context,
617 rc: &mut RenderContext<'reg, 'rc>,
618 ) -> Result<PathAndJson<'rc>, RenderError> {
619 match self {
620 Parameter::Name(ref name) => {
621 Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
623 }
624 Parameter::Path(ref path) => {
625 if let Some(rc_context) = rc.context() {
626 let result = rc.evaluate2(rc_context.borrow(), path)?;
627 Ok(PathAndJson::new(
628 Some(path.raw().to_owned()),
629 ScopedJson::Derived(result.as_json().clone()),
630 ))
631 } else {
632 let result = rc.evaluate2(ctx, path)?;
633 Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
634 }
635 }
636 Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
637 Parameter::Subexpression(ref t) => match *t.as_element() {
638 Expression(ref ht) => {
639 let name = ht.name.expand_as_name(registry, ctx, rc)?;
640
641 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
642 if let Some(ref d) = rc.get_local_helper(&name) {
643 call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
644 } else {
645 let mut helper = registry.get_or_load_helper(&name)?;
646
647 if helper.is_none() {
648 helper = registry.get_or_load_helper(if ht.block {
649 BLOCK_HELPER_MISSING
650 } else {
651 HELPER_MISSING
652 })?;
653 }
654
655 helper
656 .ok_or_else(|| {
657 RenderErrorReason::HelperNotFound(name.to_string()).into()
658 })
659 .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
660 }
661 }
662 _ => unreachable!(),
663 },
664 }
665 }
666}
667
668impl Renderable for Template {
669 fn render<'reg: 'rc, 'rc>(
670 &'rc self,
671 registry: &'reg Registry<'reg>,
672 ctx: &'rc Context,
673 rc: &mut RenderContext<'reg, 'rc>,
674 out: &mut dyn Output,
675 ) -> Result<(), RenderError> {
676 rc.set_current_template_name(self.name.as_ref());
677 let iter = self.elements.iter();
678
679 for (idx, t) in iter.enumerate() {
680 t.render(registry, ctx, rc, out).map_err(|mut e| {
681 if e.line_no.is_none() {
683 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
684 e.line_no = Some(line);
685 e.column_no = Some(col);
686 }
687 }
688
689 if e.template_name.is_none() {
690 e.template_name = self.name.clone();
691 }
692
693 e
694 })?;
695 }
696 Ok(())
697 }
698}
699
700impl Evaluable for Template {
701 fn eval<'reg: 'rc, 'rc>(
702 &'rc self,
703 registry: &'reg Registry<'reg>,
704 ctx: &'rc Context,
705 rc: &mut RenderContext<'reg, 'rc>,
706 ) -> Result<(), RenderError> {
707 let iter = self.elements.iter();
708
709 for (idx, t) in iter.enumerate() {
710 t.eval(registry, ctx, rc).map_err(|mut e| {
711 if e.line_no.is_none() {
712 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
713 e.line_no = Some(line);
714 e.column_no = Some(col);
715 }
716 }
717
718 e.template_name = self.name.clone();
719 e
720 })?;
721 }
722 Ok(())
723 }
724}
725
726fn helper_exists<'reg: 'rc, 'rc>(
727 name: &str,
728 reg: &Registry<'reg>,
729 rc: &RenderContext<'reg, 'rc>,
730) -> bool {
731 rc.has_local_helper(name) || reg.has_helper(name)
732}
733
734#[inline]
735fn render_helper<'reg: 'rc, 'rc>(
736 ht: &'rc HelperTemplate,
737 registry: &'reg Registry<'reg>,
738 ctx: &'rc Context,
739 rc: &mut RenderContext<'reg, 'rc>,
740 out: &mut dyn Output,
741) -> Result<(), RenderError> {
742 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
743 debug!(
744 "Rendering helper: {:?}, params: {:?}, hash: {:?}",
745 h.name(),
746 h.params(),
747 h.hash()
748 );
749 if let Some(ref d) = rc.get_local_helper(h.name()) {
750 d.call(&h, registry, ctx, rc, out)
751 } else {
752 let mut helper = registry.get_or_load_helper(h.name())?;
753
754 if helper.is_none() {
755 helper = registry.get_or_load_helper(if ht.block {
756 BLOCK_HELPER_MISSING
757 } else {
758 HELPER_MISSING
759 })?;
760 }
761
762 helper
763 .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
764 .and_then(|d| d.call(&h, registry, ctx, rc, out))
765 }
766}
767
768pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
769 if !rc.is_disable_escape() {
770 r.get_escape_fn()(&content)
771 } else {
772 content
773 }
774}
775
776#[inline]
777fn indent_aware_write(
778 v: &str,
779 rc: &RenderContext<'_, '_>,
780 out: &mut dyn Output,
781) -> Result<(), RenderError> {
782 if let Some(indent) = rc.get_indent_string() {
783 out.write(support::str::with_indent(v, indent).as_ref())?;
784 } else {
785 out.write(v.as_ref())?;
786 }
787 Ok(())
788}
789
790impl Renderable for TemplateElement {
791 fn render<'reg: 'rc, 'rc>(
792 &'rc self,
793 registry: &'reg Registry<'reg>,
794 ctx: &'rc Context,
795 rc: &mut RenderContext<'reg, 'rc>,
796 out: &mut dyn Output,
797 ) -> Result<(), RenderError> {
798 match self {
799 RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
800 Expression(ref ht) | HtmlExpression(ref ht) => {
801 let is_html_expression = matches!(self, HtmlExpression(_));
802 if is_html_expression {
803 rc.set_disable_escape(true);
804 }
805
806 let result = if ht.is_name_only() {
808 let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
809 if helper_exists(&helper_name, registry, rc) {
810 render_helper(ht, registry, ctx, rc, out)
811 } else {
812 debug!("Rendering value: {:?}", ht.name);
813 let context_json = ht.name.expand(registry, ctx, rc)?;
814 if context_json.is_value_missing() {
815 if registry.strict_mode() {
816 Err(RenderError::strict_error(context_json.relative_path()))
817 } else {
818 if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
820 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
821 hook.call(&h, registry, ctx, rc, out)
822 } else {
823 Ok(())
824 }
825 }
826 } else {
827 let rendered = context_json.value().render();
828 let output = do_escape(registry, rc, rendered);
829 indent_aware_write(output.as_ref(), rc, out)
830 }
831 }
832 } else {
833 render_helper(ht, registry, ctx, rc, out)
835 };
836
837 if is_html_expression {
838 rc.set_disable_escape(false);
839 }
840
841 result
842 }
843 HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
844 DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
845 PartialExpression(ref dt) | PartialBlock(ref dt) => {
846 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
847
848 partial::expand_partial(&di, registry, ctx, rc, out)
849 }
850 _ => Ok(()),
851 }
852 }
853}
854
855impl Evaluable for TemplateElement {
856 fn eval<'reg: 'rc, 'rc>(
857 &'rc self,
858 registry: &'reg Registry<'reg>,
859 ctx: &'rc Context,
860 rc: &mut RenderContext<'reg, 'rc>,
861 ) -> Result<(), RenderError> {
862 match *self {
863 DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
864 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
865 match registry.get_decorator(di.name()) {
866 Some(d) => d.call(&di, registry, ctx, rc),
867 None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
868 }
869 }
870 _ => Ok(()),
871 }
872 }
873}
874
875#[cfg(test)]
876mod test {
877 use std::collections::BTreeMap;
878
879 use super::{Helper, RenderContext, Renderable};
880 use crate::block::BlockContext;
881 use crate::context::Context;
882 use crate::error::RenderError;
883 use crate::json::path::Path;
884 use crate::json::value::JsonRender;
885 use crate::output::{Output, StringOutput};
886 use crate::registry::Registry;
887 use crate::template::TemplateElement::*;
888 use crate::template::{HelperTemplate, Template, TemplateElement};
889
890 #[test]
891 fn test_raw_string() {
892 let r = Registry::new();
893 let raw_string = RawString("<h1>hello world</h1>".to_string());
894
895 let mut out = StringOutput::new();
896 let ctx = Context::null();
897 {
898 let mut rc = RenderContext::new(None);
899 raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
900 }
901 assert_eq!(
902 out.into_string().unwrap(),
903 "<h1>hello world</h1>".to_string()
904 );
905 }
906
907 #[test]
908 fn test_expression() {
909 let r = Registry::new();
910 let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
911 &["hello"],
912 ))));
913
914 let mut out = StringOutput::new();
915 let mut m: BTreeMap<String, String> = BTreeMap::new();
916 let value = "<p></p>".to_string();
917 m.insert("hello".to_string(), value);
918 let ctx = Context::wraps(&m).unwrap();
919 {
920 let mut rc = RenderContext::new(None);
921 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
922 }
923
924 assert_eq!(
925 out.into_string().unwrap(),
926 "<p></p>".to_string()
927 );
928 }
929
930 #[test]
931 fn test_html_expression() {
932 let r = Registry::new();
933 let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
934 &["hello"],
935 ))));
936
937 let mut out = StringOutput::new();
938 let mut m: BTreeMap<String, String> = BTreeMap::new();
939 let value = "world";
940 m.insert("hello".to_string(), value.to_string());
941 let ctx = Context::wraps(&m).unwrap();
942 {
943 let mut rc = RenderContext::new(None);
944 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
945 }
946
947 assert_eq!(out.into_string().unwrap(), value.to_string());
948 }
949
950 #[test]
951 fn test_template() {
952 let r = Registry::new();
953 let mut out = StringOutput::new();
954 let mut m: BTreeMap<String, String> = BTreeMap::new();
955 let value = "world".to_string();
956 m.insert("hello".to_string(), value);
957 let ctx = Context::wraps(&m).unwrap();
958
959 let elements: Vec<TemplateElement> = vec![
960 RawString("<h1>".to_string()),
961 Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
962 &["hello"],
963 )))),
964 RawString("</h1>".to_string()),
965 Comment("".to_string()),
966 ];
967
968 let template = Template {
969 elements,
970 name: None,
971 mapping: Vec::new(),
972 span: (0,0)
973 };
974
975 {
976 let mut rc = RenderContext::new(None);
977 template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
978 }
979
980 assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
981 }
982
983 #[test]
984 fn test_render_context_promotion_and_demotion() {
985 use crate::json::value::to_json;
986 let mut render_context = RenderContext::new(None);
987 let mut block = BlockContext::new();
988
989 block.set_local_var("index", to_json(0));
990 render_context.push_block(block);
991
992 render_context.push_block(BlockContext::new());
993 assert_eq!(
994 render_context.get_local_var(1, "index").unwrap(),
995 &to_json(0)
996 );
997
998 render_context.pop_block();
999
1000 assert_eq!(
1001 render_context.get_local_var(0, "index").unwrap(),
1002 &to_json(0)
1003 );
1004 }
1005
1006 #[test]
1007 fn test_render_subexpression_issue_115() {
1008 use crate::support::str::StringWriter;
1009
1010 let mut r = Registry::new();
1011 r.register_helper(
1012 "format",
1013 Box::new(
1014 |h: &Helper<'_>,
1015 _: &Registry<'_>,
1016 _: &Context,
1017 _: &mut RenderContext<'_, '_>,
1018 out: &mut dyn Output|
1019 -> Result<(), RenderError> {
1020 out.write(&h.param(0).unwrap().value().render())
1021 .map(|_| ())
1022 .map_err(RenderError::from)
1023 },
1024 ),
1025 );
1026
1027 let mut sw = StringWriter::new();
1028 let mut m: BTreeMap<String, String> = BTreeMap::new();
1029 m.insert("a".to_string(), "123".to_string());
1030
1031 {
1032 if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1033 panic!("{}", e);
1034 }
1035 }
1036
1037 assert_eq!(sw.into_string(), "123".to_string());
1038 }
1039
1040 #[test]
1041 fn test_render_error_line_no() {
1042 let mut r = Registry::new();
1043 let m: BTreeMap<String, String> = BTreeMap::new();
1044
1045 let name = "invalid_template";
1046 assert!(r
1047 .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}")
1048 .is_ok());
1049
1050 if let Err(e) = r.render(name, &m) {
1051 assert_eq!(e.line_no.unwrap(), 3);
1052 assert_eq!(e.column_no.unwrap(), 3);
1053 assert_eq!(e.template_name, Some(name.to_owned()));
1054 } else {
1055 panic!("Error expected");
1056 }
1057 }
1058
1059 #[test]
1060 fn test_partial_failback_render() {
1061 let mut r = Registry::new();
1062
1063 assert!(r
1064 .register_template_string("parent", "<html>{{> layout}}</html>")
1065 .is_ok());
1066 assert!(r
1067 .register_template_string(
1068 "child",
1069 "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1070 )
1071 .is_ok());
1072 assert!(r.register_template_string("seg", "1234").is_ok());
1073
1074 let r = r.render("child", &true).expect("should work");
1075 assert_eq!(r, "<html>content</html>");
1076 }
1077
1078 #[test]
1079 fn test_key_with_slash() {
1080 let mut r = Registry::new();
1081
1082 assert!(r
1083 .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1084 .is_ok());
1085
1086 let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1087
1088 assert_eq!(r, "/foo: bar\n");
1089 }
1090
1091 #[test]
1092 fn test_comment() {
1093 let r = Registry::new();
1094
1095 assert_eq!(
1096 r.render_template("Hello {{this}} {{! test me }}", &0)
1097 .unwrap(),
1098 "Hello 0 "
1099 );
1100 }
1101
1102 #[test]
1103 fn test_zero_args_heler() {
1104 let mut r = Registry::new();
1105
1106 r.register_helper(
1107 "name",
1108 Box::new(
1109 |_: &Helper<'_>,
1110 _: &Registry<'_>,
1111 _: &Context,
1112 _: &mut RenderContext<'_, '_>,
1113 out: &mut dyn Output|
1114 -> Result<(), RenderError> {
1115 out.write("N/A").map_err(Into::into)
1116 },
1117 ),
1118 );
1119
1120 r.register_template_string("t0", "Output name: {{name}}")
1121 .unwrap();
1122 r.register_template_string("t1", "Output name: {{first_name}}")
1123 .unwrap();
1124 r.register_template_string("t2", "Output name: {{./name}}")
1125 .unwrap();
1126
1127 assert_eq!(
1129 r.render("t0", &json!({"name": "Alex"})).unwrap(),
1130 "Output name: N/A"
1131 );
1132
1133 assert_eq!(
1135 r.render("t2", &json!({"name": "Alex"})).unwrap(),
1136 "Output name: Alex"
1137 );
1138
1139 assert_eq!(
1141 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1142 "Output name: "
1143 );
1144
1145 r.set_strict_mode(true);
1147 assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1148
1149 r.set_strict_mode(false);
1151 r.register_helper(
1152 "helperMissing",
1153 Box::new(
1154 |h: &Helper<'_>,
1155 _: &Registry<'_>,
1156 _: &Context,
1157 _: &mut RenderContext<'_, '_>,
1158 out: &mut dyn Output|
1159 -> Result<(), RenderError> {
1160 let name = h.name();
1161 write!(out, "{} not resolved", name)?;
1162 Ok(())
1163 },
1164 ),
1165 );
1166 assert_eq!(
1167 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1168 "Output name: first_name not resolved"
1169 );
1170 }
1171
1172 #[test]
1173 fn test_identifiers_starting_with_numbers() {
1174 let mut r = Registry::new();
1175
1176 assert!(r
1177 .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1178 .is_ok());
1179 let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1180 assert_eq!(r1, "true");
1181
1182 assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1183 let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1184 assert_eq!(r2, "false");
1185
1186 assert!(r
1187 .register_template_string("r3", "0: {{0}} {{#if (eq 0 true)}}resolved from context{{/if}}\n1a: {{1a}} {{#if (eq 1a true)}}resolved from context{{/if}}\n2_2: {{2_2}} {{#if (eq 2_2 true)}}resolved from context{{/if}}") .is_ok());
1189 let r3 = r
1190 .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1191 .unwrap();
1192 assert_eq!(
1193 r3,
1194 "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1195 );
1196
1197 assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1199 assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1200 assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1201 assert!(r.render("r4", &()).is_err());
1202 assert!(r.render("r5", &()).is_err());
1203 assert!(r.render("r6", &()).is_err());
1204 }
1205}