Expand description
A Rust implementation of the Markdoc language.
Markdoc is CommonMark plus a tag syntax that turns documents into structured, validatable content instead of pre-rendered HTML:
{% callout type="note" %}
Tags nest, take typed attributes, and are validated against a schema.
{% /callout %}This crate implements that language as a pipeline of pure stages:
parse -> AST -> validate -> transform -> renderable tree -> format§What this crate does not do
It performs no I/O, reads no configuration, and decides no HTML policy. It has no concept of a file, a theme, a template, or a plugin. Everything host-specific arrives as data the caller passes in, or through a trait the caller implements:
Tokenizersegments Markdown. A default implementation over pulldown-cmark ships behind thepulldown-cmark-tokenizerfeature, so a host that already owns a CommonMark parser can supply its own rather than compile a second one.SchemaSourceanswers “what is the schema for this tag name?”. Whether that answer comes from a file, a constant, or a sandboxed guest is the host’s business, not this crate’s;MapSchemaSourceis the answer for a host that assembles it by hand.TagRendererturns a validated tag into markup. Escaping, void elements, and HTML policy live there; the walk over the tree does not, which keeps the document’s depth off the host’s stack.
That boundary is deliberate and is enforced by a CI job that builds and tests this crate with nothing else present.
§Compatibility
Ported from upstream Markdoc at revision afee1a4 (v0.5.9). The tag
language and the validation error ids are the contract; CommonMark edge
behaviour is not, because upstream is built on markdown-it and this crate is
built on pulldown-cmark. Every deliberate difference is recorded in
DIVERGENCES.md at the repository root, which is normative rather than a
changelog.
§Conventions this crate commits to
-
Public enums are
#[non_exhaustive]. Markdoc gained node types across its 0.5.x line; spelling them exhaustively would turn each new one into a breaking release. The one exception isSchemaKey: its two variants are the two ways a node is looked up, not a list that grows with Markdoc, and a source that implementsSchemaSourceshould stop compiling if a third appeared rather than silently answerNone. -
Validation errors are data, not failures. The validator returns a
Vecof them.Result::Erris reserved for internal invariants. -
Output is deterministic. Attribute order is authored order, never hash order, so two runs over the same input produce identical bytes.
-
Panic-freedom is a promise. Property tests assert the parser never panics on arbitrary input, and fuzzing precedes publication. An open parser is a claim about its attack surface.
The promise covers values a caller builds as well as documents this crate parses, and it covers every way of touching one. Each public recursive type –
ast::Node,ast::Value,renderable::Tagandrenderable::Scalar– writes out all four of its traversals:Drop,Clone,PartialEqandDebug. A derived implementation of any of them recurses once per level, and a stack overflow aborts rather than panics, so a caller could otherwise kill the process with a value it assembled through the public API. Nothing here isunsafe:DropandPartialEqwalk a worklist,Clonewalks post-order onto a plan and rebuilds bottom-up, andDebugemits from a token stack.Three costs, stated because they are invisible until met. A variant’s contents are taken with
std::mem::takerather than moved out, since a type with a manualDropforbids the partial move.Debugoutput is observable, so the emitters are pinned against a mirror type that still derives it, in both{:?}and{:#?}. And equality over anindexmap::IndexMapfield stays unordered, matching what that map’s ownPartialEqdoes rather than what a positional walk would be tempted to.
Modules§
- ast
- The document tree: nodes, tags, variables, functions, and source locations.
- builtins
- The vocabulary Markdoc ships: node schemas, tags, and functions.
- format
- Printing an AST back to canonical Markdoc source.
- functions
- The built-in functions available in tag attributes and annotations.
- grammar
- The tag-internals grammar: what appears between
{%and%}. - parse
- Segmentation and document parsing: source text in, AST out.
- render
- Rendering a renderable tree to output.
- renderable
- The renderable tree: what transform produces and a renderer consumes.
- tags
- The built-in tags every Markdoc document can use without a schema.
- transform
- Transformation: a validated AST becomes a renderable tree.
- validate
- Schema definitions and validation.