Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Structured Core

mdbook-structured-core is the deep module at the format seam. Callers provide a format, UTF-8 source text, a source name, and limits; they receive a StructuredDocument or a structured diagnostic. Parser-library types do not cross this interface.

Public model

The conceptual public model is:

#![allow(unused)]
fn main() {
struct StructuredDocument {
    format: StructuredFormat,
    loaded_source: String,
    root: Node,
    stats: DocumentStats,
}

struct Node {
    span: SourceSpan,
    value: NodeValue,
}

enum NodeValue {
    Mapping(Vec<MappingEntry>),
    Sequence(Vec<Node>),
    String(String),
    Number(NumberLexeme),
    Boolean(bool),
    Null,
}

struct MappingEntry {
    decoded_key: String,
    key_span: SourceSpan,
    value: Node,
}
}

SourceSpan is a zero-based, half-open UTF-8 byte range. Human-facing line and column locations are one-based, with columns counted in Unicode scalar values rather than bytes or display cells. Every span and source location refers to loaded_source: the UTF-8 text supplied in Chapter.content after mdBook’s loading and BOM handling. Adapters normalize parser-native coordinates at this boundary. A span records parser-reported provenance; it does not promise that its source slice includes every syntactic delimiter. loaded_source remains the authority for the exact source text.

Sequence positions are represented by their ordered position, and mapping entries retain insertion order. Decoded keys are unique strings. Numbers retain their validated lexical form rather than being converted through f64. Strings retain their decoded value exactly: the core performs no trimming, case normalization, or simplification. The loaded source is retained separately so comments, quoting, whitespace, line endings, and formatting remain available to the Original source view.

Format adapters

Internally, format adapters satisfy a small parser seam equivalent to:

#![allow(unused)]
fn main() {
trait FormatAdapter {
    fn parse(
        &self,
        source: &str,
        source_name: &Path,
        limits: &Limits,
    ) -> Result<StructuredDocument, Diagnostic>;
}
}

The initial adapters use Rust parser libraries that expose the ordering and source provenance required by the public model. The JSON adapter uses json-syntax 0.12.x. The YAML adapter uses rlsp-yaml-parser with an exact =0.11.1 version requirement. Parser-library types remain private to their adapters.

The YAML adapter first consumes the library’s event stream as a bounded preflight, then uses its lossless loader with the library’s default Core schema to build the parsed tree. The library therefore owns YAML grammar, scalar decoding, tree construction, and scalar meaning. The adapter owns resource preflight, ordered projection into StructuredDocument, and conversion of parser errors and provenance. When projecting an implicitly resolved number, it retains the parser-returned plain-scalar spelling rather than converting it through a Rust numeric type. V1 does not define a separate YAML schema or an exhaustive YAML acceptance and rejection matrix.

rlsp-yaml-parser is a young, pre-1.0 dependency, so the exact pin is part of the v1 design. A version change must requalify the adapter through the public core contract tests. Keeping the dependency behind FormatAdapter allows it to be replaced without changing StructuredDocument or its callers.

After parsing succeeds, an adapter must project the parser result losslessly into StructuredDocument. Duplicate decoded mapping keys are rejected so the public mapping invariant remains unambiguous. Any other parsed construct that the model cannot represent without coercion, omission, or simplification causes an explicit projection diagnostic. The adapter must not silently discard data or substitute a reduced representation merely to complete the conversion.

Limits and diagnostics

The parser and renderer enforce bounded input, node, and depth limits. The v1 defaults are:

max-input-bytes = 1048576
max-nodes = 10000
max-depth = 64

The input-byte limit is checked before parser entry. The YAML event preflight counts nodes and open containers, so configured node and depth limits stop the input before the loader builds its tree or rendering begins. Parser-owned hard safety limits may reject an input earlier. Limits cause a diagnostic before unbounded expansion or output generation. A diagnostic identifies the source name, category, line and column when available, and the structured key path when the adapter can establish one.

The verification strategy treats this public interface—not parser-specific representations—as the test surface.