Skip to main content

schematron_engine/
resolver.rs

1//! External resource resolution for `<include href="...">` (issue #2).
2//!
3//! This crate never reads files or the network itself — `Cargo.toml`/
4//! `CLAUDE.md`'s "no host-format/I/O dependency in the core" policy
5//! applies here too. A caller who wants `<include>` support implements
6//! [`SchemaResolver`] over whatever loading mechanism (files, an
7//! in-memory map, HTTP, ...) fits, with whatever security boundary
8//! (allowed schemes, path restrictions) is appropriate there, and calls
9//! [`crate::parse_with_resolver`] instead of [`crate::parse`]. Mirrors the
10//! sister crate `relax-ng`'s `SchemaResolver`/`SchemaSource` shape (same
11//! problem — external `href` resolution without owning I/O — same shape).
12//!
13//! `<extends href="...">` (external-file rule reuse, the other half of
14//! issue #2) is *not* wired up to this trait yet — the ISO reference
15//! implementation (`iso_dsdl_include.xsl`) itself labels `extends[@href]`
16//! "experimental and non-standard", with different substitution semantics
17//! than `<include>` (it splices the target element's *children*, not the
18//! element itself, and shares `<include>`'s own `#fragment-id` lookup
19//! complexity). Deferred rather than rushed — see `README.md`'s support
20//! matrix.
21
22use std::fmt;
23
24/// Schema text plus the base URI relative `href`s inside it resolve
25/// against.
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct SchemaSource {
28    text: String,
29    base_uri: String,
30}
31
32impl SchemaSource {
33    /// Builds a source from its text and base URI (used to resolve any
34    /// relative `href`s this schema fragment itself contains).
35    pub fn new(text: impl Into<String>, base_uri: impl Into<String>) -> Self {
36        Self {
37            text: text.into(),
38            base_uri: base_uri.into(),
39        }
40    }
41
42    /// The schema fragment's raw XML text.
43    pub fn text(&self) -> &str {
44        &self.text
45    }
46
47    /// The base URI relative `href`s in this fragment resolve against.
48    pub fn base_uri(&self) -> &str {
49        &self.base_uri
50    }
51}
52
53/// Resolves an `<include href="...">` `href` (relative to a base URI) to
54/// the [`SchemaSource`] it names. This crate has no file-system or network
55/// access of its own — implement this over whatever loading mechanism
56/// fits the caller.
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 {}