Skip to main content

gen_html/
value.rs

1use crate::Render;
2use std::fmt::{self, Write};
3
4/// Types that can be used as attribute values in the [`html!`] macro.
5///
6/// | Value       | Renders the attribute? |
7/// |-------------|-----------------------:|
8/// | `T`         |                      ✓ |
9/// | `true`      |                      ✓ |
10/// | `false`     |                      ✗ |
11/// | [`Some<T>`] |                      ✓ |
12/// | [`None`]    |                      ✗ |
13///
14/// `where T: Render`
15///
16/// # Examples
17///
18/// Passing a [`bool`] conditionally renders a boolean attribute.
19///
20/// ```
21/// # use gen_html::{html, Render};
22/// let checked = true;
23///
24/// let markup = html! {
25///     input r#type: "checkbox" checked: (checked);
26/// };
27///
28/// assert_eq!(markup.render().0, r#"<input type="checkbox" checked>"#);
29/// ```
30///
31/// To conditionally include an attribute with a value, pass an [`Option<T>`].
32/// The attribute is only rendered when the value is [`Some`].
33///
34/// ```
35/// # use gen_html::{html, Render};
36/// let cooldown = Some("200ms");
37///
38/// let markup = html! {
39///     button data_cooldown: (cooldown) {
40///         "click me"
41///     }
42/// };
43///
44/// assert_eq!(markup.render().0, r#"<button data-cooldown="200ms">click me</button>"#);
45/// ```
46///
47/// [`html!`]: crate::html
48pub trait Value: private::Sealed {
49    #[doc(hidden)]
50    fn render_value_to(&self, name: &str, f: &mut fmt::Formatter) -> fmt::Result;
51}
52
53impl Value for bool {
54    fn render_value_to(&self, name: &str, f: &mut fmt::Formatter) -> fmt::Result {
55        if *self { write!(f, " {name}") } else { Ok(()) }
56    }
57}
58
59impl<R: Render> Value for Option<R> {
60    fn render_value_to(&self, name: &str, f: &mut fmt::Formatter) -> fmt::Result {
61        if let Some(r) = self {
62            write!(f, " {name}=\"")?;
63            r.render_to(f)?;
64            f.write_char('"')?;
65        }
66
67        Ok(())
68    }
69}
70
71impl<R: Render> Value for R {
72    fn render_value_to(&self, name: &str, f: &mut fmt::Formatter) -> fmt::Result {
73        write!(f, " {name}=\"")?;
74        self.render_to(f)?;
75        f.write_char('"')?;
76
77        Ok(())
78    }
79}
80
81mod private {
82    pub trait Sealed {}
83
84    impl<T: crate::Value> Sealed for T {}
85}
86
87#[cfg(test)]
88mod tests {
89    use super::Value;
90    use std::fmt;
91
92    #[test]
93    fn bool_attributes() {
94        let display = fmt::from_fn(|f| {
95            false.render_value_to("checked", f).unwrap();
96            true.render_value_to("some-attribute", f)
97        });
98
99        assert_eq!(display.to_string(), " some-attribute");
100    }
101
102    #[test]
103    fn optional_attributes() {
104        let display = fmt::from_fn(|f| {
105            None::<i32>.render_value_to("hello-world", f).unwrap();
106            Some("escape this\"<").render_value_to("attr-123", f)
107        });
108
109        assert_eq!(display.to_string(), r#" attr-123="escape this&quot;&lt;""#);
110    }
111
112    #[test]
113    fn normal_attributes() {
114        let display = fmt::from_fn(|f| {
115            "&".render_value_to("hello-world", f).unwrap();
116            "escape this\"<".render_value_to("attr-123", f)
117        });
118
119        assert_eq!(
120            display.to_string(),
121            r#" hello-world="&amp;" attr-123="escape this&quot;&lt;""#
122        );
123    }
124}