relax_ng/resolver.rs
1use std::fmt;
2
3/// Which of RELAX NG's two equivalent syntaxes a [`SchemaSource`]'s text is
4/// written in.
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum SchemaSyntax {
7 /// The compact (non-XML) syntax, conventionally using a `.rnc` file
8 /// extension.
9 Compact,
10 /// The XML syntax, conventionally using a `.rng` file extension.
11 Xml,
12}
13
14/// Schema text plus enough context (a base URI, and which syntax it's
15/// written in) to parse it and resolve any `include`/`externalRef` it
16/// contains. This crate never reads files or network resources itself —
17/// callers supply schema text directly, and a [`SchemaResolver`] for
18/// anything the schema references by `href`.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct SchemaSource {
21 text: String,
22 base_uri: String,
23 syntax: SchemaSyntax,
24}
25
26impl SchemaSource {
27 /// Builds a source from its text, base URI (used to resolve any
28 /// relative `href`s the schema references), and syntax.
29 pub fn new(text: impl Into<String>, base_uri: impl Into<String>, syntax: SchemaSyntax) -> Self {
30 Self {
31 text: text.into(),
32 base_uri: base_uri.into(),
33 syntax,
34 }
35 }
36
37 /// The schema's raw text.
38 pub fn text(&self) -> &str {
39 &self.text
40 }
41 /// The base URI relative `href`s in this schema resolve against.
42 pub fn base_uri(&self) -> &str {
43 &self.base_uri
44 }
45 /// Which syntax [`Self::text`] is written in.
46 pub fn syntax(&self) -> SchemaSyntax {
47 self.syntax
48 }
49}
50
51/// Resolves an `include`/`externalRef` `href` (relative to a base URI) to
52/// the [`SchemaSource`] it names. This crate has no file-system or
53/// network access of its own — implement this over whatever loading
54/// mechanism (files, an in-memory map, HTTP, ...) fits the caller, with
55/// whatever security boundary (allowed schemes, path restrictions) is
56/// appropriate there.
57pub trait SchemaResolver {
58 /// Resolves `href` (as it literally appears in the schema) relative to
59 /// `base_uri`, or fails with a [`ResolveError`] if it can't be found,
60 /// read, or is disallowed.
61 fn resolve(&self, href: &str, base_uri: &str) -> Result<SchemaSource, ResolveError>;
62}
63
64/// A [`SchemaResolver`] failed to resolve an `href`.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct ResolveError {
67 message: String,
68}
69
70impl ResolveError {
71 /// Builds a `ResolveError` with the given message.
72 pub fn new(message: impl Into<String>) -> Self {
73 Self {
74 message: message.into(),
75 }
76 }
77}
78
79impl fmt::Display for ResolveError {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 formatter.write_str(&self.message)
82 }
83}
84
85impl std::error::Error for ResolveError {}