relax_ng/datatypes/registry.rs
1//! The plugin surface: a [`DatatypeLibrary`] trait one `datatypeLibrary` URI
2//! implements, and a [`DatatypeRegistry`] mapping URIs to implementations
3//! that [`crate::validate`] takes as a parameter.
4//!
5//! [`DatatypeRegistry::new`] always has the RELAX NG built-in (empty-string)
6//! library and the XSD library (`http://www.w3.org/2001/XMLSchema-datatypes`)
7//! registered — see `plan/DECISIONS.md`'s Phase 06 entry for why both are
8//! treated as "this crate natively supports them" rather than something a
9//! caller must supply. Anything else (e.g. vnu's own
10//! `http://whattf.org/datatype-draft`) is the caller's responsibility to
11//! implement and [`DatatypeRegistry::register`] — this crate has no
12//! knowledge of any non-standard library's types.
13
14use std::collections::BTreeMap;
15use std::fmt;
16
17use super::{builtin, xsd};
18
19/// The namespace URI identifying the XSD datatype library, as used in
20/// `datatypeLibrary="..."` — see `plan/DECISIONS.md`.
21pub const XSD_DATATYPE_LIBRARY: &str = "http://www.w3.org/2001/XMLSchema-datatypes";
22
23/// The in-scope XML namespace prefix bindings at the point a value occurs
24/// in the *document* (not the schema) — needed to resolve `QName`/
25/// `NOTATION`-typed values, whose lexical form (`prefix:local`) only
26/// denotes a value together with a prefix-to-URI binding. Built once per
27/// element from [`crate::Element::namespace_bindings`].
28#[derive(Clone, Debug, Default)]
29pub struct DatatypeContext {
30 bindings: Vec<(String, String)>,
31}
32
33impl DatatypeContext {
34 pub(crate) fn empty() -> Self {
35 Self {
36 bindings: Vec::new(),
37 }
38 }
39
40 pub(crate) fn from_bindings(bindings: Vec<(String, String)>) -> Self {
41 Self { bindings }
42 }
43
44 /// The namespace URI bound to `prefix` (`""` for the default
45 /// namespace), or `None` if no such binding is in scope. Later
46 /// bindings win over earlier ones, matching normal XML shadowing.
47 pub fn resolve(&self, prefix: &str) -> Option<&str> {
48 self.bindings
49 .iter()
50 .rev()
51 .find(|(bound_prefix, _)| bound_prefix == prefix)
52 .map(|(_, uri)| uri.as_str())
53 }
54}
55
56/// A `datatypeLibrary` implementation: recognizes a set of type names,
57/// each with its own applicable parameters (facets, in XSD's terminology).
58///
59/// Modeled on the datatype interface the RELAX NG spec itself describes
60/// (type validity, parameter validity, value matching, value equality) —
61/// independently designed for Rust, not ported from any existing
62/// implementation.
63pub trait DatatypeLibrary: Send + Sync {
64 /// Checks that `type_name` is known to this library and that `params`
65 /// are individually well-formed and applicable to it (e.g. XSD's
66 /// `pattern` facet takes a regular expression; `minInclusive` only
67 /// applies to ordered types). Called once per `data`/`value` pattern,
68 /// before any document is validated, so a bad schema fails fast with a
69 /// message identifying the problem.
70 fn validate_params(&self, type_name: &str, params: &[(&str, &str)]) -> Result<(), String>;
71
72 /// Whether `value` is a legal value of `type_name` (already checked
73 /// valid via [`Self::validate_params`]) satisfying every param. Schema
74 /// `except` branches are handled by the caller, not here.
75 fn matches(
76 &self,
77 type_name: &str,
78 params: &[(&str, &str)],
79 value: &str,
80 context: &DatatypeContext,
81 ) -> bool;
82
83 /// Whether `expected` (the schema's `<value>` literal, resolved
84 /// against `expected_context` — the schema XML's *own* in-scope
85 /// namespace bindings and its `ns` attribute, for `QName`/`NOTATION`-
86 /// like types) and `actual` (the document's text, resolved against
87 /// `actual_context` — the document's bindings at that point) denote
88 /// the same value of `type_name`. The two contexts are deliberately
89 /// separate: a `<value>` literal's prefixes are the *schema* author's,
90 /// never the validated document's.
91 fn values_equal(
92 &self,
93 type_name: &str,
94 expected: &str,
95 expected_context: &DatatypeContext,
96 actual: &str,
97 actual_context: &DatatypeContext,
98 ) -> bool;
99}
100
101/// The schema uses a `datatypeLibrary`/type/parameter this
102/// [`DatatypeRegistry`] can't handle.
103#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct DatatypeError {
105 library: String,
106 message: String,
107}
108
109impl DatatypeError {
110 pub(crate) fn unsupported_library(library: impl Into<String>) -> Self {
111 let library = library.into();
112 Self {
113 message: format!("unsupported datatype library `{library}`"),
114 library,
115 }
116 }
117
118 pub(crate) fn type_problem(library: impl Into<String>, type_name: &str, reason: &str) -> Self {
119 let library = library.into();
120 Self {
121 message: format!("datatype `{type_name}` in library `{library}`: {reason}"),
122 library,
123 }
124 }
125
126 /// The offending `datatypeLibrary` URI (the empty string denotes the
127 /// built-in library).
128 pub fn library(&self) -> &str {
129 &self.library
130 }
131}
132
133impl fmt::Display for DatatypeError {
134 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
135 formatter.write_str(&self.message)
136 }
137}
138
139impl std::error::Error for DatatypeError {}
140
141/// Maps `datatypeLibrary` URIs to [`DatatypeLibrary`] implementations for
142/// one [`crate::validate`] call.
143pub struct DatatypeRegistry {
144 libraries: BTreeMap<String, Box<dyn DatatypeLibrary>>,
145}
146
147impl DatatypeRegistry {
148 /// A registry with the RELAX NG built-in library (`""`) and the XSD
149 /// library registered — the two this crate implements itself. Use
150 /// [`Self::builtin_only`] to reject `xsd:`-typed schemas explicitly, or
151 /// [`Self::register`] to add further libraries (e.g. a caller's own
152 /// non-standard one).
153 pub fn new() -> Self {
154 let mut registry = Self::builtin_only();
155 registry.register(XSD_DATATYPE_LIBRARY, xsd::XsdLibrary);
156 registry
157 }
158
159 /// A registry with only the mandatory RELAX NG built-in library (`""`)
160 /// registered — every other `datatypeLibrary`, including XSD, fails
161 /// fast with [`DatatypeError`].
162 pub fn builtin_only() -> Self {
163 let mut libraries: BTreeMap<String, Box<dyn DatatypeLibrary>> = BTreeMap::new();
164 libraries.insert(String::new(), Box::new(builtin::BuiltinLibrary));
165 Self { libraries }
166 }
167
168 /// Registers `library` under `library_uri`, replacing any previous
169 /// registration for that URI (including the built-in ones, should a
170 /// caller want to override them — not recommended, but not prevented).
171 pub fn register(
172 &mut self,
173 library_uri: impl Into<String>,
174 library: impl DatatypeLibrary + 'static,
175 ) -> &mut Self {
176 self.libraries.insert(library_uri.into(), Box::new(library));
177 self
178 }
179
180 pub(crate) fn get(&self, library_uri: &str) -> Option<&dyn DatatypeLibrary> {
181 self.libraries.get(library_uri).map(Box::as_ref)
182 }
183}
184
185impl Default for DatatypeRegistry {
186 fn default() -> Self {
187 Self::new()
188 }
189}