by_loco/controller/views/
mod.rs

1// Choose the correct engine implementation based on the feature flag
2#[cfg(feature = "embedded_assets")]
3pub mod engine_embedded;
4#[cfg(feature = "embedded_assets")]
5pub use engine_embedded as engines;
6
7#[cfg(not(feature = "embedded_assets"))]
8pub mod engine;
9#[cfg(not(feature = "embedded_assets"))]
10pub use engine as engines;
11
12use axum::{extract::FromRequestParts, http::request::Parts, Extension};
13use serde::Serialize;
14pub mod tera_builtins;
15use crate::Result;
16
17#[cfg(feature = "with-db")]
18pub mod pagination;
19
20pub trait ViewRenderer {
21    /// Render a view template located by `key`
22    ///
23    /// # Errors
24    ///
25    /// This function will return an error if render fails
26    fn render<S: Serialize>(&self, key: &str, data: S) -> Result<String>;
27}
28
29#[derive(Debug, PartialEq, Eq, Clone)]
30pub struct ViewEngine<E>(pub E);
31
32impl<E> ViewEngine<E> {
33    /// Creates a new [`Engine`] that wraps the given engine
34    pub fn new(engine: E) -> Self {
35        Self(engine)
36    }
37}
38
39/// A struct representing an inline Tera view renderer.
40///
41/// This struct provides functionality to render templates using the Tera
42/// templating engine directly from raw template strings.
43///
44/// # Example
45/// ```
46/// use serde_json::json;
47/// use loco_rs::controller::views;
48/// let render = views::template("{{name}} website", json!({"name": "Loco"})).unwrap();
49/// assert_eq!(render, "Loco website");
50/// ```
51///
52/// # Errors
53///
54/// This function will return an error if building fails
55pub fn template<S>(template: &str, data: S) -> Result<String>
56where
57    S: Serialize,
58{
59    let mut tera = tera::Tera::default();
60    Ok(tera.render_str(template, &tera::Context::from_serialize(data)?)?)
61}
62
63impl<E> From<E> for ViewEngine<E> {
64    fn from(inner: E) -> Self {
65        Self::new(inner)
66    }
67}
68
69impl<S, E> FromRequestParts<S> for ViewEngine<E>
70where
71    S: Send + Sync,
72    E: Clone + Send + Sync + 'static,
73{
74    type Rejection = std::convert::Infallible;
75
76    async fn from_request_parts(
77        parts: &mut Parts,
78        state: &S,
79    ) -> std::result::Result<Self, Self::Rejection> {
80        let Extension(tl): Extension<Self> = Extension::from_request_parts(parts, state)
81            .await
82            .expect("TeraLayer missing. Is the TeraLayer installed?");
83        /*
84        let locale = parts
85            .headers
86            .get("Accept-Language")
87            .unwrap()
88            .to_str()
89            .unwrap();
90        // BUG: this does not mutate or set anything because of clone
91        tl.default_context.clone().insert("locale", &locale);
92        */
93
94        Ok(tl)
95    }
96}