Skip to main content

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 {}