Skip to main content

fig_schema/
lib.rs

1//! `fig-schema` — the schema layer: what a field *expects* — its type, its
2//! allowed values, and how to present it — layered over [`fig`]'s schema-free
3//! value tree.
4//!
5//! fig parses bytes → [`fig::Value`] and edits losslessly; it has no notion of
6//! "what is valid here". This crate adds that knowledge as a **generic,
7//! embedder-agnostic** engine. It never learns the word "prov" or "flower": a
8//! consumer (prov, for frontmatter fields; flower, for a metadata editor)
9//! defines its own constraint type — an enum covering whatever kinds of
10//! constraint it needs (a controlled vocabulary, a reference into a
11//! workspace, …) — and implements [`Validate`] on it. [`FieldRule`]/[`Schema`]
12//! are generic over that type, so the path-matching and commit-time
13//! validation plumbing is written once, here, and reused by every embedder.
14//!
15//! What's genuinely reusable, and lives here as concrete types rather than
16//! being left to the embedder:
17//!
18//! - [`PathPat`] / [`SegPat`] — pattern-matching a fig path, including "every
19//!   item of this list" ([`SegPat::EachItem`]) and "this subtree"
20//!   ([`SegPat::AnyDepth`]).
21//! - [`FieldType`] — the expected type, and type-directed coercion of an edit
22//!   buffer ([`FieldType::coerce`]).
23//! - [`Term`] / [`Cardinality`] / [`validate_enum`] — a controlled vocabulary
24//!   and the logic to check a value against one (closed-vocabulary rejection,
25//!   open-vocabulary near-miss warnings). Cardinality (one vs. many) is pure
26//!   data shape, useful even to a constraint this crate doesn't otherwise model
27//!   (a relation/reference field, for instance).
28//! - [`Presentation`] / [`Icon`] / [`Tint`] — renderer-neutral display hints,
29//!   carried on every rule but never interpreted here.
30//! - [`Issue`] / [`IssueKind`] — why a value failed, as data rather than
31//!   prose, so the embedder owns the wording. [`Issue`]'s `Display` renders a
32//!   reasonable English default for embedders that don't care.
33//!
34//! # Example
35//!
36//! ```
37//! use fig::Value;
38//! use fig_schema::{
39//!     FieldRule, FieldType, PathPat, Presentation, Schema, Seg, Term, Validate,
40//!     Validation, validate_enum,
41//! };
42//!
43//! // The embedder's own constraint type — the seam this crate is built around.
44//! struct Vocabulary { values: Vec<Term>, closed: bool }
45//!
46//! impl Validate for Vocabulary {
47//!     fn validate(&self, value: &Value) -> Validation {
48//!         validate_enum(&self.values, self.closed, value)
49//!     }
50//! }
51//!
52//! let schema = Schema::new(vec![FieldRule {
53//!     at: PathPat::each_item_of("audience"),
54//!     ty: Some(FieldType::Str),
55//!     constraint: Some(Vocabulary {
56//!         values: vec![Term::value("public"), Term::value("family")],
57//!         closed: true,
58//!     }),
59//!     present: Presentation::default(),
60//! }]);
61//!
62//! // Find the rule governing `audience[0]`, then check a candidate against it.
63//! let path = [Seg::Key("audience".into()), Seg::Index(0)];
64//! let rule = schema.rule_for(&path).expect("a rule governs this path");
65//! assert!(rule.validate(&Value::Str("public".into())).is_ok());
66//!
67//! let rejected = rule.validate(&Value::Str("familly".into()));
68//! assert!(rejected.is_reject());
69//! assert_eq!(
70//!     rejected.issue().unwrap().suggestion.as_deref(),
71//!     Some("family"),
72//! );
73//! ```
74
75mod field;
76mod path;
77mod present;
78mod vocab;
79
80pub use field::{FieldRule, FieldType, Schema};
81pub use path::{PathPat, Seg, SegPat};
82pub use present::{Icon, Presentation, Tint};
83pub use vocab::{Cardinality, Issue, IssueKind, Term, Validate, Validation, validate_enum};