rust_html/
lib.rs

1pub use rust_html_macros::rhtml;
2
3pub mod integration;
4
5/// Struct representing a rust_html template.
6/// Enables easy reusability and injection safety.
7///
8/// Create one using the `rust_html::rhtml!` macro:
9///
10/// ```rust
11/// use rust_html::{rhtml, Template};
12/// let my_template: Template = rhtml! { "<div>Hello!</div> "};
13/// ```
14///
15/// You can convert a template to a HTML string:
16///
17/// ```rust
18/// use rust_html::{rhtml, Template};
19/// let template = rhtml! { "<div>hello, world</div>"};
20/// let html: String = template.into();
21/// ```
22///
23/// You should typically only do this just before
24/// returning the HTML in your endpoint.
25///
26#[derive(Debug, Clone, Eq, PartialEq)]
27pub struct Template {
28    content: TemplateContent,
29}
30
31/// Represents a group of rust_html templates
32///
33/// Use this wrapper if you need to insert a
34/// `Vec<Template>` into another template.
35#[derive(Debug, Clone, Eq, PartialEq)]
36pub struct TemplateGroup(pub Vec<Template>);
37
38/// Wrapper to insert unescaped content into
39/// a rust_html template. Never use Unescaped
40/// on untrusted user input!
41///
42/// ```rust
43/// use rust_html::{rhtml, Unescaped};
44/// let sketchy_user_input = "<script>".to_string();
45///
46/// let safe_template = rhtml! { "{sketchy_user_input}" };
47/// assert_eq!(String::from(safe_template), "&lt;script&gt;");
48///
49/// let unescaped = Unescaped(sketchy_user_input.clone());
50/// let unsafe_template = rhtml! { "{unescaped}" };
51/// assert_eq!(String::from(unsafe_template), "<script>");
52/// ```
53///
54#[derive(Debug, Clone)]
55pub struct Unescaped(pub String);
56
57/// Render trait for rust_html templates
58///
59/// Implement this trait on a struct to create
60/// reusable struct components that you
61/// can reuse inside other templates.
62pub trait Render {
63    fn render(&self) -> Template;
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
67enum TemplateContent {
68    RawString(String),
69    WithParameters {
70        template_parts: Vec<(&'static str, Template)>,
71        template_end: &'static str,
72    },
73}
74
75impl Template {
76    /// Internal macro creation of a rust_html template.
77    ///
78    /// DO NOT USE THIS.
79    /// USE THE `rhtml!` MACRO.
80    ///
81    /// This implementation is low level and intended
82    /// to be used by the rust_html_macros crate.
83    pub fn build_internal(
84        template_parts: Vec<(&'static str, Template)>,
85        template_end: &'static str,
86    ) -> Self {
87        Template {
88            content: TemplateContent::WithParameters {
89                template_parts,
90                template_end,
91            },
92        }
93    }
94    /// Internal function. Converts a template to String
95    fn build(&self) -> String {
96        match &self.content {
97            TemplateContent::RawString(value) => value.to_owned(),
98            TemplateContent::WithParameters {
99                template_parts,
100                template_end,
101            } => {
102                if template_parts.is_empty() {
103                    return template_end.to_string();
104                }
105                let mut output: Vec<String> = Vec::with_capacity(template_parts.len() * 2 + 1);
106                for (html_part, param_part) in template_parts.iter() {
107                    output.push(html_part.to_string());
108                    output.push(param_part.build());
109                }
110                output.push(template_end.to_string());
111                output.join("")
112            }
113        }
114    }
115}
116
117impl Render for Template {
118    fn render(&self) -> Template {
119        self.clone()
120    }
121}
122
123impl Render for Unescaped {
124    fn render(&self) -> Template {
125        Template {
126            content: TemplateContent::RawString(self.0.to_owned()),
127        }
128    }
129}
130
131impl Render for TemplateGroup {
132    fn render(&self) -> Template {
133        let string: String = self
134            .0
135            .iter()
136            .map(|template| template.build())
137            .collect::<Vec<_>>()
138            .join("");
139        Template {
140            content: TemplateContent::RawString(string),
141        }
142    }
143}
144
145impl<I> From<I> for TemplateGroup
146where
147    I: IntoIterator<Item = Template>,
148{
149    fn from(value: I) -> Self {
150        TemplateGroup(value.into_iter().collect())
151    }
152}
153
154impl std::iter::FromIterator<Template> for TemplateGroup {
155    fn from_iter<T: IntoIterator<Item = Template>>(iter: T) -> Self {
156        TemplateGroup(iter.into_iter().collect())
157    }
158}
159
160impl<T> Render for T
161where
162    T: std::fmt::Display,
163{
164    fn render(&self) -> Template {
165        let string = self.to_string();
166        let escaped_value = html_escape::encode_safe(&string);
167        Template {
168            content: TemplateContent::RawString(escaped_value.into()),
169        }
170    }
171}
172
173impl From<Template> for String {
174    fn from(value: Template) -> Self {
175        value.build()
176    }
177}
178
179impl<T> From<T> for Template
180where
181    T: std::fmt::Display,
182{
183    fn from(value: T) -> Self {
184        Render::render(&value)
185    }
186}