tera/
tera.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::fs::File;
4use std::io::prelude::*;
5use std::path::Path;
6use std::sync::Arc;
7
8use globwalk::glob_builder;
9
10use crate::builtins::filters::{array, common, number, object, string, Filter};
11use crate::builtins::functions::{self, Function};
12use crate::builtins::testers::{self, Test};
13use crate::context::Context;
14use crate::errors::{Error, Result};
15use crate::renderer::Renderer;
16use crate::template::Template;
17use crate::utils::escape_html;
18
19/// Default template name used for `Tera::render_str` and `Tera::one_off`.
20const ONE_OFF_TEMPLATE_NAME: &str = "__tera_one_off";
21
22/// The escape function type definition
23pub type EscapeFn = fn(&str) -> String;
24
25/// Main point of interaction in this library.
26///
27/// The [`Tera`] struct is the primary interface for working with the Tera template engine. It contains parsed templates, registered filters (which can filter
28/// data), functions, and testers. It also contains some configuration options, such as a list of
29/// suffixes for files that have autoescaping turned on.
30///
31/// It is responsible for:
32///
33/// - Loading and managing templates from files or strings
34/// - Parsing templates and checking for syntax errors
35/// - Maintaining a cache of compiled templates for efficient rendering
36/// - Providing an interface for rendering templates with given contexts
37/// - Managing template inheritance and includes
38/// - Handling custom filters and functions
39/// - Overriding settings, such as autoescape rules
40///
41/// # Example
42///
43/// Basic usage:
44///
45/// ```
46/// use tera::Tera;
47///
48/// // Create a new Tera instance and add a template from a string
49/// let mut tera = Tera::new("templates/**/*").unwrap();
50/// tera.add_raw_template("hello", "Hello, {{ name }}!").unwrap();
51///
52/// // Prepare the context with some data
53/// let mut context = tera::Context::new();
54/// context.insert("name", "World");
55///
56/// // Render the template with the given context
57/// let rendered = tera.render("hello", &context).unwrap();
58/// assert_eq!(rendered, "Hello, World!");
59/// ```
60#[derive(Clone)]
61pub struct Tera {
62    // The glob used in `Tera::new`, None if Tera was instantiated differently
63    #[doc(hidden)]
64    glob: Option<String>,
65    #[doc(hidden)]
66    pub templates: HashMap<String, Template>,
67    #[doc(hidden)]
68    pub filters: HashMap<String, Arc<dyn Filter>>,
69    #[doc(hidden)]
70    pub testers: HashMap<String, Arc<dyn Test>>,
71    #[doc(hidden)]
72    pub functions: HashMap<String, Arc<dyn Function>>,
73    // Which extensions does Tera automatically autoescape on.
74    // Defaults to [".html", ".htm", ".xml"]
75    #[doc(hidden)]
76    pub autoescape_suffixes: Vec<&'static str>,
77    #[doc(hidden)]
78    escape_fn: EscapeFn,
79}
80
81impl Tera {
82    fn create(dir: &str, parse_only: bool) -> Result<Tera> {
83        if dir.find('*').is_none() {
84            return Err(Error::msg(format!(
85                "Tera expects a glob as input, no * were found in `{}`",
86                dir
87            )));
88        }
89
90        let mut tera = Tera {
91            glob: Some(dir.to_string()),
92            templates: HashMap::new(),
93            filters: HashMap::new(),
94            functions: HashMap::new(),
95            testers: HashMap::new(),
96            autoescape_suffixes: vec![".html", ".htm", ".xml"],
97            escape_fn: escape_html,
98        };
99
100        tera.load_from_glob()?;
101        if !parse_only {
102            tera.build_inheritance_chains()?;
103            tera.check_macro_files()?;
104        }
105        tera.register_tera_filters();
106        tera.register_tera_testers();
107        tera.register_tera_functions();
108        Ok(tera)
109    }
110
111    /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob.
112    ///
113    /// A glob is a pattern for matching multiple file paths, employing special characters such as
114    /// the single asterisk (`*`) to match any sequence of characters within a single directory
115    /// level, and the double asterisk (`**`) to match any sequence of characters across multiple
116    /// directory levels, thereby providing a flexible and concise way to select files based on
117    /// their names, extensions, or hierarchical relationships. For example, the glob pattern
118    /// `templates/*.html` will match all files with the `.html` extension located directly inside
119    /// the `templates` folder, while the glob pattern `templates/**/*.html` will match all files
120    /// with the `.html` extension directly inside or in a subdirectory of `templates`.
121    ///
122    /// In order to create an empty [`Tera`] instance, you can use the [`Default`] implementation.
123    ///
124    /// # Examples
125    ///
126    /// Basic usage:
127    ///
128    /// ```no_run
129    /// # use tera::Tera;
130    /// let tera = Tera::new("examples/basic/templates/**/*").unwrap();
131    /// ```
132    pub fn new(dir: &str) -> Result<Tera> {
133        Self::create(dir, false)
134    }
135
136    /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob.
137    ///
138    /// The difference to [`Tera::new`] is that it won't build the inheritance chains
139    /// automatically, so you are free to modify the templates if you need to.
140    ///
141    /// # Inheritance Chains
142    ///
143    /// You will *not* get a working Tera instance using this method. You will need to call
144    /// [`build_inheritance_chains()`](Tera::build_inheritance_chains) to make it usable.
145    ///
146    /// # Examples
147    ///
148    /// Basic usage:
149    ///
150    /// ```no_run
151    /// # use tera::Tera;
152    /// let mut tera = Tera::parse("examples/basic/templates/**/*").unwrap();
153    ///
154    /// // do not forget to build the inheritance chains
155    /// tera.build_inheritance_chains().unwrap();
156    /// ```
157    pub fn parse(dir: &str) -> Result<Tera> {
158        Self::create(dir, true)
159    }
160
161    /// Loads all the templates found in the glob that was given to [`Tera::new`].
162    fn load_from_glob(&mut self) -> Result<()> {
163        let glob = match &self.glob {
164            Some(g) => g,
165            None => return Err(Error::msg("Tera can only load from glob if a glob is provided")),
166        };
167
168        // We want to preserve templates that have been added through
169        // Tera::extend so we only keep those
170        self.templates = self
171            .templates
172            .iter()
173            .filter(|&(_, t)| t.from_extend)
174            .map(|(n, t)| (n.clone(), t.clone())) // TODO: avoid that clone
175            .collect();
176
177        let mut errors = String::new();
178
179        // Need to canonicalize the glob path because globwalk always returns
180        // an empty list for paths starting with `./` or `../`.
181        // See https://github.com/Keats/tera/issues/574 for the Tera discussion
182        // and https://github.com/Gilnaa/globwalk/issues/28 for the upstream issue.
183        let (parent_dir, glob_end) = glob.split_at(glob.find('*').unwrap());
184        let parent_dir = match std::fs::canonicalize(parent_dir) {
185            Ok(d) => d,
186            // If canonicalize fails, just abort it and resume with the given path.
187            // Consumers expect invalid globs to just return the empty set instead of failing.
188            // See https://github.com/Keats/tera/issues/819#issuecomment-1480392230
189            Err(_) => std::path::PathBuf::from(parent_dir),
190        };
191        let dir = parent_dir.join(glob_end).into_os_string().into_string().unwrap();
192
193        // We are parsing all the templates on instantiation
194        for entry in glob_builder(&dir)
195            .follow_links(true)
196            .build()
197            .unwrap()
198            .filter_map(std::result::Result::ok)
199        {
200            let mut path = entry.into_path();
201            // We only care about actual files
202            if path.is_file() {
203                if path.starts_with("./") {
204                    path = path.strip_prefix("./").unwrap().to_path_buf();
205                }
206
207                let filepath = path
208                    .strip_prefix(&parent_dir)
209                    .unwrap()
210                    .to_string_lossy()
211                    // unify on forward slash
212                    .replace('\\', "/");
213
214                if let Err(e) = self.add_file(Some(&filepath), path) {
215                    use std::error::Error;
216
217                    errors += &format!("\n* {}", e);
218                    let mut cause = e.source();
219                    while let Some(e) = cause {
220                        errors += &format!("\n{}", e);
221                        cause = e.source();
222                    }
223                }
224            }
225        }
226
227        if !errors.is_empty() {
228            return Err(Error::msg(errors));
229        }
230
231        Ok(())
232    }
233
234    // Add a template from a path: reads the file and parses it.
235    // This will return an error if the template is invalid and doesn't check the validity of
236    // inheritance chains.
237    fn add_file<P: AsRef<Path>>(&mut self, name: Option<&str>, path: P) -> Result<()> {
238        let path = path.as_ref();
239        let tpl_name = name.unwrap_or_else(|| path.to_str().unwrap());
240
241        let mut f = File::open(path)
242            .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
243
244        let mut input = String::new();
245        f.read_to_string(&mut input)
246            .map_err(|e| Error::chain(format!("Failed to read template '{:?}'", path), e))?;
247
248        let tpl = Template::new(tpl_name, Some(path.to_str().unwrap().to_string()), &input)
249            .map_err(|e| Error::chain(format!("Failed to parse {:?}", path), e))?;
250
251        self.templates.insert(tpl_name.to_string(), tpl);
252        Ok(())
253    }
254
255    /// Build inheritance chains for loaded templates.
256    ///
257    /// We need to know the hierarchy of templates to be able to render multiple extends level.
258    /// This happens at compile-time to avoid checking it every time we want to render a template.
259    /// This also checks for soundness issues in the inheritance chains, such as missing template
260    /// or circular extends.  It also builds the block inheritance chain and detects when super()
261    /// is called in a place where it can't possibly work
262    ///
263    /// You generally don't need to call that yourself, unless you used [`Tera::parse()`].
264    pub fn build_inheritance_chains(&mut self) -> Result<()> {
265        // Recursive fn that finds all the parents and put them in an ordered Vec from closest to first parent
266        // parent template
267        fn build_chain(
268            templates: &HashMap<String, Template>,
269            start: &Template,
270            template: &Template,
271            mut parents: Vec<String>,
272        ) -> Result<Vec<String>> {
273            if !parents.is_empty() && start.name == template.name {
274                return Err(Error::circular_extend(&start.name, parents));
275            }
276
277            match template.parent {
278                Some(ref p) => match templates.get(p) {
279                    Some(parent) => {
280                        parents.push(parent.name.clone());
281                        build_chain(templates, start, parent, parents)
282                    }
283                    None => Err(Error::missing_parent(&template.name, p)),
284                },
285                None => Ok(parents),
286            }
287        }
288
289        // TODO: if we can rewrite the 2 loops below to be only one loop, that'd be great
290        let mut tpl_parents = HashMap::new();
291        let mut tpl_block_definitions = HashMap::new();
292        for (name, template) in &self.templates {
293            if template.parent.is_none() && template.blocks.is_empty() {
294                continue;
295            }
296
297            let parents = build_chain(&self.templates, template, template, vec![])?;
298
299            let mut blocks_definitions = HashMap::new();
300            for (block_name, def) in &template.blocks {
301                // push our own block first
302                let mut definitions = vec![(template.name.clone(), def.clone())];
303
304                // and then see if our parents have it
305                for parent in &parents {
306                    let t = self.get_template(parent)?;
307
308                    if let Some(b) = t.blocks.get(block_name) {
309                        definitions.push((t.name.clone(), b.clone()));
310                    }
311                }
312                blocks_definitions.insert(block_name.clone(), definitions);
313            }
314            tpl_parents.insert(name.clone(), parents);
315            tpl_block_definitions.insert(name.clone(), blocks_definitions);
316        }
317
318        for template in self.templates.values_mut() {
319            // Simple template: no inheritance or blocks -> nothing to do
320            if template.parent.is_none() && template.blocks.is_empty() {
321                continue;
322            }
323
324            template.parents = match tpl_parents.remove(&template.name) {
325                Some(parents) => parents,
326                None => vec![],
327            };
328            template.blocks_definitions = match tpl_block_definitions.remove(&template.name) {
329                Some(blocks) => blocks,
330                None => HashMap::new(),
331            };
332        }
333
334        Ok(())
335    }
336
337    /// We keep track of macro files loaded in each Template so we can know whether one or them
338    /// is missing and error accordingly before the user tries to render a template.
339    ///
340    /// As with [`build_inheritance_chains()`](Self::build_inheritance_chains), you don't usually need to call that yourself.
341    pub fn check_macro_files(&self) -> Result<()> {
342        for template in self.templates.values() {
343            for (tpl_name, _) in &template.imported_macro_files {
344                if !self.templates.contains_key(tpl_name) {
345                    return Err(Error::msg(format!(
346                        "Template `{}` loads macros from `{}` which isn't present in Tera",
347                        template.name, tpl_name
348                    )));
349                }
350            }
351        }
352
353        Ok(())
354    }
355
356    /// Renders a Tera template given a [`Context`].
357    ///
358    /// # Examples
359    ///
360    /// Basic usage:
361    ///
362    /// ```
363    /// # use tera::{Tera, Context};
364    /// // Create new tera instance with sample template
365    /// let mut tera = Tera::default();
366    /// tera.add_raw_template("info", "My age is {{ age }}.");
367    ///
368    /// // Create new context
369    /// let mut context = Context::new();
370    /// context.insert("age", &18);
371    ///
372    /// // Render template using the context
373    /// let output = tera.render("info", &context).unwrap();
374    /// assert_eq!(output, "My age is 18.");
375    /// ```
376    ///
377    /// To render a template with an empty context, simply pass an empty [`Context`] object.
378    ///
379    /// ```
380    /// # use tera::{Tera, Context};
381    /// // Create new tera instance with demo template
382    /// let mut tera = Tera::default();
383    /// tera.add_raw_template("hello.html", "<h1>Hello</h1>");
384    ///
385    /// // Render a template with an empty context
386    /// let output = tera.render("hello.html", &Context::new()).unwrap();
387    /// assert_eq!(output, "<h1>Hello</h1>");
388    /// ```
389    pub fn render(&self, template_name: &str, context: &Context) -> Result<String> {
390        let template = self.get_template(template_name)?;
391        let renderer = Renderer::new(template, self, context);
392        renderer.render()
393    }
394
395    /// Renders a Tera template given a [`Context`] to something that implements [`Write`].
396    ///
397    /// The only difference from [`render()`](Self::render) is that this version doesn't convert
398    /// buffer to a String, allowing to render directly to anything that implements [`Write`]. For
399    /// example, this could be used to write directly to a [`File`](std::fs::File).
400    ///
401    /// Any I/O error will be reported in the result.
402    ///
403    /// # Examples
404    ///
405    /// Rendering into a `Vec<u8>`:
406    ///
407    /// ```
408    /// # use tera::{Context, Tera};
409    /// let mut tera = Tera::default();
410    /// tera.add_raw_template("index.html", "<p>{{ name }}</p>");
411    ///
412    /// // Rendering a template to an internal buffer
413    /// let mut buffer = Vec::new();
414    /// let mut context = Context::new();
415    /// context.insert("name", "John Wick");
416    /// tera.render_to("index.html", &context, &mut buffer).unwrap();
417    /// assert_eq!(buffer, b"<p>John Wick</p>");
418    /// ```
419    pub fn render_to(
420        &self,
421        template_name: &str,
422        context: &Context,
423        write: impl Write,
424    ) -> Result<()> {
425        let template = self.get_template(template_name)?;
426        let renderer = Renderer::new(template, self, context);
427        renderer.render_to(write)
428    }
429
430    /// Renders a one off template (for example a template coming from a user
431    /// input) given a `Context` and an instance of Tera. This allows you to
432    /// render templates using custom filters or functions.
433    ///
434    /// Any errors will mention the `__tera_one_off` template: this is the name
435    /// given to the template by Tera.
436    ///
437    /// ```no_compile
438    /// let mut context = Context::new();
439    /// context.insert("greeting", &"Hello");
440    /// let string = tera.render_str("{{ greeting }} World!", &context)?;
441    /// assert_eq!(string, "Hello World!");
442    /// ```
443    pub fn render_str(&mut self, input: &str, context: &Context) -> Result<String> {
444        self.add_raw_template(ONE_OFF_TEMPLATE_NAME, input)?;
445        let result = self.render(ONE_OFF_TEMPLATE_NAME, context);
446        self.templates.remove(ONE_OFF_TEMPLATE_NAME);
447        result
448    }
449
450    /// Renders a one off template (for example a template coming from a user input) given a `Context`
451    ///
452    /// This creates a separate instance of Tera with no possibilities of adding custom filters
453    /// or testers, parses the template and render it immediately.
454    /// Any errors will mention the `__tera_one_off` template: this is the name given to the template by
455    /// Tera
456    ///
457    /// ```no_compile
458    /// let mut context = Context::new();
459    /// context.insert("greeting", &"hello");
460    /// Tera::one_off("{{ greeting }} world", &context, true);
461    /// ```
462    pub fn one_off(input: &str, context: &Context, autoescape: bool) -> Result<String> {
463        let mut tera = Tera::default();
464
465        if autoescape {
466            tera.autoescape_on(vec![ONE_OFF_TEMPLATE_NAME]);
467        }
468
469        tera.render_str(input, context)
470    }
471
472    #[doc(hidden)]
473    #[inline]
474    pub fn get_template(&self, template_name: &str) -> Result<&Template> {
475        match self.templates.get(template_name) {
476            Some(tpl) => Ok(tpl),
477            None => Err(Error::template_not_found(template_name)),
478        }
479    }
480
481    /// Returns an iterator over the names of all registered templates in an
482    /// unspecified order.
483    ///
484    /// # Example
485    ///
486    /// ```rust
487    /// use tera::Tera;
488    ///
489    /// let mut tera = Tera::default();
490    /// tera.add_raw_template("foo", "{{ hello }}");
491    /// tera.add_raw_template("another-one.html", "contents go here");
492    ///
493    /// let names: Vec<_> = tera.get_template_names().collect();
494    /// assert_eq!(names.len(), 2);
495    /// assert!(names.contains(&"foo"));
496    /// assert!(names.contains(&"another-one.html"));
497    /// ```
498    #[inline]
499    pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
500        self.templates.keys().map(|s| s.as_str())
501    }
502
503    /// Add a single template to the Tera instance.
504    ///
505    /// This will error if the inheritance chain can't be built, such as adding a child
506    /// template without the parent one.
507    ///
508    /// # Bulk loading
509    ///
510    /// If you want to add several templates, use
511    /// [`add_raw_templates()`](Tera::add_raw_templates).
512    ///
513    /// # Examples
514    ///
515    /// Basic usage:
516    ///
517    /// ```
518    /// # use tera::Tera;
519    /// let mut tera = Tera::default();
520    /// tera.add_raw_template("new.html", "Blabla").unwrap();
521    /// ```
522    pub fn add_raw_template(&mut self, name: &str, content: &str) -> Result<()> {
523        let tpl = Template::new(name, None, content)
524            .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
525        self.templates.insert(name.to_string(), tpl);
526        self.build_inheritance_chains()?;
527        self.check_macro_files()?;
528        Ok(())
529    }
530
531    /// Add all the templates given to the Tera instance
532    ///
533    /// This will error if the inheritance chain can't be built, such as adding a child
534    /// template without the parent one.
535    ///
536    /// ```no_compile
537    /// tera.add_raw_templates(vec![
538    ///     ("new.html", "blabla"),
539    ///     ("new2.html", "hello"),
540    /// ]);
541    /// ```
542    pub fn add_raw_templates<I, N, C>(&mut self, templates: I) -> Result<()>
543    where
544        I: IntoIterator<Item = (N, C)>,
545        N: AsRef<str>,
546        C: AsRef<str>,
547    {
548        for (name, content) in templates {
549            let name = name.as_ref();
550            let tpl = Template::new(name, None, content.as_ref())
551                .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
552            self.templates.insert(name.to_string(), tpl);
553        }
554        self.build_inheritance_chains()?;
555        self.check_macro_files()?;
556        Ok(())
557    }
558
559    /// Add a single template from a path to the Tera instance. The default name for the template is
560    /// the path given, but this can be renamed with the `name` parameter
561    ///
562    /// This will error if the inheritance chain can't be built, such as adding a child
563    /// template without the parent one.
564    /// If you want to add several file, use [Tera::add_template_files](struct.Tera.html#method.add_template_files)
565    ///
566    /// ```
567    /// # use tera::Tera;
568    /// let mut tera = Tera::default();
569    /// // Rename template with custom name
570    /// tera.add_template_file("examples/basic/templates/macros.html", Some("macros.html")).unwrap();
571    /// // Use path as name
572    /// tera.add_template_file("examples/basic/templates/base.html", None).unwrap();
573    /// ```
574    pub fn add_template_file<P: AsRef<Path>>(&mut self, path: P, name: Option<&str>) -> Result<()> {
575        self.add_file(name, path)?;
576        self.build_inheritance_chains()?;
577        self.check_macro_files()?;
578        Ok(())
579    }
580
581    /// Add several templates from paths to the Tera instance.
582    ///
583    /// The default name for the template is the path given, but this can be renamed with the
584    /// second parameter of the tuple
585    ///
586    /// This will error if the inheritance chain can't be built, such as adding a child
587    /// template without the parent one.
588    ///
589    /// ```no_run
590    /// # use tera::Tera;
591    /// let mut tera = Tera::default();
592    /// tera.add_template_files(vec![
593    ///     ("./path/to/template.tera", None), // this template will have the value of path1 as name
594    ///     ("./path/to/other.tera", Some("hey")), // this template will have `hey` as name
595    /// ]);
596    /// ```
597    pub fn add_template_files<I, P, N>(&mut self, files: I) -> Result<()>
598    where
599        I: IntoIterator<Item = (P, Option<N>)>,
600        P: AsRef<Path>,
601        N: AsRef<str>,
602    {
603        for (path, name) in files {
604            self.add_file(name.as_ref().map(AsRef::as_ref), path)?;
605        }
606        self.build_inheritance_chains()?;
607        self.check_macro_files()?;
608        Ok(())
609    }
610
611    #[doc(hidden)]
612    #[inline]
613    pub fn get_filter(&self, filter_name: &str) -> Result<&dyn Filter> {
614        match self.filters.get(filter_name) {
615            Some(fil) => Ok(&**fil),
616            None => Err(Error::filter_not_found(filter_name)),
617        }
618    }
619
620    /// Register a filter with Tera.
621    ///
622    /// If a filter with that name already exists, it will be overwritten
623    ///
624    /// ```no_compile
625    /// tera.register_filter("upper", string::upper);
626    /// ```
627    pub fn register_filter<F: Filter + 'static>(&mut self, name: &str, filter: F) {
628        self.filters.insert(name.to_string(), Arc::new(filter));
629    }
630
631    #[doc(hidden)]
632    #[inline]
633    pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
634        match self.testers.get(tester_name) {
635            Some(t) => Ok(&**t),
636            None => Err(Error::test_not_found(tester_name)),
637        }
638    }
639
640    /// Register a tester with Tera.
641    ///
642    /// If a tester with that name already exists, it will be overwritten
643    ///
644    /// ```no_compile
645    /// tera.register_tester("odd", testers::odd);
646    /// ```
647    pub fn register_tester<T: Test + 'static>(&mut self, name: &str, tester: T) {
648        self.testers.insert(name.to_string(), Arc::new(tester));
649    }
650
651    #[doc(hidden)]
652    #[inline]
653    pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
654        match self.functions.get(fn_name) {
655            Some(t) => Ok(&**t),
656            None => Err(Error::function_not_found(fn_name)),
657        }
658    }
659
660    /// Register a function with Tera.
661    ///
662    /// This registers an arbitrary function to make it callable from within a template. If a
663    /// function with that name already exists, it will be overwritten.
664    ///
665    /// ```no_compile
666    /// tera.register_function("range", range);
667    /// ```
668    pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
669        self.functions.insert(name.to_string(), Arc::new(function));
670    }
671
672    fn register_tera_filters(&mut self) {
673        self.register_filter("upper", string::upper);
674        self.register_filter("lower", string::lower);
675        self.register_filter("trim", string::trim);
676        self.register_filter("trim_start", string::trim_start);
677        self.register_filter("trim_end", string::trim_end);
678        self.register_filter("trim_start_matches", string::trim_start_matches);
679        self.register_filter("trim_end_matches", string::trim_end_matches);
680        self.register_filter("truncate", string::truncate);
681        self.register_filter("wordcount", string::wordcount);
682        self.register_filter("replace", string::replace);
683        self.register_filter("capitalize", string::capitalize);
684        self.register_filter("title", string::title);
685        self.register_filter("linebreaksbr", string::linebreaksbr);
686        self.register_filter("indent", string::indent);
687        self.register_filter("striptags", string::striptags);
688        self.register_filter("spaceless", string::spaceless);
689        #[cfg(feature = "urlencode")]
690        self.register_filter("urlencode", string::urlencode);
691        #[cfg(feature = "urlencode")]
692        self.register_filter("urlencode_strict", string::urlencode_strict);
693        self.register_filter("escape", string::escape_html);
694        self.register_filter("escape_xml", string::escape_xml);
695        #[cfg(feature = "builtins")]
696        self.register_filter("slugify", string::slugify);
697        self.register_filter("addslashes", string::addslashes);
698        self.register_filter("split", string::split);
699        self.register_filter("int", string::int);
700        self.register_filter("float", string::float);
701
702        self.register_filter("first", array::first);
703        self.register_filter("last", array::last);
704        self.register_filter("nth", array::nth);
705        self.register_filter("join", array::join);
706        self.register_filter("sort", array::sort);
707        self.register_filter("unique", array::unique);
708        self.register_filter("slice", array::slice);
709        self.register_filter("group_by", array::group_by);
710        self.register_filter("filter", array::filter);
711        self.register_filter("map", array::map);
712        self.register_filter("concat", array::concat);
713
714        self.register_filter("abs", number::abs);
715        self.register_filter("pluralize", number::pluralize);
716        self.register_filter("round", number::round);
717
718        #[cfg(feature = "builtins")]
719        self.register_filter("filesizeformat", number::filesizeformat);
720
721        self.register_filter("length", common::length);
722        self.register_filter("reverse", common::reverse);
723        #[cfg(feature = "builtins")]
724        self.register_filter("date", common::date);
725        self.register_filter("json_encode", common::json_encode);
726        self.register_filter("as_str", common::as_str);
727
728        self.register_filter("get", object::get);
729    }
730
731    fn register_tera_testers(&mut self) {
732        self.register_tester("defined", testers::defined);
733        self.register_tester("undefined", testers::undefined);
734        self.register_tester("odd", testers::odd);
735        self.register_tester("even", testers::even);
736        self.register_tester("string", testers::string);
737        self.register_tester("number", testers::number);
738        self.register_tester("divisibleby", testers::divisible_by);
739        self.register_tester("iterable", testers::iterable);
740        self.register_tester("object", testers::object);
741        self.register_tester("starting_with", testers::starting_with);
742        self.register_tester("ending_with", testers::ending_with);
743        self.register_tester("containing", testers::containing);
744        self.register_tester("matching", testers::matching);
745    }
746
747    fn register_tera_functions(&mut self) {
748        self.register_function("range", functions::range);
749        #[cfg(feature = "builtins")]
750        self.register_function("now", functions::now);
751        self.register_function("throw", functions::throw);
752        #[cfg(feature = "builtins")]
753        self.register_function("get_random", functions::get_random);
754        self.register_function("get_env", functions::get_env);
755    }
756
757    /// Select which suffix(es) to automatically do HTML escaping on.
758    ///
759    /// By default, autoescaping is performed on `.html`, `.htm` and `.xml` template files. Only
760    /// call this function if you wish to change the defaults.
761    ///
762    /// # Examples
763    ///
764    /// Basic usage:
765    ///
766    /// ```
767    /// # use tera::Tera;
768    /// let mut tera = Tera::default();
769    /// // escape only files ending with `.php.html`
770    /// tera.autoescape_on(vec![".php.html"]);
771    /// // disable autoescaping completely
772    /// tera.autoescape_on(vec![]);
773    /// ```
774    pub fn autoescape_on(&mut self, suffixes: Vec<&'static str>) {
775        self.autoescape_suffixes = suffixes;
776    }
777
778    #[doc(hidden)]
779    #[inline]
780    pub fn get_escape_fn(&self) -> &EscapeFn {
781        &self.escape_fn
782    }
783
784    /// Set user-defined function that is used to escape content.
785    ///
786    /// Often times, arbitrary data needs to be injected into a template without allowing injection
787    /// attacks. For this reason, typically escaping is performed on all input. By default, the
788    /// escaping function will produce HTML escapes, but it can be overridden to produce escapes
789    /// more appropriate to the language being used.
790    ///
791    /// Inside templates, escaping can be turned off for specific content using the `safe` filter.
792    /// For example, the string `{{ data }}` inside a template will escape data, while `{{ data |
793    /// safe }}` will not.
794    ///
795    /// # Examples
796    ///
797    /// Basic usage:
798    ///
799    /// ```
800    /// # use tera::{Tera, Context};
801    /// // Create new Tera instance
802    /// let mut tera = Tera::default();
803    ///
804    /// // Override escape function
805    /// tera.set_escape_fn(|input| {
806    ///     input.escape_default().collect()
807    /// });
808    ///
809    /// // Create template and enable autoescape
810    /// tera.add_raw_template("hello.js", "const data = \"{{ content }}\";").unwrap();
811    /// tera.autoescape_on(vec!["js"]);
812    ///
813    /// // Create context with some data
814    /// let mut context = Context::new();
815    /// context.insert("content", &"Hello\n\'world\"!");
816    ///
817    /// // Render template
818    /// let result = tera.render("hello.js", &context).unwrap();
819    /// assert_eq!(result, r#"const data = "Hello\n\'world\"!";"#);
820    /// ```
821    pub fn set_escape_fn(&mut self, function: EscapeFn) {
822        self.escape_fn = function;
823    }
824
825    /// Reset escape function to default [`escape_html()`].
826    pub fn reset_escape_fn(&mut self) {
827        self.escape_fn = escape_html;
828    }
829
830    /// Re-parse all templates found in the glob given to Tera.
831    ///
832    /// Use this when you are watching a directory and want to reload everything,
833    /// for example when a file is added.
834    ///
835    /// If you are adding templates without using a glob, we can't know when a template
836    /// is deleted, which would result in an error if we are trying to reload that file.
837    pub fn full_reload(&mut self) -> Result<()> {
838        if self.glob.is_some() {
839            self.load_from_glob()?;
840        } else {
841            return Err(Error::msg("Reloading is only available if you are using a glob"));
842        }
843
844        self.build_inheritance_chains()?;
845        self.check_macro_files()
846    }
847
848    /// Extend this [`Tera`] instance with the templates, filters, testers and functions defined in
849    /// another instance.
850    ///
851    /// Use that method when you want to add a given Tera instance templates/filters/testers/functions
852    /// to your own. If a template/filter/tester/function with the same name already exists in your instance,
853    /// it will not be overwritten.
854    ///
855    ///```no_compile
856    /// // add all the templates from FRAMEWORK_TERA
857    /// // except the ones that have an identical name to the ones in `my_tera`
858    /// my_tera.extend(&FRAMEWORK_TERA);
859    ///```
860    pub fn extend(&mut self, other: &Tera) -> Result<()> {
861        for (name, template) in &other.templates {
862            if !self.templates.contains_key(name) {
863                let mut tpl = template.clone();
864                tpl.from_extend = true;
865                self.templates.insert(name.to_string(), tpl);
866            }
867        }
868
869        for (name, filter) in &other.filters {
870            if !self.filters.contains_key(name) {
871                self.filters.insert(name.to_string(), filter.clone());
872            }
873        }
874
875        for (name, tester) in &other.testers {
876            if !self.testers.contains_key(name) {
877                self.testers.insert(name.to_string(), tester.clone());
878            }
879        }
880
881        for (name, function) in &other.functions {
882            if !self.functions.contains_key(name) {
883                self.functions.insert(name.to_string(), function.clone());
884            }
885        }
886
887        self.build_inheritance_chains()?;
888        self.check_macro_files()
889    }
890}
891
892impl Default for Tera {
893    fn default() -> Tera {
894        let mut tera = Tera {
895            glob: None,
896            templates: HashMap::new(),
897            filters: HashMap::new(),
898            testers: HashMap::new(),
899            functions: HashMap::new(),
900            autoescape_suffixes: vec![".html", ".htm", ".xml"],
901            escape_fn: escape_html,
902        };
903
904        tera.register_tera_filters();
905        tera.register_tera_testers();
906        tera.register_tera_functions();
907        tera
908    }
909}
910
911// Needs a manual implementation since borrows in Fn's don't implement Debug.
912impl fmt::Debug for Tera {
913    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
914        write!(f, "Tera {{")?;
915        writeln!(f, "\n\ttemplates: [")?;
916
917        for template in self.templates.keys() {
918            writeln!(f, "\t\t{},", template)?;
919        }
920        write!(f, "\t]")?;
921        writeln!(f, "\n\tfilters: [")?;
922
923        for filter in self.filters.keys() {
924            writeln!(f, "\t\t{},", filter)?;
925        }
926        write!(f, "\t]")?;
927        writeln!(f, "\n\ttesters: [")?;
928
929        for tester in self.testers.keys() {
930            writeln!(f, "\t\t{},", tester)?;
931        }
932        writeln!(f, "\t]")?;
933
934        writeln!(f, "}}")
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use tempfile::tempdir;
941
942    use std::collections::HashMap;
943    use std::fs::File;
944
945    use super::Tera;
946    use crate::context::Context;
947    use serde_json::{json, Value as JsonValue};
948
949    #[test]
950    fn test_get_inheritance_chain() {
951        let mut tera = Tera::default();
952        tera.add_raw_templates(vec![
953            ("a", "{% extends \"b\" %}"),
954            ("b", "{% extends \"c\" %}"),
955            ("c", "{% extends \"d\" %}"),
956            ("d", ""),
957        ])
958        .unwrap();
959
960        assert_eq!(
961            tera.get_template("a").unwrap().parents,
962            vec!["b".to_string(), "c".to_string(), "d".to_string()]
963        );
964        assert_eq!(tera.get_template("b").unwrap().parents, vec!["c".to_string(), "d".to_string()]);
965        assert_eq!(tera.get_template("c").unwrap().parents, vec!["d".to_string()]);
966        assert_eq!(tera.get_template("d").unwrap().parents.len(), 0);
967    }
968
969    #[test]
970    fn test_missing_parent_template() {
971        let mut tera = Tera::default();
972        assert_eq!(
973            tera.add_raw_template("a", "{% extends \"b\" %}").unwrap_err().to_string(),
974            "Template \'a\' is inheriting from \'b\', which doesn\'t exist or isn\'t loaded."
975        );
976    }
977
978    #[test]
979    fn test_circular_extends() {
980        let mut tera = Tera::default();
981        let err = tera
982            .add_raw_templates(vec![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
983            .unwrap_err();
984
985        assert!(err.to_string().contains("Circular extend detected for template"));
986    }
987
988    #[test]
989    fn test_get_parent_blocks_definition() {
990        let mut tera = Tera::default();
991        tera.add_raw_templates(vec![
992            (
993                "grandparent",
994                "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
995            ),
996            (
997                "parent",
998                "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
999            ),
1000            (
1001                "child",
1002                "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
1003            ),
1004        ]).unwrap();
1005
1006        let hey_definitions =
1007            tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
1008        assert_eq!(hey_definitions.len(), 3);
1009
1010        let ending_definitions =
1011            tera.get_template("child").unwrap().blocks_definitions.get("ending").unwrap();
1012        assert_eq!(ending_definitions.len(), 2);
1013    }
1014
1015    #[test]
1016    fn test_get_parent_blocks_definition_nested_block() {
1017        let mut tera = Tera::default();
1018        tera.add_raw_templates(vec![
1019            ("grandparent", "{% block hey %}hello{% endblock hey %}"),
1020            (
1021                "parent",
1022                "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
1023            ),
1024            (
1025                "child",
1026                "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
1027            ),
1028        ]).unwrap();
1029
1030        let hey_definitions =
1031            tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
1032        assert_eq!(hey_definitions.len(), 3);
1033
1034        let ending_definitions =
1035            tera.get_template("parent").unwrap().blocks_definitions.get("ending").unwrap();
1036        assert_eq!(ending_definitions.len(), 1);
1037    }
1038
1039    #[test]
1040    fn test_can_autoescape_one_off_template() {
1041        let mut context = Context::new();
1042        context.insert("greeting", &"<p>");
1043        let result = Tera::one_off("{{ greeting }} world", &context, true).unwrap();
1044
1045        assert_eq!(result, "&lt;p&gt; world");
1046    }
1047
1048    #[test]
1049    fn test_can_disable_autoescape_one_off_template() {
1050        let mut context = Context::new();
1051        context.insert("greeting", &"<p>");
1052        let result = Tera::one_off("{{ greeting }} world", &context, false).unwrap();
1053
1054        assert_eq!(result, "<p> world");
1055    }
1056
1057    #[test]
1058    fn test_set_escape_function() {
1059        let escape_c_string: super::EscapeFn = |input| {
1060            let mut output = String::with_capacity(input.len() * 2);
1061            for c in input.chars() {
1062                match c {
1063                    '\'' => output.push_str("\\'"),
1064                    '\"' => output.push_str("\\\""),
1065                    '\\' => output.push_str("\\\\"),
1066                    '\n' => output.push_str("\\n"),
1067                    '\r' => output.push_str("\\r"),
1068                    '\t' => output.push_str("\\t"),
1069                    _ => output.push(c),
1070                }
1071            }
1072            output
1073        };
1074        let mut tera = Tera::default();
1075        tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
1076        tera.autoescape_on(vec!["foo"]);
1077        tera.set_escape_fn(escape_c_string);
1078        let mut context = Context::new();
1079        context.insert("content", &"Hello\n\'world\"!");
1080        let result = tera.render("foo", &context).unwrap();
1081        assert_eq!(result, r#""Hello\n\'world\"!""#);
1082    }
1083
1084    #[test]
1085    fn test_reset_escape_function() {
1086        let no_escape: super::EscapeFn = |input| input.to_string();
1087        let mut tera = Tera::default();
1088        tera.add_raw_template("foo", "{{ content }}").unwrap();
1089        tera.autoescape_on(vec!["foo"]);
1090        tera.set_escape_fn(no_escape);
1091        tera.reset_escape_fn();
1092        let mut context = Context::new();
1093        context.insert("content", &"Hello\n\'world\"!");
1094        let result = tera.render("foo", &context).unwrap();
1095        assert_eq!(result, "Hello\n&#x27;world&quot;!");
1096    }
1097
1098    #[test]
1099    fn test_value_one_off_template() {
1100        let m = json!({
1101            "greeting": "Good morning"
1102        });
1103        let result =
1104            Tera::one_off("{{ greeting }} world", &Context::from_value(m).unwrap(), true).unwrap();
1105
1106        assert_eq!(result, "Good morning world");
1107    }
1108
1109    #[test]
1110    fn test_render_str_with_custom_function() {
1111        let mut tera = Tera::default();
1112        tera.register_function("echo", |args: &HashMap<_, JsonValue>| {
1113            Ok(args.get("greeting").map(JsonValue::to_owned).unwrap())
1114        });
1115
1116        let result =
1117            tera.render_str("{{ echo(greeting='Hello') }} world", &Context::default()).unwrap();
1118
1119        assert_eq!(result, "Hello world");
1120    }
1121
1122    #[test]
1123    fn test_render_map_with_dotted_keys() {
1124        let mut my_tera = Tera::default();
1125        my_tera
1126            .add_raw_templates(vec![
1127                ("dots", r#"{{ map["a.b.c"] }}"#),
1128                ("urls", r#"{{ map["https://example.com"] }}"#),
1129            ])
1130            .unwrap();
1131
1132        let mut map = HashMap::new();
1133        map.insert("a.b.c", "success");
1134        map.insert("https://example.com", "success");
1135
1136        let mut tera_context = Context::new();
1137        tera_context.insert("map", &map);
1138
1139        my_tera.render("dots", &tera_context).unwrap();
1140        my_tera.render("urls", &tera_context).unwrap();
1141    }
1142
1143    #[test]
1144    fn test_extend_no_overlap() {
1145        let mut my_tera = Tera::default();
1146        my_tera
1147            .add_raw_templates(vec![
1148                ("one", "{% block hey %}1{% endblock hey %}"),
1149                ("two", "{% block hey %}2{% endblock hey %}"),
1150                ("three", "{% block hey %}3{% endblock hey %}"),
1151            ])
1152            .unwrap();
1153
1154        let mut framework_tera = Tera::default();
1155        framework_tera.add_raw_templates(vec![("four", "Framework X")]).unwrap();
1156
1157        my_tera.extend(&framework_tera).unwrap();
1158        assert_eq!(my_tera.templates.len(), 4);
1159        let result = my_tera.render("four", &Context::default()).unwrap();
1160        assert_eq!(result, "Framework X");
1161    }
1162
1163    #[test]
1164    fn test_extend_with_overlap() {
1165        let mut my_tera = Tera::default();
1166        my_tera
1167            .add_raw_templates(vec![
1168                ("one", "MINE"),
1169                ("two", "{% block hey %}2{% endblock hey %}"),
1170                ("three", "{% block hey %}3{% endblock hey %}"),
1171            ])
1172            .unwrap();
1173
1174        let mut framework_tera = Tera::default();
1175        framework_tera
1176            .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1177            .unwrap();
1178
1179        my_tera.extend(&framework_tera).unwrap();
1180        assert_eq!(my_tera.templates.len(), 4);
1181        let result = my_tera.render("one", &Context::default()).unwrap();
1182        assert_eq!(result, "MINE");
1183    }
1184
1185    #[test]
1186    fn test_extend_new_filter() {
1187        let mut my_tera = Tera::default();
1188        let mut framework_tera = Tera::default();
1189        framework_tera.register_filter("hello", |_: &JsonValue, _: &HashMap<String, JsonValue>| {
1190            Ok(JsonValue::Number(10.into()))
1191        });
1192        my_tera.extend(&framework_tera).unwrap();
1193        assert!(my_tera.filters.contains_key("hello"));
1194    }
1195
1196    #[test]
1197    fn test_extend_new_tester() {
1198        let mut my_tera = Tera::default();
1199        let mut framework_tera = Tera::default();
1200        framework_tera.register_tester("hello", |_: Option<&JsonValue>, _: &[JsonValue]| Ok(true));
1201        my_tera.extend(&framework_tera).unwrap();
1202        assert!(my_tera.testers.contains_key("hello"));
1203    }
1204
1205    #[test]
1206    fn can_load_from_glob() {
1207        let tera = Tera::new("examples/basic/templates/**/*").unwrap();
1208        assert!(tera.get_template("base.html").is_ok());
1209    }
1210
1211    #[test]
1212    fn can_load_from_glob_with_patterns() {
1213        let tera = Tera::new("examples/basic/templates/**/*.{html, xml}").unwrap();
1214        assert!(tera.get_template("base.html").is_ok());
1215    }
1216
1217    #[test]
1218    fn full_reload_with_glob() {
1219        let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
1220        tera.full_reload().unwrap();
1221
1222        assert!(tera.get_template("base.html").is_ok());
1223    }
1224
1225    #[test]
1226    fn full_reload_with_glob_after_extending() {
1227        let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
1228        let mut framework_tera = Tera::default();
1229        framework_tera
1230            .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1231            .unwrap();
1232        tera.extend(&framework_tera).unwrap();
1233        tera.full_reload().unwrap();
1234
1235        assert!(tera.get_template("base.html").is_ok());
1236        assert!(tera.get_template("one").is_ok());
1237    }
1238
1239    #[should_panic]
1240    #[test]
1241    fn test_can_only_parse_templates() {
1242        let mut tera = Tera::parse("examples/basic/templates/**/*").unwrap();
1243        for tpl in tera.templates.values_mut() {
1244            tpl.name = format!("a-theme/templates/{}", tpl.name);
1245            if let Some(ref parent) = tpl.parent.clone() {
1246                tpl.parent = Some(format!("a-theme/templates/{}", parent));
1247            }
1248        }
1249        // Will panic here as we changed the parent and it won't be able
1250        // to build the inheritance chain in this case
1251        tera.build_inheritance_chains().unwrap();
1252    }
1253
1254    // https://github.com/Keats/tera/issues/380
1255    #[test]
1256    fn glob_work_with_absolute_paths() {
1257        let tmp_dir = tempdir().expect("create temp dir");
1258        let cwd = tmp_dir.path().canonicalize().unwrap();
1259        File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1260        File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1261        let glob = cwd.join("*.html").into_os_string().into_string().unwrap();
1262        let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1263        assert_eq!(tera.templates.len(), 2);
1264    }
1265
1266    #[test]
1267    fn glob_work_with_absolute_paths_and_double_star() {
1268        let tmp_dir = tempdir().expect("create temp dir");
1269        let cwd = tmp_dir.path().canonicalize().unwrap();
1270        File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1271        File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1272        let glob = cwd.join("**").join("*.html").into_os_string().into_string().unwrap();
1273        let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1274        assert_eq!(tera.templates.len(), 2);
1275    }
1276
1277    // Test for https://github.com/Keats/tera/issues/574
1278    #[test]
1279    fn glob_work_with_paths_starting_with_dots() {
1280        use std::path::PathBuf;
1281
1282        let this_dir = std::env::current_dir()
1283            .expect("Could not retrieve the executable's current directory.");
1284
1285        let scratch_dir = tempfile::Builder::new()
1286            .prefix("tera_test_scratchspace")
1287            .tempdir_in(&this_dir)
1288            .expect(&format!(
1289                "Could not create temporary directory for test in current directory ({}).",
1290                this_dir.display()
1291            ));
1292        dbg!(&scratch_dir.path().display());
1293
1294        File::create(scratch_dir.path().join("hey.html")).expect("Failed to create a test file");
1295        File::create(scratch_dir.path().join("ho.html")).expect("Failed to create a test file");
1296        let glob = PathBuf::from("./")
1297            .join(scratch_dir.path().file_name().unwrap())
1298            .join("**")
1299            .join("*.html")
1300            .into_os_string()
1301            .into_string()
1302            .unwrap();
1303        let tera = Tera::new(&glob).expect("Couldn't build Tera instance.");
1304        assert_eq!(tera.templates.len(), 2);
1305    }
1306
1307    // https://github.com/Keats/tera/issues/396
1308    #[test]
1309    fn issues_found_fuzzing_expressions_are_fixed() {
1310        let samples: Vec<(&str, Option<&str>)> = vec![
1311            // sample, expected result if it isn't an error
1312            ("{{0%0}}", None),
1313            ("{{W>W>vv}}{", None),
1314            ("{{22220222222022222220}}", None),
1315            ("{_{{W~1+11}}k{", None),
1316            ("{{n~n<n.11}}}", None),
1317            ("{{266314325266577770*4167}}7}}7", None),
1318            ("{{0~1~``~0~0~177777777777777777777~``~0~0~h}}", None),
1319        ];
1320
1321        for (sample, expected_output) in samples {
1322            let res = Tera::one_off(sample, &Context::new(), true);
1323            if let Some(output) = expected_output {
1324                assert!(res.is_ok());
1325                assert_eq!(res.unwrap(), output);
1326            } else {
1327                assert!(res.is_err());
1328            }
1329        }
1330    }
1331
1332    #[test]
1333    fn issues_found_fuzzing_conditions_are_fixed() {
1334        let samples: Vec<(&str, Option<&str>)> = vec![
1335            // (sample, expected result if it isn't an error)
1336            ("C~Q", None),
1337            ("s is V*0", None),
1338            ("x0x::N()", None),
1339            // this is an issue in pest itself: https://github.com/pest-parser/pest/issues/402
1340            //            ("_(p=__(p=[_(p=__(p=[_(p=[_(p=[_1", None),
1341        ];
1342
1343        for (sample, expected_output) in samples {
1344            println!("{}, {:?}", sample, expected_output);
1345            let res = Tera::one_off(
1346                &format!("{{% if {} %}}true{{% endif %}}", sample),
1347                &Context::new(),
1348                true,
1349            );
1350            if let Some(output) = expected_output {
1351                assert!(res.is_ok());
1352                assert_eq!(res.unwrap(), output);
1353            } else {
1354                assert!(res.is_err());
1355            }
1356        }
1357    }
1358
1359    // https://github.com/Keats/tera/issues/819
1360    #[test]
1361    fn empty_list_on_invalid_glob() {
1362        let tera = Tera::new("\\dev/null/*");
1363        println!("{:?}", tera);
1364        assert!(tera.is_ok());
1365        assert!(tera.unwrap().templates.is_empty());
1366    }
1367}