1use std::fmt;
2
3use wavefunk_ui::Template;
4pub use wavefunk_ui::components::{HtmlAttr, TrustedHtml};
5
6pub type UiRenderResult<T> = Result<T, wavefunk_ui::askama::Error>;
7
8#[derive(Debug, Default, Eq, PartialEq)]
9pub struct RenderedUi {
10 html: String,
11}
12
13impl RenderedUi {
14 pub fn new(html: String) -> Self {
15 Self { html }
16 }
17
18 pub fn as_str(&self) -> &str {
19 &self.html
20 }
21
22 pub fn as_trusted_html(&self) -> TrustedHtml<'_> {
23 TrustedHtml::new(&self.html)
24 }
25
26 pub fn into_string(self) -> String {
27 self.html
28 }
29}
30
31impl AsRef<str> for RenderedUi {
32 fn as_ref(&self) -> &str {
33 self.as_str()
34 }
35}
36
37impl fmt::Display for RenderedUi {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str(&self.html)
40 }
41}
42
43impl wavefunk_ui::askama::FastWritable for RenderedUi {
44 #[inline]
45 fn write_into(
46 &self,
47 dest: &mut dyn fmt::Write,
48 _: &dyn wavefunk_ui::askama::Values,
49 ) -> wavefunk_ui::askama::Result<()> {
50 Ok(dest.write_str(&self.html)?)
51 }
52}
53
54impl wavefunk_ui::askama::filters::HtmlSafe for RenderedUi {}
55
56pub fn render_component<T>(component: &T) -> UiRenderResult<RenderedUi>
57where
58 T: Template + ?Sized,
59{
60 component.render().map(RenderedUi::new)
61}
62
63pub const fn trusted_html<'a>(html: &'a str) -> TrustedHtml<'a> {
64 TrustedHtml::new(html)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use wavefunk_ui::components::{FormPanel, SplitShell};
71
72 #[test]
73 fn rendered_html_can_be_reused_as_trusted_component_slot() {
74 let body = trusted_html(r#"<form action="/login"></form>"#);
75 let panel = FormPanel::new("Sign in", body);
76 let panel_html = render_component(&panel).expect("form panel should render");
77
78 let shell = SplitShell::new(panel_html.as_trusted_html()).with_mode("dark");
79 let shell_html = render_component(&shell).expect("split shell should render");
80
81 assert!(shell_html.as_str().contains("wf-split-shell"));
82 assert!(shell_html.as_str().contains("data-mode=\"dark\""));
83 assert!(shell_html.as_str().contains("wf-form-panel"));
84 assert!(
85 shell_html
86 .as_str()
87 .contains(r#"<form action="/login"></form>"#)
88 );
89 }
90
91 #[test]
92 fn rendered_ui_wraps_owned_html_without_losing_access_to_string() {
93 let rendered = RenderedUi::new("<section>owned</section>".to_owned());
94
95 assert_eq!(rendered.as_str(), "<section>owned</section>");
96 assert_eq!(
97 rendered.as_trusted_html().as_str(),
98 "<section>owned</section>"
99 );
100 assert_eq!(
101 rendered.into_string(),
102 "<section>owned</section>".to_owned()
103 );
104 }
105
106 fn assert_html_safe<T: wavefunk_ui::askama::filters::HtmlSafe>() {}
107
108 #[test]
109 fn rendered_ui_can_be_embedded_as_owned_safe_html() {
110 assert_html_safe::<RenderedUi>();
111 }
112}