1use crate::parser::location::SourceLocation;
6use thiserror::Error;
7
8pub type SchemaResult<T> = Result<T, SchemaError>;
10
11pub type FacetResult<T> = Result<T, FacetError>;
13
14#[derive(Error, Debug, Clone)]
16pub enum FacetError {
17 #[error("length constraint violation: {message}")]
19 LengthViolation { message: String },
20
21 #[error("minLength constraint violation: value length {actual} is less than minimum {min}")]
23 MinLengthViolation { actual: u64, min: u64 },
24
25 #[error("maxLength constraint violation: value length {actual} exceeds maximum {max}")]
27 MaxLengthViolation { actual: u64, max: u64 },
28
29 #[error("pattern constraint violation: value '{value}' does not match pattern '{pattern}'")]
31 PatternViolation { value: String, pattern: String },
32
33 #[error("enumeration constraint violation: value '{value}' is not in the allowed set")]
35 EnumerationViolation { value: String },
36
37 #[error("minInclusive constraint violation: value '{value}' is less than minimum '{min}'")]
39 MinInclusiveViolation { value: String, min: String },
40
41 #[error("maxInclusive constraint violation: value '{value}' is greater than maximum '{max}'")]
43 MaxInclusiveViolation { value: String, max: String },
44
45 #[error("minExclusive constraint violation: value '{value}' is not greater than '{min}'")]
47 MinExclusiveViolation { value: String, min: String },
48
49 #[error("maxExclusive constraint violation: value '{value}' is not less than '{max}'")]
51 MaxExclusiveViolation { value: String, max: String },
52
53 #[error("totalDigits constraint violation: value has {actual} digits, maximum is {max}")]
55 TotalDigitsViolation { actual: u32, max: u32 },
56
57 #[error(
59 "fractionDigits constraint violation: value has {actual} fraction digits, maximum is {max}"
60 )]
61 FractionDigitsViolation { actual: u32, max: u32 },
62
63 #[error("explicitTimezone constraint violation: {message}")]
65 ExplicitTimezoneViolation { message: String },
66
67 #[error("invalid pattern regex '{pattern}': {message}")]
69 InvalidPattern { pattern: String, message: String },
70
71 #[error("derivation restriction violation: {message}")]
73 DerivationRestriction { message: String },
74
75 #[error("fixed facet violation: cannot override fixed {facet_name} value '{base_value}' with '{derived_value}'")]
77 FixedFacetViolation {
78 facet_name: String,
79 base_value: String,
80 derived_value: String,
81 },
82
83 #[error("conflicting facets: {message}")]
85 ConflictingFacets { message: String },
86
87 #[error("facet '{facet}' is not applicable to type '{type_name}'")]
89 NotApplicable { facet: String, type_name: String },
90}
91
92impl FacetError {
93 pub fn length(message: impl Into<String>) -> Self {
95 FacetError::LengthViolation {
96 message: message.into(),
97 }
98 }
99
100 pub fn pattern(value: impl Into<String>, pattern: impl Into<String>) -> Self {
102 FacetError::PatternViolation {
103 value: value.into(),
104 pattern: pattern.into(),
105 }
106 }
107
108 pub fn enumeration(value: impl Into<String>) -> Self {
110 FacetError::EnumerationViolation {
111 value: value.into(),
112 }
113 }
114
115 pub fn derivation(message: impl Into<String>) -> Self {
117 FacetError::DerivationRestriction {
118 message: message.into(),
119 }
120 }
121
122 pub fn fixed_violation(
124 facet_name: impl Into<String>,
125 base_value: impl Into<String>,
126 derived_value: impl Into<String>,
127 ) -> Self {
128 FacetError::FixedFacetViolation {
129 facet_name: facet_name.into(),
130 base_value: base_value.into(),
131 derived_value: derived_value.into(),
132 }
133 }
134
135 pub fn conflicting(message: impl Into<String>) -> Self {
137 FacetError::ConflictingFacets {
138 message: message.into(),
139 }
140 }
141}
142
143#[derive(Error, Debug)]
145pub enum SchemaError {
146 #[error("XML parse error{}: {message}", location_str(.location))]
148 XmlError {
149 message: String,
150 location: Option<SourceLocation>,
151 },
152
153 #[error("Schema structural error{}: {message} (constraint: {constraint})", location_str(.location))]
155 StructuralError {
156 constraint: &'static str,
157 message: String,
158 location: Option<SourceLocation>,
159 },
160
161 #[error("Namespace error{}: {message}", location_str(.location))]
163 NamespaceError {
164 message: String,
165 location: Option<SourceLocation>,
166 },
167
168 #[error("Feature not supported{}: {message}", location_str(.location))]
170 FeatureError {
171 message: String,
172 location: Option<SourceLocation>,
173 },
174
175 #[error("Schema resolution error: {message}")]
177 ResolutionError { message: String },
178
179 #[error("I/O error: {0}")]
181 IoError(#[from] std::io::Error),
182
183 #[error("Internal error: {0}")]
185 Internal(String),
186}
187
188impl SchemaError {
189 pub fn structural(
191 constraint: &'static str,
192 message: impl Into<String>,
193 location: Option<SourceLocation>,
194 ) -> Self {
195 SchemaError::StructuralError {
196 constraint,
197 message: message.into(),
198 location,
199 }
200 }
201
202 pub fn namespace(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
204 SchemaError::NamespaceError {
205 message: message.into(),
206 location,
207 }
208 }
209
210 pub fn feature(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
212 SchemaError::FeatureError {
213 message: message.into(),
214 location,
215 }
216 }
217
218 pub fn resolution(message: impl Into<String>) -> Self {
220 SchemaError::ResolutionError {
221 message: message.into(),
222 }
223 }
224
225 pub fn xml(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
227 SchemaError::XmlError {
228 message: message.into(),
229 location,
230 }
231 }
232
233 pub fn internal(message: impl Into<String>) -> Self {
235 SchemaError::Internal(message.into())
236 }
237
238 pub fn with_location(self, location: SourceLocation) -> Self {
240 match self {
241 SchemaError::XmlError {
242 message,
243 location: None,
244 } => SchemaError::XmlError {
245 message,
246 location: Some(location),
247 },
248 SchemaError::StructuralError {
249 constraint,
250 message,
251 location: None,
252 } => SchemaError::StructuralError {
253 constraint,
254 message,
255 location: Some(location),
256 },
257 SchemaError::NamespaceError {
258 message,
259 location: None,
260 } => SchemaError::NamespaceError {
261 message,
262 location: Some(location),
263 },
264 SchemaError::FeatureError {
265 message,
266 location: None,
267 } => SchemaError::FeatureError {
268 message,
269 location: Some(location),
270 },
271 other => other,
273 }
274 }
275
276 pub fn is_schema_content_error(&self) -> bool {
283 matches!(
284 self,
285 SchemaError::StructuralError { .. }
286 | SchemaError::NamespaceError { .. }
287 | SchemaError::XmlError { .. }
288 | SchemaError::FeatureError { .. }
289 )
290 }
291}
292
293impl From<quick_xml::Error> for SchemaError {
295 fn from(err: quick_xml::Error) -> Self {
296 SchemaError::XmlError {
297 message: err.to_string(),
298 location: None,
299 }
300 }
301}
302
303fn location_str(loc: &Option<SourceLocation>) -> String {
305 match loc {
306 Some(l) => format!(" at {}", l),
307 None => String::new(),
308 }
309}