Skip to main content

validate_section_content

Function validate_section_content 

Source
pub fn validate_section_content<'a>(
    sections: impl Iterator<Item = (&'a str, &'a str)>,
    catch_all: Option<CatchAllContext<'_>>,
) -> Result<(), ValidationError>
Expand description

Refuse section content that would round-trip through the compose pipeline as a section delimiter. The compose-then-reparse loop’s parser anchors on (?m)^## (.+)$ over the masked body, so a section body that shows a ^## line to that scan gets split at that heading on the next read — content after the heading lands under a different section key (or a fabricated one). Deeper headings (### and below) are safe — the parser only matches level 2.

The guard is applied to the content as the reparse will see it, which is what makes it exact rather than approximate:

  • the content is trimmed first, because the splitter stores the trimmed body — an indented block opening a section loses its indent on write-back, so ## Not A Heading becomes a real column-0 delimiter on the next parse. Checking the still-indented provided content missed that fork entirely;
  • code blocks are masked first, by the same CommonMark referee the splitter uses (crate::markdown) — a ## inside a fenced or indented code block never splits anything, so refusing it was the write path disagreeing with the read path about what a code block is.

catch_all names the type’s catch-all section and its declared headings, when the caller knows them (consistency-sweep 04/01, criterion 6). Inside the CATCH-ALL body only, a ## line whose heading the type does not declare is accepted, because the reparse absorbs it straight back into the catch-all: the content does not land under a different key, which is the whole basis of this guard. That case is not hypothetical — it is what the engine itself emits, since the catch-all builder re-emits absorbed content under its original heading line, and an agent that read an entity and wrote that section back in replace mode was refused its own value.

This does NOT weaken the guard. A DECLARED heading inside the catch-all still refuses, because that one really does fork: the reparse would move the content to the declared key. Every other section is unchanged, and a caller who passes None gets exactly the old behaviour.