gen_html/
render.rs

1use crate::escape::escape;
2use std::{
3    borrow::Cow,
4    fmt::{self, Arguments},
5};
6
7/// Trait for safely rendering HTML content.
8///
9/// `Render` is similar to [`Display`] and [`Debug`] but it is for safely generating HTML.
10///
11/// # Example
12///
13/// ```
14/// use std::fmt::{Formatter, Result};
15/// use gen_html::Render;
16///
17/// struct Vector2D {
18///     x: f32,
19///     y: f32,
20/// }
21///
22/// impl Render for Vector2D {
23///     fn render_to(&self, f: &mut Formatter) -> Result {
24///         let Self { x, y } = self;
25///         // `f32` cannot contain special characters, so we don't need to worry about escaping
26///         write!(f, "({x}, {y})")
27///     }
28/// }
29/// # let point = Vector2D { x: 2.0, y: -1.0 };
30/// # assert_eq!(point.render().0, "(2, -1)");
31/// ```
32///
33/// [`Display`]: std::fmt::Display
34/// [`Debug`]: std::fmt::Debug
35/// [`Escaped`]: crate::Escaped
36#[diagnostic::on_unimplemented(
37    message = "the type `{Self}` cannot be safely rendered as HTML",
38    note = "to safely render `{Self}` as HTML, implement the `Render` trait or wrap it in `Escaped<{Self}>`"
39)]
40pub trait Render {
41    /// Renders HTML to a given formatter.
42    ///
43    /// When implementing this function, you should make sure that the output is valid HTML.
44    ///
45    /// # Errors
46    ///
47    /// This function should return [`Err`] if, and only if, the provided [`Formatter`] returns [`Err`].
48    ///
49    /// [`Formatter`]: std::fmt::Formatter
50    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result;
51
52    /// Converts the given value to a `Raw<String>`.
53    ///
54    /// ```
55    /// # use gen_html::Render;
56    /// let content = "<this is escaped>";
57    /// assert_eq!(content.render().0, "&lt;this is escaped&gt;");
58    /// ```
59    fn render(&self) -> Raw<String> {
60        use std::fmt::Write;
61        let mut buf = String::new();
62        write!(&mut buf, "{}", render_fn(|f| self.render_to(f))).expect("render_to returned Err");
63        Raw(buf)
64    }
65}
66
67impl Render for str {
68    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        escape(self, f)
70    }
71}
72
73impl Render for String {
74    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
75        self.as_str().render_to(f)
76    }
77}
78
79impl Render for Arguments<'_> {
80    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        match self.as_str() {
82            Some(s) => s.render_to(f),
83            None => self.to_string().render_to(f),
84        }
85    }
86}
87
88impl<B> Render for Cow<'_, B>
89where
90    B: Render + ToOwned + ?Sized,
91{
92    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        self.as_ref().render_to(f)
94    }
95}
96
97macro_rules! ref_render_impl {
98    ( $( $t:ty )* ) => {
99        $(
100            impl<T> Render for $t
101            where
102                T: Render + ?Sized,
103            {
104                fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
105                    T::render_to(self, f)
106                }
107            }
108        )*
109    };
110}
111
112ref_render_impl! {
113    &T
114    &mut T
115    Box<T>
116    std::rc::Rc<T>
117    std::sync::Arc<T>
118}
119
120macro_rules! trusted_render_impl {
121    ( $( $t:ty )* ) => {
122        $(
123            impl Render for $t {
124                fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
125                    write!(f, "{self}")
126                }
127            }
128        )*
129    };
130}
131
132trusted_render_impl! {
133    f32 f64
134    i8 i16 i32 i64 i128 isize
135    u8 u16 u32 u64 u128 usize
136}
137
138/// Wrapper to render content using [`Display`] without escaping.
139///
140/// Use this wrapper when you have HTML content that is already safe and should be
141/// rendered without escaping. This should generally only be used for trusted content,
142/// not for user-provided input.
143///
144/// # Example
145///
146/// ```
147/// use gen_html::{html, Raw};
148///
149/// let oopsie = html! {
150///     p { (Raw("<div>")) }
151/// };
152/// # assert_eq!(oopsie.to_string(), "<p><div></p>");
153/// ```
154///
155/// [`Display`]: std::fmt::Display
156#[derive(Debug, Clone, Copy)]
157pub struct Raw<T>(pub T);
158
159impl<T: fmt::Display> fmt::Display for Raw<T> {
160    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161        self.0.fmt(f)
162    }
163}
164
165impl<T: fmt::Display> Render for Raw<T> {
166    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
167        self.0.fmt(f)
168    }
169}
170
171/// Implements [`Render`] using a function.
172///
173/// This `struct` is created by [`render_fn()`].
174pub struct RenderFn<F> {
175    f: F,
176}
177
178impl<F> fmt::Debug for RenderFn<F> {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.debug_struct("RenderFn").finish()
181    }
182}
183
184impl<F> Render for RenderFn<F>
185where
186    F: Fn(&mut fmt::Formatter) -> fmt::Result,
187{
188    fn render_to(&self, f: &mut fmt::Formatter) -> fmt::Result {
189        (self.f)(f)
190    }
191}
192
193impl<F> fmt::Display for RenderFn<F>
194where
195    F: Fn(&mut fmt::Formatter) -> fmt::Result,
196{
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        (self.f)(f)
199    }
200}
201
202/// Creates a type whose [`Render`] impl is provided with the function `f`.
203pub fn render_fn<F>(f: F) -> RenderFn<F>
204where
205    F: Fn(&mut fmt::Formatter) -> fmt::Result,
206{
207    RenderFn { f }
208}