Skip to main content

opys_engine/mdprism/
schema.rs

1//! The parsed schema data model — the in-memory form of the DSL.
2//!
3//! See `docs/structure-dsl-spec.md` for the language this represents.
4
5/// A parsed schema: per-schema options, the frontmatter field schemas, and the
6/// body node tree.
7#[derive(Debug, Clone, PartialEq)]
8pub struct Schema {
9    pub opts: SchemaOpts,
10    pub frontmatter: Vec<FieldSchema>,
11    pub body: Vec<Node>,
12}
13
14/// The `%`-directive options. Defaults match the spec: strict ordering, strict
15/// matching, closed frontmatter.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct SchemaOpts {
18    /// Body nodes must appear in declared order (`%ordered`).
19    pub ordered: bool,
20    /// Error on mismatch / unexpected blocks (`%strict`).
21    pub strict: bool,
22    /// Allow undeclared frontmatter keys (`%frontmatter = open`).
23    pub frontmatter_open: bool,
24}
25
26impl Default for SchemaOpts {
27    fn default() -> Self {
28        SchemaOpts {
29            ordered: true,
30            strict: true,
31            frontmatter_open: false,
32        }
33    }
34}
35
36/// One typed frontmatter key.
37#[derive(Debug, Clone, PartialEq)]
38pub struct FieldSchema {
39    pub key: String,
40    /// Capture alias; defaults to `key` unless renamed with `@name`.
41    pub alias: String,
42    pub optional: bool,
43    pub ty: FieldType,
44    pub desc: Option<String>,
45}
46
47/// A frontmatter value type. `Regex` keeps the pattern *source* so the type is
48/// `PartialEq`/`Clone`; compile it on demand.
49#[derive(Debug, Clone, PartialEq)]
50pub enum FieldType {
51    Str,
52    Int,
53    Bool,
54    Date,
55    Enum(Vec<String>),
56    List(Box<FieldType>),
57    Regex(String),
58}
59
60/// A body node: a heading (with children), a list (with a per-item child
61/// schema), or a required paragraph.
62#[derive(Debug, Clone, PartialEq)]
63pub enum Node {
64    Heading {
65        level: u8,
66        title: Match,
67        head: Head,
68        children: Vec<Node>,
69    },
70    List {
71        style: ListStyle,
72        item: Option<Match>,
73        head: Head,
74        children: Vec<Node>,
75    },
76    Prose {
77        text: Option<Match>,
78        head: Head,
79    },
80}
81
82impl Node {
83    pub(crate) fn set_children(&mut self, kids: Vec<Node>) {
84        match self {
85            Node::Heading { children, .. } | Node::List { children, .. } => *children = kids,
86            // Prose has no children; ignore (the parser rejects indented prose).
87            Node::Prose { .. } => {}
88        }
89    }
90}
91
92/// The shared annotation head of every node: alias, cardinality, description.
93#[derive(Debug, Clone, PartialEq, Eq, Default)]
94pub struct Head {
95    /// Explicit `@name`. `None` means "auto-derive" (headings slug their title).
96    pub name: Option<String>,
97    pub card: Card,
98    pub desc: Option<String>,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum ListStyle {
103    Bullet,
104    Ordered,
105    Checklist,
106}
107
108/// A label: a literal the text must start with, or a regex (pattern source) the
109/// text must match. A bare heading title is a `Literal`.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub enum Match {
112    Literal(String),
113    Regex(String),
114}
115
116/// Cardinality. On a list it bounds item count; on a heading/prose it is
117/// presence. `Required` is the bare default.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
119pub enum Card {
120    #[default]
121    Required,
122    Optional,
123    Star,
124    Plus,
125    Range(u32, Option<u32>),
126}