1use askama::Template;
10
11#[derive(Debug, Clone, Default)]
13pub struct Markup(String);
14
15impl Markup {
16 pub fn empty() -> Self {
18 Self(String::new())
19 }
20
21 pub fn from_template<T: Template>(t: &T) -> Result<Self, askama::Error> {
23 Ok(Self(t.render()?))
24 }
25
26 pub fn raw_unchecked(html: impl Into<String>) -> Self {
29 Self(html.into())
30 }
31
32 pub(crate) fn from_override(rendered: String) -> Self {
35 Self(rendered)
36 }
37
38 pub fn as_str(&self) -> &str {
40 &self.0
41 }
42
43 pub fn into_string(self) -> String {
45 self.0
46 }
47
48 pub fn push(&mut self, other: Markup) {
50 self.0.push_str(&other.0);
51 }
52}
53
54impl std::fmt::Display for Markup {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.write_str(&self.0)
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use std::path::Path;
63
64 #[test]
68 fn raw_html_is_confined_to_the_html_module() {
69 let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
70 let mut offenders = Vec::new();
71 visit(&src, &mut |path, text| {
72 if path.file_name().and_then(|n| n.to_str()) == Some("html.rs") {
73 return;
74 }
75 if text.contains("raw_unchecked") {
76 offenders.push(path.display().to_string());
77 }
78 });
79 assert!(
80 offenders.is_empty(),
81 "`raw_unchecked` used outside html.rs; audit each and update this allowlist: {offenders:?}"
82 );
83 }
84
85 fn visit(dir: &Path, f: &mut impl FnMut(&Path, &str)) {
86 for entry in std::fs::read_dir(dir).unwrap().flatten() {
87 let path = entry.path();
88 if path.is_dir() {
89 visit(&path, f);
90 } else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
91 let text = std::fs::read_to_string(&path).unwrap_or_default();
92 f(&path, &text);
93 }
94 }
95 }
96}