moostache/
lib.rs

1// moostache readme rendered on docs.rs
2#![doc = include_str!("./lib.md")]
3#![deny(missing_docs)]
4#![warn(clippy::pedantic)]
5// ignored lints
6#![allow(
7    clippy::needless_pass_by_value,
8    clippy::enum_glob_use,
9    clippy::enum_variant_names,
10)]
11
12use fnv::FnvBuildHasher;
13use lru::LruCache;
14use serde::Serialize;
15use serde_json::json;
16use winnow::{
17    ascii::multispace0,
18    combinator::{alt, cut_err, delimited, repeat, separated},
19    error::{AddContext, ErrMode, ErrorKind, ParserError as WParserError},
20    stream::{FindSlice, Stream},
21    token::{literal, take_while},
22    PResult,
23    Parser,
24    Stateful,
25};
26use yoke::{Yoke, Yokeable};
27use std::{
28    borrow::{Borrow, Cow},
29    cell::RefCell,
30    collections::HashMap,
31    fmt::{Debug, Display},
32    fs,
33    hash::{BuildHasher, BuildHasherDefault, Hash},
34    io::{self, Write},
35    num::NonZeroUsize,
36    ops::Deref,
37    path::{Path, PathBuf, MAIN_SEPARATOR_STR},
38    rc::Rc,
39    str,
40};
41use walkdir::WalkDir;
42
43#[cfg(test)]
44mod tests;
45
46/////////////
47// PARSING //
48/////////////
49
50// Source strings are parsed into template fragments.
51// A "compiled" template is just a list of fragments
52// and list of "section skips". See SectionSkip.
53#[derive(PartialEq, Debug)]
54enum Fragment<'src> {
55    Literal(&'src str),
56    EscapedVariable(&'src str),
57    UnescapedVariable(&'src str),
58    Section(&'src str),
59    InvertedSection(&'src str),
60    Partial(&'src str),
61}
62
63// We have a stateful parser, and that state
64// is maintained in this struct.
65#[derive(Debug)]
66struct State<'src, 'skips> {
67    fragment_index: usize,
68    section_index: usize,
69    section_starts: Vec<SectionMeta<'src>>,
70    section_skips: &'skips mut Vec<SectionSkip>,
71}
72
73// Things our stateful parser needs to keep track of.
74// Mostly this is for checking that all sections which
75// are opened are correctly closed, and also for calculating
76// section skips.
77impl<'src, 'skips> State<'src, 'skips> {
78    fn visited_fragment(&mut self) {
79        self.fragment_index += 1;
80    }
81    fn visited_section_start(&mut self, name: &'src str) {
82        self.section_starts.push(SectionMeta {
83            name,
84            section_index: self.section_index,
85            fragment_index: self.fragment_index,
86        });
87        self.section_skips.push(SectionSkip {
88            nested_sections: 0,
89            nested_fragments: 0,
90        });
91        self.fragment_index += 1;
92        self.section_index += 1;
93    }
94    fn visited_section_end(&mut self, name: &'src str) -> Result<(), ()> {
95        let start = self.section_starts
96            .pop()
97            .ok_or(())?;
98        if start.name != name {
99            return Err(());
100        }
101        let skip = &mut self.section_skips[start.section_index];
102        skip.nested_sections = u16::try_from((self.section_index - 1) - start.section_index)
103            .expect("can't have more than 65k sections within a section");
104        skip.nested_fragments = u16::try_from((self.fragment_index - 1) - start.fragment_index)
105            .expect("can't have more than 65k fragments within a section");
106        Ok(())
107    }
108    fn still_expecting_section_ends(&self) -> bool {
109        !self.section_starts.is_empty()
110    }
111}
112
113// Just data we keep track of in our parser state.
114// Helps calculate section skips.
115#[derive(Debug)]
116struct SectionMeta<'src> {
117    name: &'src str,
118    section_index: usize,
119    fragment_index: usize,
120}
121
122// As you may have noticed in Fragment, there's a
123// Section and InvertedSection variant but there's
124// no SectionEnd variant, so how do we know where
125// sections end? Section ends are maintained in a list
126// of SectionSkips. The first section in the list
127// correponds to the first section that appears in the
128// source template, and so is the same for the second,
129// third, and so on. A "section skip" basically tells us
130// how many nested sections and fragments are in a given
131// section, so if the section value is falsy and we need
132// to skip over it during render, we can do so quickly
133// and efficiently since we know exactly where it ends
134// in the fragment list.
135#[derive(Debug, PartialEq)]
136struct SectionSkip {
137    nested_sections: u16,
138    nested_fragments: u16,
139}
140
141type Input<'src, 'skips> = Stateful<&'src str, State<'src, 'skips>>;
142
143// We can't do "impl Input { fn new()" because Input is a type
144// alias and not a newtype.
145#[inline]
146fn new_input<'src, 'skips>(template: &'src str, skips: &'skips mut Vec<SectionSkip>) -> Input<'src, 'skips> {
147    Input {
148        input: template,
149        state: State {
150            fragment_index: 0,
151            section_index: 0,
152            section_starts: Vec::new(),
153            section_skips: skips,
154        },
155    }
156}
157
158// parses a source string into a compiled Template
159fn parse<S: Into<Cow<'static, str>>>(source: S) -> Result<Template, InternalError> {
160    let source = match source.into() {
161        Cow::Owned(s) => Yoke::attach_to_cart(s, |s| &s[..]).wrap_cart_in_option(),
162        Cow::Borrowed(s) => Yoke::new_owned(s),
163    };
164    let mut skips = Vec::new();
165
166    let fragments = source.try_map_project(|source, _| {
167        let input = new_input(source, &mut skips);
168        match _parse.parse(input) {
169            Ok(frags) => Ok(Fragments(frags)),
170            Err(err) => Err(err.into_inner()),
171        }
172    })?;
173
174    Ok(Template { fragments, skips })
175}
176
177// parses a source string into a compiled Template
178#[inline]
179fn _parse<'src>(
180    input: &mut Input<'src, '_>,
181) -> PResult<Vec<Fragment<'src>>, InternalError> {
182    if input.input.is_empty() {
183        return Err(ErrMode::Cut(InternalError::ParseErrorNoContent));
184    }
185
186    let frags = repeat(1.., alt((
187        parse_literal.map(Some),
188        parse_section_end.map(|()| None),
189        parse_section_start.map(Some),
190        parse_inverted_section_start.map(Some),
191        parse_unescaped_variable.map(Some),
192        parse_comment.map(|()| None),
193        parse_partial.map(Some),
194        parse_escaped_variable.map(Some),
195    )))
196        .fold(Vec::new, |mut acc, item: Option<Fragment>| {
197            if let Some(item) = item {
198                acc.push(item);
199            }
200            acc
201        })
202        .parse_next(input)?;
203
204    // means we had unclosed sections
205    if input.state.still_expecting_section_ends() {
206        return Err(ErrMode::Cut(InternalError::ParseErrorUnclosedSectionTags));
207    }
208
209    Ok(frags)
210}
211
212// parses a fragment literal, i.e. anything that doesn't begin with
213// {{, until it reaches a {{ or EOF
214fn parse_literal<'src>(
215    input: &mut Input<'src, '_>,
216) -> PResult<Fragment<'src>, InternalError> {
217    if input.is_empty() {
218        return Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric));
219    }
220
221    if let Some(range) = input.input.find_slice("{{") {
222        if range.start == 0 {
223            return Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric));
224        }
225        let literal = &input.input[..range.start];
226        let frag = Fragment::Literal(literal);
227        input.input = &input.input[range.start..];
228        input.state.visited_fragment();
229        Ok(frag)
230    } else {
231        let frag = Fragment::Literal(input);
232        input.input = &input.input[input.input.len()..];
233        input.state.visited_fragment();
234        Ok(frag)
235    }
236}
237
238// valid variable names can only contain alphanumeric,
239// dash, or underscore chars
240fn is_variable_name(c: char) -> bool {
241    matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')
242}
243
244// valid variable names must be at least 1 char long, and
245// must only contain valid variable chars
246fn parse_variable_name<'src>(
247    input: &mut Input<'src, '_>,
248) -> PResult<&'src str, InternalError> {
249    take_while(1.., is_variable_name)
250        .parse_next(input)
251}
252
253// a variable "path" can potentially be several variable names
254// delimited by dots, e.g. some.variable.path
255fn parse_variable_path<'src>(
256    input: &mut Input<'src, '_>,
257) -> PResult<&'src str, InternalError> {
258    delimited(
259        multispace0,
260        alt((
261            separated(
262                1..,
263                parse_variable_name, 
264                '.'
265            ).map(|()| ()).take(),
266            literal("."),
267        )),
268        multispace0,
269    )
270        .parse_next(input)
271}
272
273// parses an escaped variable, e.g. {{ some.variable }}
274fn parse_escaped_variable<'src>(
275    input: &mut Input<'src, '_>,
276) -> PResult<Fragment<'src>, InternalError> {
277    let result = delimited(
278        literal("{{"),
279        cut_err(parse_variable_path),
280        cut_err(literal("}}"))
281    )
282        .context(InternalError::ParseErrorInvalidEscapedVariableTag)
283        .parse_next(input)
284        .map(Fragment::EscapedVariable);
285    if result.is_ok() {
286        input.state.visited_fragment();
287    }
288    result
289}
290
291// parses an unescaped variable, e.g. {{{ some.variable }}}
292fn parse_unescaped_variable<'src>(
293    input: &mut Input<'src, '_>,
294) -> PResult<Fragment<'src>, InternalError> {
295    let result = delimited(
296        literal("{{{"),
297        cut_err(parse_variable_path),
298        cut_err(literal("}}}"))
299    )
300        .context(InternalError::ParseErrorInvalidUnescapedVariableTag)
301        .parse_next(input)
302        .map(Fragment::UnescapedVariable);
303    if result.is_ok() {
304        input.state.visited_fragment();
305    }
306    result
307}
308
309// parses a comment, e.g. {{! comment }}
310fn parse_comment(
311    input: &mut Input<'_, '_>
312) -> PResult<(), InternalError> {
313    if input.input.starts_with("{{!") {
314        if let Some(range) = input.input.find_slice("}}") {
315            input.input = &input.input[range.end..];
316            return Ok(());
317        }
318        return Err(ErrMode::Cut(InternalError::ParseErrorInvalidCommentTag));
319    }
320    Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric))
321}
322
323// parses a section start, e.g. {{# section.start }}
324fn parse_section_start<'src>(
325    input: &mut Input<'src, '_>
326) -> PResult<Fragment<'src>, InternalError> {
327    let variable = delimited(
328        literal("{{#"),
329        cut_err(parse_variable_path),
330        cut_err(literal("}}")),
331    )
332        .context(InternalError::ParseErrorInvalidSectionStartTag)
333        .parse_next(input)?;
334
335    input.state.visited_section_start(variable);
336
337    Ok(Fragment::Section(variable))
338}
339
340// parses an inverted section start, e.g. {{^ inverted.section.start }}
341fn parse_inverted_section_start<'src>(
342    input: &mut Input<'src, '_>,
343) -> PResult<Fragment<'src>, InternalError> {
344    let variable = delimited(
345        literal("{{^"),
346        cut_err(parse_variable_path),
347        cut_err(literal("}}")),
348    )
349        .context(InternalError::ParseErrorInvalidInvertedSectionStartTag)
350        .parse_next(input)?;
351
352    input.state.visited_section_start(variable);
353
354    Ok(Fragment::InvertedSection(variable))
355}
356
357// parses a section end, e.g. {{/ section.end }}
358fn parse_section_end(
359    input: &mut Input<'_, '_>,
360) -> PResult<(), InternalError> {
361    let variable = delimited(
362        literal("{{/"),
363        cut_err(parse_variable_path),
364        cut_err(literal("}}")),
365    )
366        .context(InternalError::ParseErrorInvalidSectionEndTag)
367        .parse_next(input)?;
368
369    if input.state.visited_section_end(variable).is_err() {
370        return Err(ErrMode::Cut(InternalError::ParseErrorMismatchedSectionEndTag));
371    }
372
373    Ok(())
374}
375
376// there's way more valid file name chars than
377// variable chars, so we calculate a bitfield
378// at compile time of all of the chars that
379// can appear in a file name
380const fn valid_file_chars() -> u128 {
381    let mut bitfield = 0u128;
382
383    let mut b = b'0';
384    while b <= b'9' {
385        bitfield |= 1u128 << b;
386        b += 1;
387    }
388
389    b = b'a';
390    while b <= b'z' {
391        bitfield |= 1u128 << b;
392        b += 1;
393    }
394
395    b = b'A';
396    while b <= b'Z' {
397        bitfield |= 1u128 << b;
398        b += 1;
399    }
400
401    let bytes = b"_-.,!@#$%^&()+=[]~";
402    let mut i = 0;
403    while i < bytes.len() {
404        b = bytes[i];
405        bitfield |= 1u128 << b;
406        i += 1;
407    }
408
409    bitfield
410}
411
412// store bitfield in const
413const VALID_FILE_CHARS: u128 = valid_file_chars();
414
415// checks if char is a valid file name char
416#[inline]
417fn is_file_name(c: char) -> bool {
418    c.is_ascii() && (VALID_FILE_CHARS & (1u128 << c as u32)) != 0
419}
420
421// parses a file name, must be at least 1 char long, and
422// can contain any valid file name char, with these notable
423// exceptions: "{" (used for mustache tags), "}" (used for
424// mustache tags), " " (whitespace, used as a delimiter)
425fn parse_file_name<'src>(
426    input: &mut Input<'src, '_>
427) -> PResult<&'src str, InternalError> {
428    take_while(1.., is_file_name)
429        .parse_next(input)
430}
431
432// parses a file path, i.e. a list of file names delimited
433// by slashes, e.g. some/file/path
434fn parse_file_path<'src>(
435    input: &mut Input<'src, '_>,
436) -> PResult<&'src str, InternalError> {
437    delimited(
438        multispace0,
439        separated(
440            1..,
441            parse_file_name, 
442            '/'
443        ).map(|()| ()).take(),
444        multispace0,
445    )
446        .parse_next(input)
447}
448
449// parses a partial, e.g. {{> some/file/path }}
450fn parse_partial<'src>(
451    input: &mut Input<'src, '_>,
452) -> PResult<Fragment<'src>, InternalError> {
453    let result = delimited(
454        literal("{{>"),
455        cut_err(parse_file_path),
456        cut_err(literal("}}")),
457    )
458        .context(InternalError::ParseErrorInvalidPartialTag)
459        .parse_next(input)
460        .map(Fragment::Partial);
461    if result.is_ok() {
462        input.state.visited_fragment();
463    }
464    result
465}
466
467/// A compiled moostache template.
468/// 
469/// ### Examples
470/// 
471/// ```rust
472/// use moostache::Template;
473/// use serde_json::json;
474/// 
475/// let template = Template::parse("hello {{name}}!").unwrap();
476/// let data = json!({"name": "John"});
477/// let rendered = template.render_no_partials_to_string(&data).unwrap();
478/// assert_eq!(rendered, "hello John!");
479/// ```
480pub struct Template {
481    // parsed template fragments
482    fragments: Yoke<Fragments<'static>, Option<String>>,
483    // parsed section skips, i.e. tell us where sections end
484    skips: Vec<SectionSkip>,
485}
486#[derive(Yokeable)]
487struct Fragments<'src>(Vec<Fragment<'src>>);
488
489impl Debug for Template {
490    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        f.debug_struct("Template")
492            .field("fragments", &self.fragments.get().0)
493            .field("skips", &self.skips)
494            .finish()
495    }
496}
497impl PartialEq for Template {
498    fn eq(&self, other: &Self) -> bool {
499        self.fragments.get().0 == other.fragments.get().0 && self.skips == other.skips
500    }
501}
502
503impl Template {
504    /// Parse a [`&'static str`](std::str) or [`String`] into a compiled
505    /// moostache template.
506    /// 
507    /// ### Errors
508    /// 
509    /// Returns a [`MoostacheError`] parse error enum variant
510    /// if parsing fails for whatever reason.
511    #[inline]
512    pub fn parse<S: Into<Cow<'static, str>>>(source: S) -> Result<Template, MoostacheError> {
513        match parse(source) {
514            Err(err) => {
515                Err(MoostacheError::from_internal(err, String::new()))
516            },
517            Ok(template) => Ok(template),
518        }
519    }
520
521    /// Render this template.
522    /// 
523    /// ### Errors
524    /// 
525    /// If using [`HashMapLoader`] or [`FileLoader`] this function
526    /// can return any enum variant of [`MoostacheError`].
527    #[inline]
528    pub fn render<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write>(
529        &self,
530        loader: &T,
531        value: &serde_json::Value,
532        writer: &mut W,
533    ) -> Result<(), T::Error> {
534        let mut scopes = Vec::new();
535        scopes.push(value);
536        _render(
537            &self.fragments.get().0,
538            &self.skips,
539            loader,
540            &mut scopes,
541            writer
542        )
543    }
544
545    /// Render this template given a type that impls
546    /// [`serde::Serialize`].
547    /// 
548    /// ### Errors
549    /// 
550    /// If using [`HashMapLoader`] or [`FileLoader`] this function
551    /// can return any enum variant of [`MoostacheError`].
552    #[inline]
553    pub fn render_serializable<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write, S: Serialize>(
554        &self,
555        loader: &T,
556        serializeable: &S,
557        writer: &mut W,
558    ) -> Result<(), T::Error> {
559        let value = serde_json::to_value(serializeable)
560            .map_err(|_| MoostacheError::SerializationError)?;
561        self.render(
562            loader,
563            &value,
564            writer
565        )
566    }
567
568    /// Render this template, assuming it has no partial tags.
569    /// 
570    /// ### Errors
571    /// 
572    /// Returns a [`MoostacheError`] if the template contains a
573    /// partial, or serializing data during render fails for
574    /// whatever reason.
575    #[inline]
576    pub fn render_no_partials<W: Write>(
577        &self,
578        value: &serde_json::Value,
579        writer: &mut W,
580    ) -> Result<(), MoostacheError> {
581        self.render(
582            &(),
583            value,
584            writer
585        )
586    }
587
588    /// Render this template using a type which impls
589    /// [`serde::Serialize`] and assuming it has no partials.
590    /// 
591    /// ### Errors
592    /// 
593    /// Returns a [`MoostacheError`] if the template contains a
594    /// partial, or serializing data during render fails for
595    /// whatever reason.
596    #[inline]
597    pub fn render_serializable_no_partials<S: Serialize, W: Write>(
598        &self,
599        serializeable: &S,
600        writer: &mut W,
601    ) -> Result<(), MoostacheError> {
602        self.render_serializable(
603            &(),
604            serializeable,
605            writer
606        )
607    }
608
609    /// Render this template to a [`String`].
610    /// 
611    /// ### Errors
612    /// 
613    /// If using [`HashMapLoader`] or [`FileLoader`] this function
614    /// can return any enum variant of [`MoostacheError`].
615    #[inline]
616    pub fn render_to_string<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized>(
617        &self,
618        loader: &T,
619        value: &serde_json::Value,
620    ) -> Result<String, T::Error> {
621        let mut writer = Vec::<u8>::new();
622        self.render(
623            loader,
624            value,
625            &mut writer
626        )?;
627        let rendered = unsafe {
628            // SAFETY: templates are utf8 and value
629            // is utf8 so we know templates + value
630            // will also be utf8
631            debug_assert!(str::from_utf8(&writer).is_ok());
632            String::from_utf8_unchecked(writer)
633        };
634        Ok(rendered)
635    }
636
637    /// Render this template assuming it has no partial tags
638    /// and return the result as a [`String`].
639    /// 
640    /// ### Errors
641    /// 
642    /// Returns a [`MoostacheError`] if the template contains a
643    /// partial, or serializing data during render fails for
644    /// whatever reason.
645    #[inline]
646    pub fn render_no_partials_to_string(
647        &self,
648        value: &serde_json::Value,
649    ) -> Result<String, MoostacheError> {
650        self.render_to_string(
651            &(),
652            value,
653        )
654    }
655
656    /// Render this template given a type which impls
657    /// [`serde::Serialize`] and return result as a [`String`].
658    /// 
659    /// ### Errors
660    /// 
661    /// If using [`HashMapLoader`] or [`FileLoader`] this function
662    /// can return any enum variant of [`MoostacheError`].
663    #[inline]
664    pub fn render_serializable_to_string<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, S: Serialize>(
665        &self,
666        loader: &T,
667        serializable: &S,
668    ) -> Result<String, T::Error> {
669        let value = serde_json::to_value(serializable)
670            .map_err(|_| MoostacheError::SerializationError)?;
671        self.render_to_string(
672            loader,
673            &value,
674        )
675    }
676
677    /// Render this template given a type which impls
678    /// [`serde::Serialize`], assume it has no partials,
679    /// and return result as a [`String`].
680    /// 
681    /// ### Errors
682    /// 
683    /// Returns a [`MoostacheError`] if the template contains a
684    /// partial, or serializing data during render fails for
685    /// whatever reason.
686    #[inline]
687    pub fn render_serializable_no_partials_to_string<S: Serialize>(
688        &self,
689        serializable: &S,
690    ) -> Result<String, MoostacheError> {
691        let value = serde_json::to_value(serializable)
692            .map_err(|_| MoostacheError::SerializationError)?;
693        self.render_to_string(
694            &(),
695            &value,
696        )
697    }
698}
699
700// note: can't do impl<S: Into<ImmutableStr> TryFrom<S> below
701// because compiler complains that generic impl overlaps
702// with another generic impl in the std lib, so we do separate
703// impls for &'static str and String
704
705impl TryFrom<&'static str> for Template {
706    type Error = MoostacheError;
707    fn try_from(source: &'static str) -> Result<Self, Self::Error> {
708        Self::parse(source)
709    }
710}
711
712impl TryFrom<String> for Template {
713    type Error = MoostacheError;
714    fn try_from(source: String) -> Result<Self, Self::Error> {
715        Self::parse(source)
716    }
717}
718
719//////////////////////
720// TEMPLATE LOADERS //
721//////////////////////
722
723/// Loads templates and renders them.
724pub trait TemplateLoader<K: Borrow<str> + Eq + Hash = String> {
725    /// Output type of the [`get`](TemplateLoader::get) method.
726    type Output<'a>: Deref<Target = Template> + 'a where Self: 'a;
727    /// Error type of the [`get`](TemplateLoader::get) and render methods.
728    type Error: From<MoostacheError>;
729    
730    /// Get a template by name.
731    /// 
732    /// ### Errors
733    /// 
734    /// Returns an [`Error`](TemplateLoader::Error) if getting
735    /// the template fails for whatever reason. In [`HashMapLoader`]
736    /// this would only ever return
737    /// [`MoostacheError::LoaderErrorTemplateNotFound`] since it
738    /// either has the template or it doesn't. In [`FileLoader`] it
739    /// can return almost any enum variant of [`MoostacheError`]
740    /// since it lazily loads and compiles templates on-demand.
741    fn get<'a>(&'a self, name: &str) -> Result<Self::Output<'a>, Self::Error>;
742
743    /// Insert a template by name.
744    fn insert(&mut self, name: K, value: Template) -> Option<Template>;
745
746    /// Remove a template by name.
747    fn remove(&mut self, name: &str) -> Option<Template>;
748    
749    /// Render a template by name, using a [`serde_json::Value`]
750    /// as data and writing output to a [`&mut impl Write`](std::io::Write).
751    /// 
752    /// ### Errors
753    /// 
754    /// If using [`HashMapLoader`] or [`FileLoader`] this function
755    /// can return any enum variant of [`MoostacheError`].
756    #[inline]
757    fn render<W: Write>(
758        &self,
759        name: &str,
760        value: &serde_json::Value,
761        writer: &mut W,
762    ) -> Result<(), Self::Error> {
763        let template = self.get(name)?;
764        template.render(self, value, writer)
765    }
766
767    /// Render a template by name, using a type which impls
768    /// [`serde::Serialize`] as data and writing output to a
769    /// [`&mut impl Write`](std::io::Write).
770    /// 
771    /// ### Errors
772    /// 
773    /// If using [`HashMapLoader`] or [`FileLoader`] this function
774    /// can return any enum variant of [`MoostacheError`].
775    #[inline]
776    fn render_serializable<W: Write, S: Serialize>(
777        &self,
778        name: &str,
779        serializeable: &S,
780        writer: &mut W,
781    ) -> Result<(), Self::Error> {
782        let value = serde_json::to_value(serializeable)
783            .map_err(|_| MoostacheError::SerializationError)?;
784        self.render(
785            name,
786            &value,
787            writer
788        )
789    }
790
791    /// Renders a template by name, using a [`serde_json::Value`]
792    /// as data and returning the output as a [`String`].
793    /// 
794    /// ### Errors
795    /// 
796    /// If using [`HashMapLoader`] or [`FileLoader`] this function
797    /// can return any enum variant of [`MoostacheError`].
798    #[inline]
799    fn render_to_string(
800        &self,
801        name: &str,
802        value: &serde_json::Value,
803    ) -> Result<String, Self::Error> {
804        let mut writer = Vec::<u8>::new();
805        self.render(
806            name,
807            value,
808            &mut writer
809        )?;
810        let rendered = unsafe {
811            // SAFETY: templates are utf8 and value
812            // is utf8 so we know templates + value
813            // will also be utf8
814            debug_assert!(str::from_utf8(&writer).is_ok());
815            String::from_utf8_unchecked(writer)
816        };
817        Ok(rendered)
818    }
819
820    /// Renders a template by name, using a type which impls
821    /// [`serde::Serialize`] as data and returning the output
822    /// as a [`String`].
823    /// 
824    /// ### Errors
825    /// 
826    /// If using [`HashMapLoader`] or [`FileLoader`] this function
827    /// can return any enum variant of [`MoostacheError`].
828    #[inline]
829    fn render_serializable_to_string<S: Serialize>(
830        &self,
831        name: &str,
832        serializable: &S,
833    ) -> Result<String, Self::Error> {
834        let value = serde_json::to_value(serializable)
835            .map_err(|_| MoostacheError::SerializationError)?;
836        self.render_to_string(
837            name,
838            &value,
839        )
840    }
841}
842
843/// Useful struct for creating [`HashMapLoader`]s or
844/// [`FileLoader`]s.
845/// 
846/// ### Examples
847/// 
848/// Creating a [`HashMapLoader`]:
849/// 
850/// ```rust
851/// use moostache::{LoaderConfig, HashMapLoader};
852/// 
853/// let loader = HashMapLoader::try_from(LoaderConfig::default()).unwrap();
854/// ```
855/// 
856/// Creating a [`FileLoader`]:
857/// 
858/// ```rust
859/// use moostache::{LoaderConfig, FileLoader};
860/// 
861/// let loader = FileLoader::try_from(LoaderConfig::default()).unwrap();
862/// ```
863/// 
864/// [`LoaderConfig`] default values:
865/// 
866/// ```rust
867/// use moostache::LoaderConfig;
868/// 
869/// assert_eq!(
870///     LoaderConfig::default(),
871///     LoaderConfig {
872///         templates_directory: "./templates/",
873///         templates_extension: ".html",
874///         cache_size: 200,
875///     },
876/// );
877/// ```
878#[derive(Clone, Debug, PartialEq)]
879pub struct LoaderConfig<'a> {
880    /// Directory to load templates from.
881    pub templates_directory: &'a str,
882    /// File extension of template files.
883    pub templates_extension: &'a str,
884    /// Max number of compiled templates to cache in memory.
885    pub cache_size: usize,
886}
887
888#[cfg(windows)]
889const DEFAULT_TEMPLATES_DIRECTORY: &str = ".\\templates\\";
890
891#[cfg(not(windows))]
892const DEFAULT_TEMPLATES_DIRECTORY: &str = "./templates/";
893
894impl Default for LoaderConfig<'_> {
895    fn default() -> Self {
896        Self {
897            templates_directory: DEFAULT_TEMPLATES_DIRECTORY,
898            templates_extension: ".html",
899            cache_size: 200,
900        }
901    }
902}
903
904impl TryFrom<LoaderConfig<'_>> for HashMapLoader {
905    type Error = MoostacheError;
906    fn try_from(config: LoaderConfig<'_>) -> Result<Self, MoostacheError> {
907        let mut dir: String = config.templates_directory.into();
908        if !dir.ends_with(MAIN_SEPARATOR_STR) {
909            dir.push_str(MAIN_SEPARATOR_STR);
910        }
911        let dir_path: &Path = dir.as_ref();
912        let mut ext: String = config.templates_extension.into();
913        if !ext.starts_with('.') {
914            ext.insert(0, '.');
915        }
916        let max_size = NonZeroUsize::new(config.cache_size)
917            .ok_or(MoostacheError::ConfigErrorNonPositiveCacheSize)?;
918        let max_size: usize = max_size.into();
919
920        if !dir_path.is_dir() {
921            return Err(MoostacheError::ConfigErrorInvalidTemplatesDirectory(dir_path.into()));
922        }
923
924        let mut current_size = 0usize;
925        let mut templates: HashMap<String, Template, FnvBuildHasher> = HashMap::with_hasher(BuildHasherDefault::default());
926        for entry in WalkDir::new(dir_path).into_iter().filter_map(Result::ok) {
927            if entry.file_type().is_file() {
928                let entry_path = entry.path();
929                let entry_path_str = entry_path
930                    .to_str()
931                    .ok_or_else(|| MoostacheError::LoaderErrorNonUtf8FilePath(entry_path.into()))?;
932                if entry_path_str.ends_with(&ext) {
933                    let name = entry_path_str
934                        .strip_prefix(&dir)
935                        .and_then(|path| path.strip_suffix(&ext))
936                        .unwrap()
937                        .to_string();
938                    let source = fs::read_to_string(entry_path)
939                        .map_err(|err| MoostacheError::from_io(err, name.clone()))?;
940                    let template = Template::parse(source)
941                        .map_err(|err| err.set_name(&name))?;
942                    templates.insert(name, template);
943                    current_size += 1;
944                    if current_size > max_size {
945                        return Err(MoostacheError::ConfigErrorTooManyTemplates);
946                    }
947                }
948            }
949        }
950
951        Ok(HashMapLoader {
952            templates
953        })
954    }
955}
956
957/// Stores all templates in memory.
958/// 
959/// ### Examples
960/// 
961/// Creating a [`HashMapLoader`] from a hashmap:
962/// 
963/// ```rust
964/// use moostache::HashMapLoader;
965/// use maplit::hashmap;
966/// 
967/// let loader = HashMapLoader::try_from(hashmap! {
968///    "greet" => "hello {{name}}!",
969/// }).unwrap();
970/// ```
971/// 
972/// Creating a [`HashMapLoader`] from a [`LoaderConfig`]:
973/// 
974/// ```rust
975/// use moostache::{LoaderConfig, HashMapLoader};
976/// 
977/// let loader = HashMapLoader::try_from(LoaderConfig::default()).unwrap();
978/// ```
979#[derive(Debug)]
980pub struct HashMapLoader<K: Borrow<str> + Eq + Hash = String, H: BuildHasher + Default = FnvBuildHasher> {
981    templates: HashMap<K, Template, H>,
982}
983
984impl<K: Borrow<str> + Eq + Hash, H: BuildHasher + Default> TemplateLoader<K> for HashMapLoader<K, H> {
985    type Output<'a> = &'a Template where K: 'a, H: 'a;
986    type Error = MoostacheError;
987    fn get(&self, name: &str) -> Result<&Template, MoostacheError> {
988        self.templates.get(name)
989            .ok_or_else(|| MoostacheError::LoaderErrorTemplateNotFound(name.into()))
990    }
991    fn insert(&mut self, name: K, value: Template) -> Option<Template> {
992        self.templates.insert(name, value)
993    }
994    fn remove(&mut self, name: &str) -> Option<Template> {
995        self.templates.remove(name)
996    }
997}
998
999/// Lazily loads templates on-demand during render. Caches
1000/// some compiled templates in memory.
1001/// 
1002/// ### Examples
1003/// 
1004/// Creating a [`FileLoader`] from a [`LoaderConfig`]:
1005/// 
1006/// ```rust
1007/// use moostache::{LoaderConfig, FileLoader};
1008/// 
1009/// let loader = FileLoader::try_from(LoaderConfig::default()).unwrap();
1010/// ```
1011#[derive(Debug)]
1012pub struct FileLoader<H: BuildHasher + Default = FnvBuildHasher> {
1013    templates_directory: String,
1014    templates_extension: String,
1015    path_buf: RefCell<String>,
1016    templates: RefCell<LruCache<String, Rc<Template>, H>>,
1017}
1018
1019impl TemplateLoader for FileLoader {
1020    type Output<'a> = Rc<Template>;
1021    type Error = MoostacheError;
1022    fn get(&self, name: &str) -> Result<Rc<Template>, MoostacheError> {
1023        let mut templates = self.templates.borrow_mut();
1024        let template = templates.get(name);
1025        if let Some(template) = template {
1026            return Ok(Rc::clone(template));
1027        }
1028        let mut path_buf = self.path_buf.borrow_mut();
1029        path_buf.clear();
1030        path_buf.push_str(&self.templates_directory);
1031        path_buf.push_str(name);
1032        path_buf.push_str(&self.templates_extension);
1033        let source = fs::read_to_string::<&Path>(path_buf.as_ref())
1034            .map_err(|err| MoostacheError::from_io(err, name.into()))?;
1035        let template = Template::parse(source)
1036            .map_err(|err| err.set_name(name))?;
1037        let template = Rc::new(template);
1038        templates.put(name.into(), Rc::clone(&template));
1039        Ok(template)
1040    }
1041    fn insert(&mut self, name: String, value: Template) -> Option<Template> {
1042        let option = self.templates
1043            .borrow_mut()
1044            .put(name, Rc::new(value));
1045        match option {
1046            Some(template) => {
1047                Rc::into_inner(template)
1048            },
1049            None => None,
1050        }
1051    }
1052    fn remove(&mut self, name: &str) -> Option<Template> {
1053        let option = self.templates
1054            .borrow_mut()
1055            .pop(name);
1056        match option {
1057            Some(template) => {
1058                Rc::into_inner(template)
1059            },
1060            None => None,
1061        }
1062    }
1063}
1064
1065impl TryFrom<LoaderConfig<'_>> for FileLoader {
1066    type Error = MoostacheError;
1067    fn try_from(config: LoaderConfig<'_>) -> Result<Self, MoostacheError> {
1068        let mut dir: String = config.templates_directory.into();
1069        if !dir.ends_with(MAIN_SEPARATOR_STR) {
1070            dir.push_str(MAIN_SEPARATOR_STR);
1071        }
1072        let dir_path: &Path = dir.as_ref();
1073        let mut ext: String = config.templates_extension.into();
1074        if !ext.starts_with('.') {
1075            ext.insert(0, '.');
1076        }
1077        let max_size = NonZeroUsize::new(config.cache_size)
1078            .ok_or(MoostacheError::ConfigErrorNonPositiveCacheSize)?;
1079
1080        if !dir_path.is_dir() {
1081            return Err(MoostacheError::ConfigErrorInvalidTemplatesDirectory(dir_path.into()));
1082        }
1083
1084        let templates = RefCell::new(LruCache::with_hasher(max_size, BuildHasherDefault::default()));
1085
1086        Ok(FileLoader {
1087            templates_directory: dir,
1088            templates_extension: ext,
1089            path_buf: RefCell::new(String::new()),
1090            templates,
1091        })
1092    }
1093}
1094
1095impl<K: Borrow<str> + Eq + Hash, V: Into<Cow<'static, str>>> TryFrom<HashMap<K, V>> for HashMapLoader<K> {
1096    type Error = MoostacheError;
1097    fn try_from(map: HashMap<K, V>) -> Result<Self, Self::Error> {
1098        let templates = map
1099            .into_iter()
1100            .map(|(key, value)| {
1101                match parse(value) {
1102                    Ok(template) => Ok((key, template)),
1103                    Err(err) => Err(MoostacheError::from_internal(err, key.borrow().to_owned())),
1104                }
1105            })
1106            .collect::<Result<_, _>>();
1107        templates.map(|templates| HashMapLoader {
1108            templates,
1109        })
1110    }
1111}
1112
1113impl TemplateLoader<&'static str> for () {
1114    type Output<'a> = &'a Template;
1115    type Error = MoostacheError;
1116    fn get(&self, name: &str) -> Result<&Template, MoostacheError> {
1117        Err(MoostacheError::LoaderErrorTemplateNotFound(name.into()))
1118    }
1119    fn insert(&mut self, _: &'static str, _: Template) -> Option<Template> {
1120        None
1121    }
1122    fn remove(&mut self, _: &str) -> Option<Template> {
1123        None
1124    }
1125}
1126
1127///////////////
1128// RENDERING //
1129///////////////
1130
1131// checks if serde_json::Value is truthy
1132fn is_truthy(value: &serde_json::Value) -> bool {
1133    use serde_json::Value;
1134    match value {
1135        Value::Null => false,
1136        Value::Bool(b) => *b,
1137        Value::Number(_) => value != &json!(0),
1138        Value::String(string) => !string.is_empty(),
1139        Value::Array(array) => !array.is_empty(),
1140        Value::Object(object) => !object.is_empty(),
1141    }
1142}
1143
1144// wraps a Write type and escapes HTML chars
1145// before writing to the inner Write
1146struct EscapeHtml<'a, W: Write>(&'a mut W);
1147
1148// as recommended by OWASP the chars "&", "<",
1149// ">", "\"", and "'" are escaped
1150impl<W: Write> Write for EscapeHtml<'_, W> {
1151    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1152        let written = buf.len();
1153        self.write_all(buf)
1154            .map(|()| written)
1155    }
1156    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
1157        let mut start = 0;
1158        let mut end = 0;
1159        for byte in buf {
1160            match byte {
1161                b'&' => {
1162                    if start < end {
1163                        self.0.write_all(&buf[start..end])?;
1164                    }
1165                    end += 1;
1166                    start = end;
1167                    self.0.write_all(b"&amp;")?;
1168                },
1169                b'<' => {
1170                    if start < end {
1171                        self.0.write_all(&buf[start..end])?;
1172                    }
1173                    end += 1;
1174                    start = end;
1175                    self.0.write_all(b"&lt;")?;
1176                },
1177                b'>' => {
1178                    if start < end {
1179                        self.0.write_all(&buf[start..end])?;
1180                    }
1181                    end += 1;
1182                    start = end;
1183                    self.0.write_all(b"&gt;")?;
1184                },
1185                b'"' => {
1186                    if start < end {
1187                        self.0.write_all(&buf[start..end])?;
1188                    }
1189                    end += 1;
1190                    start = end;
1191                    self.0.write_all(b"&quot;")?;
1192                },
1193                b'\'' => {
1194                    if start < end {
1195                        self.0.write_all(&buf[start..end])?;
1196                    }
1197                    end += 1;
1198                    start = end;
1199                    self.0.write_all(b"&#x27;")?;
1200                },
1201                _ => {
1202                    end += 1;
1203                },
1204            }
1205        }
1206        if start < end {
1207            self.0.write_all(&buf[start..end])?;
1208        }
1209        Ok(())
1210    }
1211    fn flush(&mut self) -> io::Result<()> {
1212        self.0.flush()
1213    }
1214}
1215
1216// serializes a serde_json::Value
1217fn write_value<W: Write>(
1218    value: &serde_json::Value,
1219    writer: &mut W,
1220) -> Result<(), MoostacheError> {
1221    use serde_json::Value;
1222    match value {
1223        Value::Null => {
1224            // serde_json serializes null as
1225            // "null" but we want this to be
1226            // an empty string instead
1227        },
1228        Value::String(string) => {
1229            // serde_json serializes strings
1230            // wrapped with quotes, but we
1231            // want to write them without quotes
1232            writer.write_all(string.as_bytes())
1233                .map_err(|err| MoostacheError::from_io(err, String::new()))?;
1234        },
1235        // let serde_json handle the rest
1236        _ => {
1237            let mut serializer = serde_json::Serializer::new(writer);
1238            value.serialize(&mut serializer)
1239                .map_err(|_| MoostacheError::SerializationError)?;
1240        },
1241    }
1242    Ok(())
1243}
1244
1245// given a variable path, e.g. variable.path, and a list of scopes,
1246// e.g. serde_json::Values, it resolves the path to the specific
1247// serde_json::Value it points to, or returns serde_json::Value::Null
1248// if it cannot be found
1249fn resolve_value<'a>(path: &str, scopes: &[&'a serde_json::Value]) -> &'a serde_json::Value {
1250    use serde_json::Value;
1251    if path == "." {
1252        return scopes[scopes.len() - 1];
1253    }
1254    let mut resolved_value = &Value::Null;
1255    'parent: for value in scopes.iter().rev() {
1256        resolved_value = *value;
1257        for (idx, key) in path.split('.').enumerate() {
1258            match resolved_value {
1259                Value::Array(array) => {
1260                    // if we're in this branch assume
1261                    // the key is an integer index
1262                    let parsed_index = key.parse::<usize>();
1263                    if let Ok(index) = parsed_index {
1264                        let get_option = array.get(index);
1265                        match get_option {
1266                            Some(get) => {
1267                                resolved_value = get;
1268                            },
1269                            None => {
1270                                return &Value::Null;
1271                            },
1272                        }
1273                    } else {
1274                        // key doesn't exist in this scope
1275                        if idx == 0 {
1276                            // go to parent scope
1277                            continue 'parent;
1278                        }
1279                        return &Value::Null;
1280                    }
1281                },
1282                Value::Object(object) => {
1283                    let get_option = object.get(key);
1284                    if let Some(get) = get_option {
1285                        resolved_value = get;
1286                    } else {
1287                        // key doesn't exist in this scope
1288                        if idx == 0 {
1289                            // go to parent scope
1290                            continue 'parent;
1291                        }
1292                        return &Value::Null;
1293                    }
1294                },
1295                // we got a null, string, or number
1296                // none of which are keyed, return null
1297                _ => {
1298                    // key doesn't exist in this scope
1299                    if idx == 0 {
1300                        // go to parent scope
1301                        continue 'parent;
1302                    }
1303                    return &Value::Null;
1304                }
1305            }
1306        }
1307        return resolved_value;
1308    }
1309    resolved_value
1310}
1311
1312// this function iterates over a list of fragments and writes
1313// each one out to the writer, will call itself recursively
1314// to render sections and partials
1315fn _render<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write>(
1316    frags: &[Fragment<'_>],
1317    skips: &[SectionSkip],
1318    loader: &T,
1319    scopes: &mut Vec<&serde_json::Value>,
1320    writer: &mut W,
1321) -> Result<(), T::Error> {
1322    use serde_json::Value;
1323    let mut frag_idx = 0;
1324    let mut section_idx = 0;
1325    while frag_idx < frags.len() {
1326        let frag = &frags[frag_idx];
1327        match frag {
1328            // write literal to writer
1329            Fragment::Literal(literal) => {
1330                writer.write_all(literal.as_bytes())
1331                    .map_err(|err| MoostacheError::from_io(err, String::new()))?;
1332                frag_idx += 1;
1333            },
1334            // write variable value to writer, escape any html chars
1335            Fragment::EscapedVariable(name) => {
1336                let resolved_value = resolve_value(name, scopes);
1337                write_value(resolved_value, &mut EscapeHtml(writer))?;
1338                frag_idx += 1;
1339            },
1340            // write variable value to writer
1341            Fragment::UnescapedVariable(name) => {
1342                let resolved_value = resolve_value(name, scopes);
1343                write_value(resolved_value, writer)?;
1344                frag_idx += 1;
1345            },
1346            // check if section value is truthy, if not skip it,
1347            // otherwise create an "implicit iterator" over
1348            // the resolved value and render the section content
1349            // that many times
1350            Fragment::Section(name) => {
1351                let resolved_value = resolve_value(name, scopes);
1352                let start_frag = frag_idx + 1;
1353                let end_frag = start_frag + skips[section_idx].nested_fragments as usize;
1354                let start_section = section_idx + 1;
1355                let end_section = start_section + skips[section_idx].nested_sections as usize;
1356                if is_truthy(resolved_value) {
1357                    if let Value::Array(array) = resolved_value {
1358                        for value in array {
1359                            scopes.push(value);
1360                            _render(
1361                                &frags[start_frag..end_frag],
1362                                &skips[start_section..end_section],
1363                                loader,
1364                                scopes,
1365                                writer,
1366                            )?;
1367                            scopes.pop();
1368                        }
1369                    } else {
1370                        scopes.push(resolved_value);
1371                        _render(
1372                            &frags[start_frag..end_frag],
1373                            &skips[start_section..end_section],
1374                            loader,
1375                            scopes,
1376                            writer,
1377                        )?;
1378                        scopes.pop();
1379                    }
1380                }
1381                frag_idx += 1 + skips[section_idx].nested_fragments as usize;
1382                section_idx += 1 + skips[section_idx].nested_sections as usize;
1383            },
1384            // check if invertedsection value is falsey, if not
1385            // skip it, otherwise render inner content
1386            Fragment::InvertedSection(name) => {
1387                let resolved_value = resolve_value(name, scopes);
1388                let start_frag = frag_idx + 1;
1389                let end_frag = start_frag + skips[section_idx].nested_fragments as usize;
1390                let start_section = section_idx + 1;
1391                let end_section = start_section + skips[section_idx].nested_sections as usize;
1392                if !is_truthy(resolved_value) {
1393                    scopes.push(resolved_value);
1394                    _render(
1395                        &frags[start_frag..end_frag],
1396                        &skips[start_section..end_section],
1397                        loader,
1398                        scopes,
1399                        writer,
1400                    )?;
1401                    scopes.pop();
1402                }
1403                frag_idx += 1 + skips[section_idx].nested_fragments as usize;
1404                section_idx += 1 + skips[section_idx].nested_sections as usize;
1405            },
1406            // render partial by loading its content via a TemplateLoader
1407            Fragment::Partial(path) => {
1408                let template = loader.get(path)?;
1409                _render(
1410                    &template.fragments.get().0,
1411                    &template.skips,
1412                    loader,
1413                    scopes,
1414                    writer,
1415                )?;
1416                frag_idx += 1;
1417            },
1418        }
1419    }
1420    Ok(())
1421}
1422
1423////////////
1424// ERRORS //
1425////////////
1426
1427/// Enum of all possible errors that
1428/// moostache can produce.
1429/// 
1430/// The [`String`] in almost every enum variant
1431/// is the name of the template which produced
1432/// the error.
1433#[derive(Debug, Clone, PartialEq)]
1434pub enum MoostacheError {
1435    /// Reading from the filesystem, or writing
1436    /// to a writer, failed for whatever reason.
1437    IoError(String, std::io::ErrorKind),
1438    /// Parsing failed for some generic unidentifiable
1439    /// reason.
1440    ParseErrorGeneric(String),
1441    /// Parsing failed because the template was empty.
1442    ParseErrorNoContent(String),
1443    /// Not all sections were properly closed
1444    /// in the template.
1445    ParseErrorUnclosedSectionTags(String),
1446    /// Some escaped variable tag, e.g. {{ variable }},
1447    /// was invalid.
1448    ParseErrorInvalidEscapedVariableTag(String),
1449    /// Some unescaped variable tag, e.g. {{{ variable }}},
1450    /// was invalid.
1451    ParseErrorInvalidUnescapedVariableTag(String),
1452    /// Some section end tag, e.g. {{/ section }},
1453    /// was invalid.
1454    ParseErrorInvalidSectionEndTag(String),
1455    /// Some section end tag doesn't match its section
1456    /// start tag, e.g. {{# section1 }} ... {{/ section2 }}
1457    ParseErrorMismatchedSectionEndTag(String),
1458    /// Some comment tag, e.g. {{! comment }}, is invalid.
1459    ParseErrorInvalidCommentTag(String),
1460    /// Some section start tag, e.g. {{ section }}, is invalid.
1461    ParseErrorInvalidSectionStartTag(String),
1462    /// Some inverted section start tag, e.g. {{^ section }}, is invalid.
1463    ParseErrorInvalidInvertedSectionStartTag(String),
1464    /// Some partial tag, e.g. {{> partial }}, is invalid.
1465    ParseErrorInvalidPartialTag(String),
1466    /// Loader tried to load a template but couldn't find it by
1467    /// its name.
1468    LoaderErrorTemplateNotFound(String),
1469    /// [`FileLoader`] tried to load a template but its filepath wasn't
1470    /// valid utf-8.
1471    LoaderErrorNonUtf8FilePath(PathBuf),
1472    /// Cache size parameter in [`LoaderConfig`] must be greater than zero.
1473    ConfigErrorNonPositiveCacheSize,
1474    /// Templates directory passed to [`LoaderConfig`] is not a directory.
1475    ConfigErrorInvalidTemplatesDirectory(PathBuf),
1476    /// Tried creating a [`HashMapLoader`] from a [`LoaderConfig`] but there were
1477    /// more templates in the directory than the maximum allowed by cache
1478    /// size so not all templates could be loaded into memory. To fix this
1479    /// increase your cache size or switch to [`FileLoader`].
1480    ConfigErrorTooManyTemplates,
1481    /// moostache uses [`serde_json`] internally, and if [`serde_json`] fails
1482    /// to serialize anything for any reason this error will be returned.
1483    SerializationError,
1484}
1485
1486impl MoostacheError {
1487    fn from_internal(internal: InternalError, s: String) -> Self {
1488        match internal {
1489            InternalError::ParseErrorGeneric => MoostacheError::ParseErrorGeneric(s),
1490            InternalError::ParseErrorNoContent => MoostacheError::ParseErrorNoContent(s),
1491            InternalError::ParseErrorUnclosedSectionTags => MoostacheError::ParseErrorUnclosedSectionTags(s),
1492            InternalError::ParseErrorInvalidEscapedVariableTag => MoostacheError::ParseErrorInvalidEscapedVariableTag(s),
1493            InternalError::ParseErrorInvalidUnescapedVariableTag => MoostacheError::ParseErrorInvalidUnescapedVariableTag(s),
1494            InternalError::ParseErrorInvalidSectionEndTag => MoostacheError::ParseErrorInvalidSectionEndTag(s),
1495            InternalError::ParseErrorMismatchedSectionEndTag => MoostacheError::ParseErrorMismatchedSectionEndTag(s),
1496            InternalError::ParseErrorInvalidCommentTag => MoostacheError::ParseErrorInvalidCommentTag(s),
1497            InternalError::ParseErrorInvalidSectionStartTag => MoostacheError::ParseErrorInvalidSectionStartTag(s),
1498            InternalError::ParseErrorInvalidInvertedSectionStartTag => MoostacheError::ParseErrorInvalidInvertedSectionStartTag(s),
1499            InternalError::ParseErrorInvalidPartialTag => MoostacheError::ParseErrorInvalidPartialTag(s),
1500        }
1501    }
1502    fn set_name(mut self, name: &str) -> Self {
1503        use MoostacheError::*;
1504        match &mut self {
1505            ParseErrorGeneric(s) |
1506            ParseErrorNoContent(s) |
1507            ParseErrorUnclosedSectionTags(s) |
1508            ParseErrorInvalidEscapedVariableTag(s) |
1509            ParseErrorInvalidUnescapedVariableTag(s) |
1510            ParseErrorInvalidSectionEndTag(s) |
1511            ParseErrorMismatchedSectionEndTag(s) |
1512            ParseErrorInvalidCommentTag(s) |
1513            ParseErrorInvalidSectionStartTag(s) |
1514            ParseErrorInvalidInvertedSectionStartTag(s) |
1515            ParseErrorInvalidPartialTag(s) |
1516            IoError(s, _) |
1517            LoaderErrorTemplateNotFound(s) => {
1518                s.clear();
1519                s.push_str(name);
1520            },
1521            _ => unreachable!("trying to set name for parse error"),
1522        };
1523        self
1524    }
1525    fn from_io(io: std::io::Error, s: String) -> Self {
1526        let kind = io.kind();
1527        MoostacheError::IoError(s, kind)
1528    }
1529}
1530
1531impl std::error::Error for MoostacheError {}
1532
1533impl Display for MoostacheError {
1534    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1535        use MoostacheError::*;
1536        fn template_name(name: &str) -> String {
1537            if name.is_empty() {
1538                return "anonymous".to_owned();
1539            }
1540            format!("\"{name}\"")
1541        }
1542        match self {
1543            ParseErrorGeneric(s) => write!(f, "error parsing {} template", template_name(s)),
1544            ParseErrorNoContent(s) => write!(f, "error parsing {} template: empty template", template_name(s)),
1545            ParseErrorUnclosedSectionTags(s) => write!(f, "error parsing {} template: unclosed section tags", template_name(s)),
1546            ParseErrorInvalidEscapedVariableTag(s) => write!(f, "error parsing {} template: invalid escaped variable tag, expected {{{{ variable }}}}", template_name(s)),
1547            ParseErrorInvalidUnescapedVariableTag(s) => write!(f, "error parsing {} template: invalid unescaped variable tag, expected {{{{{{ variable }}}}}}", template_name(s)),
1548            ParseErrorInvalidSectionEndTag(s) => write!(f, "error parsing {} template: invalid section eng tag, expected {{{{/ section }}}}", template_name(s)),
1549            ParseErrorMismatchedSectionEndTag(s) => write!(f, "error parsing {} template: mismatched section eng tag, expected {{{{# section }}}} ... {{{{/ section }}}}", template_name(s)),
1550            ParseErrorInvalidCommentTag(s) => write!(f, "error parsing {} template: invalid comment tag, expected {{{{! comment }}}}", template_name(s)),
1551            ParseErrorInvalidSectionStartTag(s) => write!(f, "error parsing {} template: invalid section start tag, expected {{{{# section }}}}", template_name(s)),
1552            ParseErrorInvalidInvertedSectionStartTag(s) => write!(f, "error parsing {} template: invalid inverted section start tag, expected {{{{^ section }}}}", template_name(s)),
1553            ParseErrorInvalidPartialTag(s) => write!(f, "error parsing {} template: invalid partial tag, expected {{{{> partial }}}}", template_name(s)),
1554            IoError(s, error_kind) => write!(f, "error reading {} template: {}", template_name(s), error_kind),
1555            LoaderErrorTemplateNotFound(s) => write!(f, "loader error: {} template not found", template_name(s)),
1556            LoaderErrorNonUtf8FilePath(s) => write!(f, "loader error: can't load non-utf8 file path: {}", s.display()),
1557            ConfigErrorNonPositiveCacheSize => write!(f, "config error: cache size must be positive"),
1558            ConfigErrorInvalidTemplatesDirectory(s) => write!(f, "config error: invalid templates directory: {}", s.display()),
1559            ConfigErrorTooManyTemplates => write!(f, "config error: templates in directory exceeds cache size"),
1560            SerializationError => write!(f, "serialization error: could not serialize data to serde_json::Value"),
1561        }
1562    }
1563}
1564
1565// The reason why we have this is because our parser is a backtracking
1566// parser that will try to parse something, and if it fails, will
1567// backtrack and try another parser. This means that even parsing a
1568// valid mustache template will generate A LOT of errors, and we don't
1569// want to heap allocate a String every time the parser has to backtrack
1570// due to an error, so we have this InternalError type instead for parser
1571// errors that eventually get converted to MoostacheErrors by the time they
1572// make it to the user.
1573#[derive(Debug, Copy, Clone, PartialEq)]
1574enum InternalError {
1575    ParseErrorGeneric,
1576    ParseErrorNoContent,
1577    ParseErrorUnclosedSectionTags,
1578    ParseErrorInvalidEscapedVariableTag,
1579    ParseErrorInvalidUnescapedVariableTag,
1580    ParseErrorInvalidSectionEndTag,
1581    ParseErrorMismatchedSectionEndTag,
1582    ParseErrorInvalidCommentTag,
1583    ParseErrorInvalidSectionStartTag,
1584    ParseErrorInvalidInvertedSectionStartTag,
1585    ParseErrorInvalidPartialTag,
1586}
1587
1588impl std::error::Error for InternalError {}
1589
1590impl Display for InternalError {
1591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1592        use InternalError::*;
1593        match self {
1594            ParseErrorGeneric => write!(f, "generic parse error"),
1595            ParseErrorNoContent => write!(f, "parse error: empty moostache template"),
1596            ParseErrorUnclosedSectionTags => write!(f, "parse error: unclosed section tags"),
1597            ParseErrorInvalidEscapedVariableTag => write!(f, "parse error: invalid escaped variable tag, expected {{{{ variable }}}}"),
1598            ParseErrorInvalidUnescapedVariableTag => write!(f, "parse error: invalid unescaped variable tag, expected {{{{{{ variable }}}}}}"),
1599            ParseErrorInvalidSectionEndTag => write!(f, "parse error: invalid section eng tag, expected {{{{/ section }}}}"),
1600            ParseErrorMismatchedSectionEndTag => write!(f, "parse error: mismatched section eng tag, expected {{{{# section }}}} ... {{{{/ section }}}}"),
1601            ParseErrorInvalidCommentTag => write!(f, "parse error: invalid comment tag, expected {{{{! comment }}}}"),
1602            ParseErrorInvalidSectionStartTag => write!(f, "parse error: invalid section start tag, expected {{{{# section }}}}"),
1603            ParseErrorInvalidInvertedSectionStartTag => write!(f, "parse error: invalid inverted section start tag, expected {{{{^ section }}}}"),
1604            ParseErrorInvalidPartialTag => write!(f, "parse error: invalid partial tag, expected {{{{> partial }}}}"),
1605        }
1606    }
1607}
1608
1609// need to impl this so InternalError plays nice with winnow
1610impl<I: Stream> WParserError<I> for InternalError {
1611    #[inline]
1612    fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
1613        InternalError::ParseErrorGeneric
1614    }
1615
1616    #[inline]
1617    fn append(
1618        self,
1619        _input: &I,
1620        _token_start: &<I as Stream>::Checkpoint,
1621        _kind: ErrorKind,
1622    ) -> Self {
1623        self
1624    }
1625
1626    #[inline]
1627    fn or(self, other: Self) -> Self {
1628        other
1629    }
1630}
1631
1632// need to impl this so InternalError plays nice with winnow
1633impl<I: Stream> AddContext<I, Self> for InternalError {
1634    #[inline]
1635    fn add_context(
1636        self,
1637        _input: &I,
1638        _token_start: &<I as Stream>::Checkpoint,
1639        context: Self,
1640    ) -> Self {
1641        context
1642    }
1643}