Skip to main content

max / makenotwork

5.1 KB · 140 lines History Blame Raw
1 //! Seal: no template emits a `<head>` tag that `base.html` already owns.
2 //!
3 //! `base.html` emits three page-invariant social tags before `{% block head %}`:
4 //! `og:site_name` as a plain tag, and `og:type` / `twitter:card` as blocks with
5 //! generic defaults. A page that needs a different type or card overrides the
6 //! block. A page that emits the tag again inside `{% block head %}` instead
7 //! ships it twice, and the two disagree.
8 //!
9 //! Base's copy comes first in the document, so a consumer taking the first
10 //! occurrence reads the generic value and the page's real one never wins.
11 //!
12 //! Rendering every page to check this would need a database and a fixture per
13 //! template. Reading the templates needs neither, and the defect is a property
14 //! of the source rather than of any particular row, so this is a source seal in
15 //! the same shape as the `frontend_globals` ratchet.
16
17 use std::fs;
18 use std::path::{Path, PathBuf};
19
20 /// Tags `base.html` owns, with the block a page must override to change one.
21 /// `None` means the tag is invariant and a page may not restate it at all.
22 const OWNED: &[(&str, Option<&str>)] = &[
23 ("og:site_name", None),
24 ("og:type", Some("og_type")),
25 ("twitter:card", Some("social_card")),
26 ];
27
28 fn templates_dir() -> PathBuf {
29 Path::new(env!("CARGO_MANIFEST_DIR")).join("templates")
30 }
31
32 /// Every `.html` under `templates/`, walked rather than listed so a new page
33 /// is covered the day it lands.
34 fn template_files(dir: &Path, out: &mut Vec<PathBuf>) {
35 for entry in fs::read_dir(dir).expect("templates/ is readable") {
36 let path = entry.expect("readable dir entry").path();
37 if path.is_dir() {
38 template_files(&path, out);
39 } else if path.extension().is_some_and(|e| e == "html") {
40 out.push(path);
41 }
42 }
43 }
44
45 /// Remove `{% block <name> %}...{% endblock %}` from `src`.
46 ///
47 /// Askama's only nestable construct that closes with `{% endblock %}` is a
48 /// block, and no sanctioned override nests one, so pairing each opener with the
49 /// next `{% endblock %}` is exact here. A nested block inside an override would
50 /// under-strip and fail the seal, which is the safe direction.
51 fn strip_block(src: &str, name: &str) -> String {
52 let opener = format!("{{% block {name} %}}");
53 let mut out = String::with_capacity(src.len());
54 let mut rest = src;
55 while let Some(start) = rest.find(&opener) {
56 out.push_str(&rest[..start]);
57 let after = &rest[start + opener.len()..];
58 match after.find("{% endblock %}") {
59 Some(end) => rest = &after[end + "{% endblock %}".len()..],
60 // Unterminated: leave the remainder in place so the assertion sees
61 // it rather than silently swallowing the rest of the file.
62 None => {
63 rest = after;
64 break;
65 }
66 }
67 }
68 out.push_str(rest);
69 out
70 }
71
72 #[test]
73 fn no_template_restates_a_tag_base_html_owns() {
74 let dir = templates_dir();
75 let base = dir.join("base.html");
76 let base_src = fs::read_to_string(&base).expect("base.html is readable");
77
78 // The seal is only meaningful if base still emits what it claims to own.
79 for (tag, block) in OWNED {
80 assert!(
81 base_src.contains(tag),
82 "base.html no longer emits {tag}, but this seal still forbids pages \
83 from emitting it. Update OWNED or restore the tag."
84 );
85 if let Some(block) = block {
86 assert!(
87 base_src.contains(&format!("{{% block {block} %}}")),
88 "base.html emits {tag} outside a `{block}` block, so a page has \
89 no sanctioned way to override it. Wrap it in the block."
90 );
91 }
92 }
93
94 let mut files = Vec::new();
95 template_files(&dir, &mut files);
96 files.sort();
97
98 let mut offenders = Vec::new();
99 for path in files {
100 if path == base {
101 continue;
102 }
103 let src = fs::read_to_string(&path).expect("template is readable");
104 // Only children of base.html inherit its head; standalone templates
105 // (partials, embeds) own their own markup.
106 if !src.contains(r#"{% extends "base.html" %}"#) {
107 continue;
108 }
109
110 let rel = path
111 .strip_prefix(&dir)
112 .unwrap_or(&path)
113 .display()
114 .to_string();
115 for (tag, block) in OWNED {
116 let searchable = match block {
117 Some(block) => strip_block(&src, block),
118 None => src.clone(),
119 };
120 if searchable.contains(tag) {
121 offenders.push(match block {
122 Some(block) => format!(
123 "{rel} emits {tag} outside `{{% block {block} %}}`; base.html \
124 already emits it, so the page ships two and the first wins"
125 ),
126 None => {
127 format!("{rel} emits {tag}, which base.html already emits for every page")
128 }
129 });
130 }
131 }
132 }
133
134 assert!(
135 offenders.is_empty(),
136 "templates restate head tags base.html owns:\n {}",
137 offenders.join("\n ")
138 );
139 }
140