//! Seal: no template emits a `
` tag that `base.html` already owns.
//!
//! `base.html` emits three page-invariant social tags before `{% block head %}`:
//! `og:site_name` as a plain tag, and `og:type` / `twitter:card` as blocks with
//! generic defaults. A page that needs a different type or card overrides the
//! block. A page that emits the tag again inside `{% block head %}` instead
//! ships it twice, and the two disagree.
//!
//! Base's copy comes first in the document, so a consumer taking the first
//! occurrence reads the generic value and the page's real one never wins.
//!
//! Rendering every page to check this would need a database and a fixture per
//! template. Reading the templates needs neither, and the defect is a property
//! of the source rather than of any particular row, so this is a source seal in
//! the same shape as the `frontend_globals` ratchet.
use std::fs;
use std::path::{Path, PathBuf};
/// Tags `base.html` owns, with the block a page must override to change one.
/// `None` means the tag is invariant and a page may not restate it at all.
const OWNED: &[(&str, Option<&str>)] = &[
("og:site_name", None),
("og:type", Some("og_type")),
("twitter:card", Some("social_card")),
];
fn templates_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("templates")
}
/// Every `.html` under `templates/`, walked rather than listed so a new page
/// is covered the day it lands.
fn template_files(dir: &Path, out: &mut Vec) {
for entry in fs::read_dir(dir).expect("templates/ is readable") {
let path = entry.expect("readable dir entry").path();
if path.is_dir() {
template_files(&path, out);
} else if path.extension().is_some_and(|e| e == "html") {
out.push(path);
}
}
}
/// Remove `{% block %}...{% endblock %}` from `src`.
///
/// Askama's only nestable construct that closes with `{% endblock %}` is a
/// block, and no sanctioned override nests one, so pairing each opener with the
/// next `{% endblock %}` is exact here. A nested block inside an override would
/// under-strip and fail the seal, which is the safe direction.
fn strip_block(src: &str, name: &str) -> String {
let opener = format!("{{% block {name} %}}");
let mut out = String::with_capacity(src.len());
let mut rest = src;
while let Some(start) = rest.find(&opener) {
out.push_str(&rest[..start]);
let after = &rest[start + opener.len()..];
match after.find("{% endblock %}") {
Some(end) => rest = &after[end + "{% endblock %}".len()..],
// Unterminated: leave the remainder in place so the assertion sees
// it rather than silently swallowing the rest of the file.
None => {
rest = after;
break;
}
}
}
out.push_str(rest);
out
}
#[test]
fn no_template_restates_a_tag_base_html_owns() {
let dir = templates_dir();
let base = dir.join("base.html");
let base_src = fs::read_to_string(&base).expect("base.html is readable");
// The seal is only meaningful if base still emits what it claims to own.
for (tag, block) in OWNED {
assert!(
base_src.contains(tag),
"base.html no longer emits {tag}, but this seal still forbids pages \
from emitting it. Update OWNED or restore the tag."
);
if let Some(block) = block {
assert!(
base_src.contains(&format!("{{% block {block} %}}")),
"base.html emits {tag} outside a `{block}` block, so a page has \
no sanctioned way to override it. Wrap it in the block."
);
}
}
let mut files = Vec::new();
template_files(&dir, &mut files);
files.sort();
let mut offenders = Vec::new();
for path in files {
if path == base {
continue;
}
let src = fs::read_to_string(&path).expect("template is readable");
// Only children of base.html inherit its head; standalone templates
// (partials, embeds) own their own markup.
if !src.contains(r#"{% extends "base.html" %}"#) {
continue;
}
let rel = path
.strip_prefix(&dir)
.unwrap_or(&path)
.display()
.to_string();
for (tag, block) in OWNED {
let searchable = match block {
Some(block) => strip_block(&src, block),
None => src.clone(),
};
if searchable.contains(tag) {
offenders.push(match block {
Some(block) => format!(
"{rel} emits {tag} outside `{{% block {block} %}}`; base.html \
already emits it, so the page ships two and the first wins"
),
None => {
format!("{rel} emits {tag}, which base.html already emits for every page")
}
});
}
}
}
assert!(
offenders.is_empty(),
"templates restate head tags base.html owns:\n {}",
offenders.join("\n ")
);
}