relax_ng/schema.rs
1//! The public entry point: [`Schema`] ties `parse`/`simplify`/`validate`
2//! together into a single "compile once, validate many documents"
3//! workflow, which is almost always what a caller wants — the lower-level
4//! [`crate::parse`]/[`crate::simplify`]/[`crate::validate`] functions
5//! remain available separately for callers who need the intermediate
6//! [`crate::SchemaDocument`]/[`CompiledSchema`] (e.g. to inspect a schema
7//! without validating anything against it).
8
9use std::fmt;
10
11use crate::{
12 CompiledSchema, DatatypeError, DatatypeRegistry, Element, ParseError, SchemaError,
13 SchemaResolver, SchemaSource, ValidationError, parse, simplify, validate,
14};
15
16/// A parsed and composed ("simplified", in RELAX NG spec terms) schema,
17/// ready to validate any number of documents against.
18///
19/// Compiling — parsing the schema text and resolving/composing any
20/// `include`/`externalRef`/`div`/`combine` it uses — is the relatively
21/// expensive step; [`Schema::compile`] does it once, and the resulting
22/// `Schema` is then reused for every document validated against it.
23///
24/// # Example
25///
26/// ```
27/// use relax_ng::{
28/// Content, DatatypeRegistry, Element, ExpandedName, ResolveError, Schema, SchemaResolver,
29/// SchemaSource, SchemaSyntax,
30/// };
31///
32/// // This schema is self-contained (no `include`/`externalRef`), so the
33/// // resolver is never actually called — it only needs to exist to
34/// // satisfy `Schema::compile`'s signature.
35/// struct NoResolver;
36/// impl SchemaResolver for NoResolver {
37/// fn resolve(&self, href: &str, _base_uri: &str) -> Result<SchemaSource, ResolveError> {
38/// Err(ResolveError::new(format!("no such resource: {href}")))
39/// }
40/// }
41///
42/// let source = SchemaSource::new(
43/// "element greeting { attribute lang { text }, text }",
44/// "memory:/greeting.rnc",
45/// SchemaSyntax::Compact,
46/// );
47/// let schema = Schema::compile(&source, &NoResolver).expect("schema compiles");
48///
49/// // A minimal `Element` implementation over an in-memory tree — a real
50/// // caller implements this over whatever tree type it already has (an
51/// // XML DOM, an HTML parser's tree, ...); this crate parses none of
52/// // that itself.
53/// struct Doc {
54/// name: &'static str,
55/// attributes: Vec<(&'static str, &'static str)>,
56/// text: &'static str,
57/// }
58/// impl Element for Doc {
59/// type Location = ();
60///
61/// fn name(&self) -> ExpandedName {
62/// ExpandedName::new(None::<String>, self.name)
63/// }
64/// fn attributes(&self) -> impl Iterator<Item = (ExpandedName, String)> {
65/// self.attributes
66/// .iter()
67/// .map(|(name, value)| (ExpandedName::new(None::<String>, *name), value.to_string()))
68/// }
69/// fn children(&self) -> impl Iterator<Item = Content<Self>> {
70/// std::iter::once(Content::Text(self.text.to_owned()))
71/// }
72/// }
73///
74/// let document = Doc {
75/// name: "greeting",
76/// attributes: vec![("lang", "en")],
77/// text: "hello",
78/// };
79///
80/// // The built-in `string`/`token` datatypes are all this schema needs.
81/// let errors = schema
82/// .validate(&DatatypeRegistry::new(), &document)
83/// .expect("only built-in/XSD datatypes are used");
84/// assert!(errors.is_empty());
85/// ```
86#[derive(Clone, Debug)]
87pub struct Schema {
88 compiled: CompiledSchema,
89}
90
91impl Schema {
92 /// Parses `source` and composes it into a ready-to-validate schema.
93 /// `resolver` is consulted for any `include`/`externalRef` the schema
94 /// (transitively) uses; if the schema is known to be self-contained,
95 /// any resolver that always returns `Err` works (it will never
96 /// actually be called).
97 pub fn compile(
98 source: &SchemaSource,
99 resolver: &impl SchemaResolver,
100 ) -> Result<Schema, CompileError> {
101 let document = parse(source)?;
102 let compiled = simplify(&document, resolver)?;
103 Ok(Schema { compiled })
104 }
105
106 /// Validates `root` (and, transitively, everything reachable from it)
107 /// against this schema. `registry` supplies the `data`/`value`
108 /// pattern semantics — see [`DatatypeRegistry`] for what's built in
109 /// and how to add more.
110 ///
111 /// Returns the list of problems found — empty means `root` is valid.
112 /// Returns [`DatatypeError`] instead of validating at all if the
113 /// schema uses a `datatypeLibrary`/type/parameter `registry` doesn't
114 /// recognize, since whether the document would pass a check that
115 /// can't be performed is unknowable.
116 pub fn validate<E: Element>(
117 &self,
118 registry: &DatatypeRegistry,
119 root: &E,
120 ) -> Result<Vec<ValidationError<E::Location>>, DatatypeError> {
121 validate(&self.compiled, registry, root)
122 }
123
124 /// The underlying compiled representation, for callers that need it
125 /// directly (e.g. [`CompiledSchema::definition_count`] for
126 /// diagnostics) rather than going through [`Schema::validate`].
127 pub fn compiled(&self) -> &CompiledSchema {
128 &self.compiled
129 }
130}
131
132/// Either step [`Schema::compile`] performs can fail on its own: `source`
133/// might not even parse (malformed `.rnc`/`.rng` syntax — [`ParseError`]),
134/// or it might parse but fail composition (an undefined `ref`, illegal
135/// recursion, a §7 restriction violation, ... — [`SchemaError`]).
136#[derive(Clone, Debug, Eq, PartialEq)]
137pub enum CompileError {
138 /// `source` isn't well-formed RELAX NG in its declared syntax.
139 Parse(ParseError),
140 /// `source` parsed, but composing/simplifying it failed (an undefined
141 /// `ref`, illegal recursion, a §7 restriction violation, ...).
142 Schema(SchemaError),
143}
144
145impl fmt::Display for CompileError {
146 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 Self::Parse(error) => write!(formatter, "{error}"),
149 Self::Schema(error) => write!(formatter, "{error}"),
150 }
151 }
152}
153
154impl std::error::Error for CompileError {
155 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156 match self {
157 Self::Parse(error) => Some(error),
158 Self::Schema(error) => Some(error),
159 }
160 }
161}
162
163impl From<ParseError> for CompileError {
164 fn from(error: ParseError) -> Self {
165 Self::Parse(error)
166 }
167}
168
169impl From<SchemaError> for CompileError {
170 fn from(error: SchemaError) -> Self {
171 Self::Schema(error)
172 }
173}