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}