Skip to main content

sup_xml_core/xsd/
error.rs

1//! Errors emitted by the schema compiler and validator.
2
3use std::fmt;
4
5/// Error returned by [`Schema::compile_str`](crate::xsd::Schema::compile_str)
6/// and friends.  Schema compilation produces at most one error — the
7/// compiler bails on the first problem since later phases assume a
8/// well-formed type graph.
9#[derive(Debug, Clone)]
10pub struct SchemaCompileError {
11    pub message: String,
12    pub line:    Option<u32>,
13    pub column:  Option<u32>,
14}
15
16impl SchemaCompileError {
17    pub fn msg(s: impl Into<String>) -> Self {
18        Self { message: s.into(), line: None, column: None }
19    }
20
21    pub fn at(mut self, line: u32, column: u32) -> Self {
22        self.line   = Some(line);
23        self.column = Some(column);
24        self
25    }
26}
27
28impl fmt::Display for SchemaCompileError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        if let (Some(l), Some(c)) = (self.line, self.column) {
31            write!(f, "{l}:{c}: ")?;
32        }
33        f.write_str(&self.message)
34    }
35}
36
37impl std::error::Error for SchemaCompileError {}
38
39impl From<crate::error::XmlError> for SchemaCompileError {
40    fn from(e: crate::error::XmlError) -> Self {
41        let mut out = Self::msg(e.message);
42        out.line   = e.line;
43        out.column = e.column;
44        out
45    }
46}
47
48// ── validation ───────────────────────────────────────────────────────────────
49
50/// Returned by [`Schema::validate_str`](crate::xsd::Schema::validate_str).
51/// May contain one or many issues depending on
52/// [`ValidationOptions::fail_fast`].
53#[derive(Debug, Clone)]
54pub struct ValidationError {
55    pub issues: Vec<ValidationIssue>,
56}
57
58impl ValidationError {
59    pub fn single(issue: ValidationIssue) -> Self {
60        Self { issues: vec![issue] }
61    }
62}
63
64impl fmt::Display for ValidationError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self.issues.len() {
67            0 => f.write_str("(no issues)"),
68            1 => self.issues[0].fmt(f),
69            n => write!(f, "{n} validation issues; first: {}", self.issues[0]),
70        }
71    }
72}
73
74impl std::error::Error for ValidationError {}
75
76/// One validation problem discovered while walking an instance document.
77#[derive(Debug, Clone)]
78pub struct ValidationIssue {
79    pub message: String,
80    pub line:    Option<u32>,
81    pub column:  Option<u32>,
82    /// XPath-ish locator into the instance document — e.g.
83    /// `/invoice/items/item[3]/@price`.  Empty string at the document root.
84    pub path:    String,
85    pub kind:    ValidationKind,
86    /// Element names the content model expected at an
87    /// [`UnexpectedElement`](ValidationKind::UnexpectedElement) failure —
88    /// used by the libxml2-compat shim to render "Expected is ( … )".
89    /// Empty for other kinds.
90    pub expected: Vec<String>,
91    /// The offending lexical value for a
92    /// [`TypeMismatch`](ValidationKind::TypeMismatch); lets the shim
93    /// render libxml2's "'value' is not a valid value …".
94    pub value: Option<String>,
95    /// The simple type's name (e.g. `"xs:integer"`) for a datatype
96    /// mismatch — the "atomic type 'xs:integer'" in libxml2's wording.
97    pub type_name: Option<String>,
98}
99
100impl fmt::Display for ValidationIssue {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        if let (Some(l), Some(c)) = (self.line, self.column) {
103            write!(f, "{l}:{c}: ")?;
104        }
105        if !self.path.is_empty() {
106            write!(f, "at {}: ", self.path)?;
107        }
108        f.write_str(&self.message)
109    }
110}
111
112/// Categorical tag for a validation issue.  Use for programmatic dispatch
113/// rather than string-matching the message.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum ValidationKind {
116    UnexpectedElement,
117    UnexpectedAttribute,
118    MissingRequiredElement,
119    MissingRequiredAttribute,
120    /// Lexical value doesn't match its declared type.
121    TypeMismatch,
122    /// Value parses but a facet (pattern, enumeration, length, range, …)
123    /// rejects it.
124    FacetViolation,
125    /// `xs:key`-declared value appears more than once in scope.
126    KeyNotUnique,
127    /// `xs:keyref` references a key value that doesn't exist.
128    KeyRefDangling,
129    /// An element is in a substitution group but isn't substitutable for
130    /// the expected head (incompatible type).
131    SubstitutionMismatch,
132    /// `xsi:nil="true"` on a non-nillable element, or with content.
133    NillableViolation,
134    /// XSD 1.1 `xs:assert` / `xs:assertion` evaluated to `false`
135    /// against the instance.  cvc-assertion.
136    AssertionViolation,
137    Other,
138}
139
140// ── options ──────────────────────────────────────────────────────────────────
141
142/// Tunables for [`Schema::validate_str_opts`](crate::xsd::Schema::validate_str_opts).
143#[derive(Debug, Clone)]
144pub struct ValidationOptions {
145    /// Stop on the first issue.  Default `true`.  When `false`, the
146    /// validator collects up to [`max_issues`](Self::max_issues) issues
147    /// before returning.
148    pub fail_fast:  bool,
149    /// Cap on collected issues when `fail_fast` is off.  Default 1000.
150    pub max_issues: usize,
151    /// Augment the instance with schema-defined attribute value
152    /// constraints: when an attribute with a `default=` / `fixed=` value
153    /// is absent on an element, add it.  Only effective when validating a
154    /// live [`Document`](sup_xml_tree::dom::Document) (the source can then
155    /// mutate it); a no-op for string/byte sources.  Default `false`
156    /// (mirrors libxml2's `XML_SCHEMA_VAL_VC_I_CREATE` opt-in).
157    pub apply_attribute_defaults: bool,
158}
159
160impl Default for ValidationOptions {
161    fn default() -> Self {
162        Self { fail_fast: true, max_issues: 1000, apply_attribute_defaults: false }
163    }
164}