//! 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 ") ); }