1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use crate::{controller::views::ViewRenderer, Result};
use serde::Serialize;
use std::collections::BTreeMap;
pub static DEFAULT_ASSET_FOLDER: &str = "assets";
// Include the generated templates at the module level
include!(concat!(
env!("OUT_DIR"),
"/generated_code/view_templates.rs"
));
#[derive(Clone, Debug)]
pub struct TeraView {
pub tera: tera::Tera,
pub default_context: tera::Context,
}
impl TeraView {
/// Create a Tera view engine
///
/// # Errors
///
/// This function will return an error if building fails
pub fn build() -> Result<Self> {
Self::from_embedded_templates()
}
/// Create a Tera view engine with a post-processing function, used to
/// register custom filters and functions (e.g. an i18n `t()`).
///
/// Mirrors the non-embedded engine's constructor of the same name so app
/// code — including the generated view-engine initializer — works
/// identically with and without the `embedded_assets` feature.
///
/// # Errors
///
/// This function will return an error if building fails or if the
/// post-processing function fails
pub fn build_with_post_process(
post_process: impl FnMut(&mut tera::Tera) -> Result<()> + Send + Sync + 'static,
) -> Result<Self> {
Self::assemble(post_process)
}
/// Attach the Tera view engine with a post-processing function for subsequent instantiation.
///
/// The post-processing function is also run during the call to this method.
///
/// Note that whenever the embedded templates themselves call the registered
/// filter or function, [`Self::build_with_post_process`] is the only usable
/// entry point — `build()` would already have failed while loading them.
///
/// # Errors
///
/// This function will return an error if the post-processing function fails
pub fn post_process(
self,
post_process: impl FnMut(&mut tera::Tera) -> Result<()> + Send + Sync + 'static,
) -> Result<Self> {
// Rebuild rather than mutate: registrations have to precede template
// loading (see `assemble`), and `self`'s templates are already loaded.
Self::assemble(post_process)
}
/// Load and initialize templates from embedded assets
///
/// # Errors
///
/// This function will return an error if:
/// - Adding templates to Tera fails
/// - There are syntax errors in any template
pub fn from_embedded_templates() -> Result<Self> {
Self::assemble(|_| Ok(()))
}
/// Builds the engine in the one order Tera 2 permits: register everything
/// the templates may reference, *then* load the templates.
///
/// Tera 2 resolves filter and function references when a template is added,
/// so a template calling `t()`, a custom filter, or `get_env` (a Tera 1
/// built-in Loco now supplies itself) fails to load unless the name is
/// already registered.
fn assemble(
mut post_process: impl FnMut(&mut tera::Tera) -> Result<()> + Send + Sync + 'static,
) -> Result<Self> {
let mut tera = crate::tera::instance();
post_process(&mut tera)?;
Self::load_templates_into_tera(&mut tera)?;
Ok(Self {
tera,
default_context: tera::Context::default(),
})
}
/// Helper function to load all embedded templates into Tera engine
///
/// # Errors
///
/// Returns an error if adding a template fails
fn load_templates_into_tera(tera: &mut tera::Tera) -> Result<()> {
let templates_map = get_embedded_templates();
let templates: BTreeMap<_, _> = templates_map.into_iter().collect();
Self::log_template_info(&templates);
Self::add_templates_to_tera(tera, templates)
}
/// Log information about the templates
fn log_template_info(templates: &BTreeMap<String, &'static str>) {
tracing::info!("Initializing embedded templates feature");
tracing::info!("Found {} embedded templates", templates.len());
}
/// Add each template to the Tera engine
///
/// # Errors
///
/// Returns an error if adding any template fails
fn add_templates_to_tera(
tera: &mut tera::Tera,
templates: BTreeMap<String, &'static str>,
) -> Result<()> {
// Register the whole set in ONE call. Tera 2 resolves inheritance as
// templates are added and rejects a child whose parent it has not seen,
// so adding them one at a time would fail whenever a child sorts before
// its `{% extends %}` parent.
for name in templates.keys() {
tracing::debug!("Adding template '{}' to Tera", name);
}
tera.add_raw_templates(templates).map_err(|e| {
tracing::error!("Failed to add templates: {}", e);
crate::Error::from(e)
})?;
Ok(())
}
}
impl ViewRenderer for TeraView {
fn render<S: Serialize>(&self, key: &str, data: S) -> Result<String> {
let context = tera::Context::from_serialize(&data)?;
// Try to render the requested template
match self.tera.render(key, &context) {
Ok(result) => Ok(result),
Err(e) => {
// Log error about missing template
if e.to_string().contains("not found") {
tracing::warn!("Template '{}' not found", key);
let template_names: Vec<String> =
self.tera.get_template_names().map(String::from).collect();
tracing::debug!("Available templates: {:?}", template_names);
}
// Return the original error
Err(e.into())
}
}
}
}