Skip to main content

xsd_schema/xpath/
error.rs

1//! XPath error types.
2//!
3//! This module defines XPath 2.0 specification error codes as per the W3C XPath 2.0
4//! specification. Error codes follow the pattern:
5//! - XPST: Static errors detected during parsing/analysis
6//! - XPDY: Dynamic errors detected during evaluation
7//! - XPTY: Type errors
8//! - XQTY: XQuery type errors
9//! - FORG: Function and operators errors (general)
10//! - FOAR: Arithmetic errors
11//! - FOCA: Casting errors
12//! - FONS: Namespace errors
13//! - FODT: Date/time errors
14
15use thiserror::Error;
16
17/// XPath-specific error type with W3C specification error codes.
18#[derive(Debug, Clone, Error)]
19pub enum XPathError {
20    // ========================================================================
21    // Static Errors (XPST)
22    // ========================================================================
23    /// XPST0003: Syntax error in expression.
24    #[error("[XPST0003] Syntax error: {message}")]
25    XPST0003 { message: String },
26
27    /// XPST0008: QName is not defined (undefined variable or type).
28    #[error("[XPST0008] QName '{qname}' is not defined")]
29    XPST0008 { qname: String },
30
31    /// XPST0051: Unknown atomic type in sequence type.
32    #[error("[XPST0051] The type name '{type_name}' is not defined as an atomic type")]
33    XPST0051 { type_name: String },
34
35    /// XPST0017: Function not found.
36    #[error("[XPST0017] Function '{name}/{arity}' not found in namespace '{namespace}'")]
37    XPST0017 {
38        name: String,
39        arity: usize,
40        namespace: String,
41    },
42
43    /// XPST0081: Prefix cannot be expanded to namespace URI.
44    #[error("[XPST0081] Prefix '{prefix}' cannot be expanded to a namespace URI")]
45    XPST0081 { prefix: String },
46
47    // ========================================================================
48    // Dynamic Errors (XPDY)
49    // ========================================================================
50    /// XPDY0002: Context item is undefined (with message).
51    #[error("[XPDY0002] {message}")]
52    XPDY0002 { message: String },
53
54    /// XPDY0050: More than one item where singleton expected.
55    #[error("[XPDY0050] More than one item in sequence where single item expected")]
56    XPDY0050,
57
58    // ========================================================================
59    // Type Errors (XPTY)
60    // ========================================================================
61    /// XPTY0004: Type mismatch.
62    #[error("[XPTY0004] Type mismatch: expected '{expected}', found '{found}'")]
63    XPTY0004 { expected: String, found: String },
64
65    /// XPTY0004 variant: Only string literals can be cast to certain types.
66    #[error("[XPTY0004] Only string literals can be cast to type '{target_type}'")]
67    XPTY0004Cast { target_type: String },
68
69    /// XPTY0018: Path expression result contains both nodes and atomic values.
70    #[error("[XPTY0018] Path expression result contains both nodes and atomic values")]
71    XPTY0018,
72
73    /// XPTY0019: Step result must not be atomic value in path expression.
74    #[error("[XPTY0019] Step result in path expression must not be an atomic value")]
75    XPTY0019,
76
77    /// XPTY0020: Context item in axis step is not a node.
78    #[error("[XPTY0020] Context item for axis step is not a node")]
79    XPTY0020,
80
81    // ========================================================================
82    // XQuery Type Errors (XQTY)
83    // ========================================================================
84    /// XQTY0030: Validate expression argument must be single document or element.
85    #[error(
86        "[XQTY0030] Validate expression argument must be exactly one document or element node"
87    )]
88    XQTY0030,
89
90    // ========================================================================
91    // XQuery Static Errors (XQST)
92    // ========================================================================
93    /// XQST0076: Invalid collation URI.
94    #[error("[XQST0076] Collation '{collation}' is not supported")]
95    XQST0076 { collation: String },
96
97    // ========================================================================
98    // Function Errors - General (FORG)
99    // ========================================================================
100    /// FORG0001: Invalid value for cast/constructor.
101    #[error("[FORG0001] Invalid value '{value}' for cast to type '{target_type}'")]
102    FORG0001 { value: String, target_type: String },
103
104    /// FORG0003: fn:zero-or-one called with sequence > 1 item.
105    #[error("[FORG0003] fn:zero-or-one called with sequence containing more than one item")]
106    FORG0003,
107
108    /// FORG0004: fn:one-or-more called with empty sequence.
109    #[error("[FORG0004] fn:one-or-more called with empty sequence")]
110    FORG0004,
111
112    /// FORG0005: fn:exactly-one called with wrong cardinality.
113    #[error(
114        "[FORG0005] fn:exactly-one called with sequence containing zero or more than one item"
115    )]
116    FORG0005,
117
118    /// FORG0006: Invalid argument type for function.
119    #[error("[FORG0006] Function '{function}' called with invalid argument type '{arg_type}'")]
120    FORG0006Named { function: String, arg_type: String },
121
122    /// FORG0006: General effective boolean value error.
123    #[error("[FORG0006] {message}")]
124    FORG0006 { message: String },
125
126    /// FORG0008: Both arguments to fn:dateTime have a timezone
127    #[error("[FORG0008] Both arguments to fn:dateTime have a timezone")]
128    FORG0008,
129
130    // ========================================================================
131    // Character/Codepoint Errors (FOCH)
132    // ========================================================================
133    /// FOCH0001: Invalid codepoint.
134    #[error("[FOCH0001] Invalid codepoint '{codepoint}' in codepoints-to-string")]
135    FOCH0001 { codepoint: String },
136
137    /// FOCH0002: Unsupported collation.
138    #[error("[FOCH0002] Unsupported collation: '{collation}'")]
139    FOCH0002 { collation: String },
140
141    /// FOCH0003: Unsupported normalization form.
142    #[error("[FOCH0003] Unsupported normalization form '{normalization_form}'")]
143    FOCH0003 { normalization_form: String },
144
145    // ========================================================================
146    // Arithmetic Errors (FOAR)
147    // ========================================================================
148    /// FOAR0001: Division by zero.
149    #[error("[FOAR0001] Division by zero")]
150    FOAR0001,
151
152    /// FOAR0002: Numeric overflow or underflow.
153    #[error("[FOAR0002] Numeric operation overflow/underflow")]
154    FOAR0002,
155
156    // ========================================================================
157    // Casting Errors (FOCA)
158    // ========================================================================
159    /// FOCA0002: QName has null namespace but non-empty prefix.
160    #[error("[FOCA0002] QName '{qname}' has null namespace but non-empty prefix")]
161    FOCA0002 { qname: String },
162
163    /// FOCA0003: Input value too large for integer.
164    #[error("[FOCA0003] {message}")]
165    FOCA0003 { message: String },
166
167    /// FOCA0005: NaN supplied as float/double value.
168    #[error("[FOCA0005] NaN supplied as float/double value")]
169    FOCA0005,
170
171    // ========================================================================
172    // Date/Time Errors (FODT)
173    // ========================================================================
174    /// FODT0001: Overflow/underflow in date/time operation.
175    #[error("[FODT0001] Overflow/underflow in date/time operation")]
176    FODT0001,
177
178    /// FODT0002: Overflow/underflow in duration operation.
179    #[error("[FODT0002] Overflow/underflow in duration operation")]
180    FODT0002,
181
182    /// FODT0003: Invalid timezone value.
183    #[error("[FODT0003] Invalid timezone value: {value}")]
184    FODT0003 { value: String },
185
186    // ========================================================================
187    // XDM Typed Value Errors (FOTY)
188    // ========================================================================
189    /// FOTY0012: Argument node does not have a typed value.
190    #[error("[FOTY0012] Argument node does not have a typed value")]
191    FOTY0012,
192
193    // ========================================================================
194    // Namespace Errors (FONS)
195    // ========================================================================
196    /// FONS0005: Base-uri not defined in static context.
197    #[error("[FONS0005] Base-uri not defined in the static context")]
198    FONS0005,
199
200    // ========================================================================
201    // URI Errors (FORG)
202    // ========================================================================
203    /// FORG0009: Error in resolving a relative URI.
204    #[error("[FORG0009] Error in resolving a relative URI against a base URI: '{uri}'")]
205    FORG0009 { uri: String },
206
207    // ========================================================================
208    // Regex Errors (FORX)
209    // ========================================================================
210    /// FORX0001: Invalid regular expression flags.
211    #[error("[FORX0001] Invalid regular expression flags: '{flags}'")]
212    FORX0001 { flags: String },
213
214    /// FORX0002: Invalid regular expression.
215    #[error("[FORX0002] Invalid regular expression: '{pattern}'")]
216    FORX0002 { pattern: String },
217
218    /// FORX0003: Regular expression matches zero-length string.
219    #[error("[FORX0003] Regular expression matches zero-length string: '{pattern}'")]
220    FORX0003 { pattern: String },
221
222    /// FORX0004: Invalid replacement string.
223    #[error("[FORX0004] Invalid replacement string: '{replacement}'")]
224    FORX0004 { replacement: String },
225
226    // ========================================================================
227    // Operator Errors
228    // ========================================================================
229    /// Binary operator not defined for argument types.
230    #[error("Operator '{operator}' is not defined for arguments of type '{left_type}' and '{right_type}'")]
231    BinaryOperatorNotDefined {
232        operator: String,
233        left_type: String,
234        right_type: String,
235    },
236
237    /// Unary operator not defined for argument type.
238    #[error("Operator '{operator}' is not defined for argument of type '{arg_type}'")]
239    UnaryOperatorNotDefined { operator: String, arg_type: String },
240
241    // ========================================================================
242    // Internal/General Errors
243    // ========================================================================
244    /// Internal error for unexpected failures.
245    #[error("XPath error: {0}")]
246    Internal(String),
247}
248
249impl From<crate::xpath::parser::ParseError> for XPathError {
250    fn from(e: crate::xpath::parser::ParseError) -> Self {
251        XPathError::XPST0003 {
252            message: e.to_string(),
253        }
254    }
255}
256
257impl From<crate::navigator::NavigatorError> for XPathError {
258    fn from(e: crate::navigator::NavigatorError) -> Self {
259        XPathError::Internal(e.to_string())
260    }
261}
262
263impl XPathError {
264    // ========================================================================
265    // Convenience Constructors
266    // ========================================================================
267
268    /// Create a new internal XPath error.
269    pub fn internal(message: impl Into<String>) -> Self {
270        XPathError::Internal(message.into())
271    }
272
273    /// Create XPST0003 syntax error.
274    pub fn syntax_error(message: impl Into<String>) -> Self {
275        XPathError::XPST0003 {
276            message: message.into(),
277        }
278    }
279
280    /// Create XPST0008 undefined QName error.
281    pub fn undefined_qname(qname: impl Into<String>) -> Self {
282        XPathError::XPST0008 {
283            qname: qname.into(),
284        }
285    }
286
287    /// Create XPST0051 unknown type error.
288    pub fn unknown_type(type_name: impl Into<String>) -> Self {
289        XPathError::XPST0051 {
290            type_name: type_name.into(),
291        }
292    }
293
294    /// Create XPST0017 function not found error.
295    pub fn function_not_found(
296        name: impl Into<String>,
297        arity: usize,
298        namespace: impl Into<String>,
299    ) -> Self {
300        XPathError::XPST0017 {
301            name: name.into(),
302            arity,
303            namespace: namespace.into(),
304        }
305    }
306
307    /// Create XPST0081 undefined prefix error.
308    pub fn undefined_prefix(prefix: impl Into<String>) -> Self {
309        XPathError::XPST0081 {
310            prefix: prefix.into(),
311        }
312    }
313
314    /// Create XPDY0002 context undefined error.
315    pub fn context_undefined() -> Self {
316        XPathError::XPDY0002 {
317            message: "The context item is undefined".to_string(),
318        }
319    }
320
321    /// Create XPDY0050 more than one item error.
322    pub fn more_than_one_item() -> Self {
323        XPathError::XPDY0050
324    }
325
326    /// Create XPTY0004 type mismatch error.
327    pub fn type_mismatch(expected: impl Into<String>, found: impl Into<String>) -> Self {
328        XPathError::XPTY0004 {
329            expected: expected.into(),
330            found: found.into(),
331        }
332    }
333
334    /// Create XPTY0004 cast-only-from-string error.
335    pub fn cast_requires_string_literal(target_type: impl Into<String>) -> Self {
336        XPathError::XPTY0004Cast {
337            target_type: target_type.into(),
338        }
339    }
340
341    /// Create XPTY0020 context item not a node error.
342    pub fn context_not_a_node() -> Self {
343        XPathError::XPTY0020
344    }
345
346    /// Create FORG0001 invalid cast value error.
347    pub fn invalid_cast_value(value: impl Into<String>, target_type: impl Into<String>) -> Self {
348        XPathError::FORG0001 {
349            value: value.into(),
350            target_type: target_type.into(),
351        }
352    }
353
354    /// Create FORG0006 invalid argument type error.
355    pub fn invalid_argument_type(function: impl Into<String>, arg_type: impl Into<String>) -> Self {
356        XPathError::FORG0006Named {
357            function: function.into(),
358            arg_type: arg_type.into(),
359        }
360    }
361
362    /// Create a not implemented error.
363    pub fn not_implemented(message: impl Into<String>) -> Self {
364        XPathError::Internal(format!("Not implemented: {}", message.into()))
365    }
366
367    /// Create XPST0017 wrong number of arguments error.
368    pub fn wrong_number_of_arguments(function: &str, expected: usize, actual: usize) -> Self {
369        XPathError::XPST0017 {
370            name: function.to_string(),
371            arity: actual,
372            namespace: format!(
373                "(expected {} argument{})",
374                expected,
375                if expected == 1 { "" } else { "s" }
376            ),
377        }
378    }
379
380    /// Create binary operator not defined error.
381    pub fn binary_operator_not_defined(
382        operator: impl Into<String>,
383        left_type: impl Into<String>,
384        right_type: impl Into<String>,
385    ) -> Self {
386        XPathError::BinaryOperatorNotDefined {
387            operator: operator.into(),
388            left_type: left_type.into(),
389            right_type: right_type.into(),
390        }
391    }
392
393    /// Create unary operator not defined error.
394    pub fn unary_operator_not_defined(
395        operator: impl Into<String>,
396        arg_type: impl Into<String>,
397    ) -> Self {
398        XPathError::UnaryOperatorNotDefined {
399            operator: operator.into(),
400            arg_type: arg_type.into(),
401        }
402    }
403
404    /// Create FOTY0012 no typed value error.
405    pub fn no_typed_value() -> Self {
406        XPathError::FOTY0012
407    }
408
409    /// Create FONS0005 base-uri not defined error.
410    pub fn base_uri_not_defined() -> Self {
411        XPathError::FONS0005
412    }
413
414    /// Create FORG0009 URI resolution error.
415    pub fn uri_resolution_error(uri: impl Into<String>) -> Self {
416        XPathError::FORG0009 { uri: uri.into() }
417    }
418
419    /// Create FORX0001 invalid regex flags error.
420    pub fn invalid_regex_flags(flags: impl Into<String>) -> Self {
421        XPathError::FORX0001 {
422            flags: flags.into(),
423        }
424    }
425
426    /// Create FORX0002 invalid regex pattern error.
427    pub fn invalid_regex_pattern(pattern: impl Into<String>) -> Self {
428        XPathError::FORX0002 {
429            pattern: pattern.into(),
430        }
431    }
432
433    /// Create FORX0003 regex matches zero-length string error.
434    pub fn regex_matches_zero_length(pattern: impl Into<String>) -> Self {
435        XPathError::FORX0003 {
436            pattern: pattern.into(),
437        }
438    }
439
440    /// Create FORX0004 invalid replacement string error.
441    pub fn invalid_replacement_string(replacement: impl Into<String>) -> Self {
442        XPathError::FORX0004 {
443            replacement: replacement.into(),
444        }
445    }
446
447    /// Create XQST0076 unsupported collation error.
448    pub fn unsupported_collation(collation: impl Into<String>) -> Self {
449        XPathError::XQST0076 {
450            collation: collation.into(),
451        }
452    }
453
454    /// Create FOCH0002 unsupported collation error.
455    pub fn unknown_collation(collation: impl Into<String>) -> Self {
456        XPathError::FOCH0002 {
457            collation: collation.into(),
458        }
459    }
460
461    /// Get the error code (e.g., "XPTY0004") if this is a spec-defined error.
462    pub fn error_code(&self) -> Option<&'static str> {
463        match self {
464            XPathError::XPST0003 { .. } => Some("XPST0003"),
465            XPathError::XPST0008 { .. } => Some("XPST0008"),
466            XPathError::XPST0051 { .. } => Some("XPST0051"),
467            XPathError::XPST0017 { .. } => Some("XPST0017"),
468            XPathError::XPST0081 { .. } => Some("XPST0081"),
469            XPathError::XPDY0002 { .. } => Some("XPDY0002"),
470            XPathError::XPDY0050 => Some("XPDY0050"),
471            XPathError::XPTY0004 { .. } => Some("XPTY0004"),
472            XPathError::XPTY0004Cast { .. } => Some("XPTY0004"),
473            XPathError::XPTY0018 => Some("XPTY0018"),
474            XPathError::XPTY0019 => Some("XPTY0019"),
475            XPathError::XPTY0020 => Some("XPTY0020"),
476            XPathError::XQTY0030 => Some("XQTY0030"),
477            XPathError::XQST0076 { .. } => Some("XQST0076"),
478            XPathError::FORG0001 { .. } => Some("FORG0001"),
479            XPathError::FORG0003 => Some("FORG0003"),
480            XPathError::FORG0004 => Some("FORG0004"),
481            XPathError::FORG0005 => Some("FORG0005"),
482            XPathError::FORG0006Named { .. } => Some("FORG0006"),
483            XPathError::FORG0006 { .. } => Some("FORG0006"),
484            XPathError::FORG0008 => Some("FORG0008"),
485            XPathError::FOCH0001 { .. } => Some("FOCH0001"),
486            XPathError::FOCH0002 { .. } => Some("FOCH0002"),
487            XPathError::FOCH0003 { .. } => Some("FOCH0003"),
488            XPathError::FOAR0001 => Some("FOAR0001"),
489            XPathError::FOAR0002 => Some("FOAR0002"),
490            XPathError::FOCA0002 { .. } => Some("FOCA0002"),
491            XPathError::FOCA0003 { .. } => Some("FOCA0003"),
492            XPathError::FOCA0005 => Some("FOCA0005"),
493            XPathError::FODT0001 => Some("FODT0001"),
494            XPathError::FODT0002 => Some("FODT0002"),
495            XPathError::FODT0003 { .. } => Some("FODT0003"),
496            XPathError::FOTY0012 => Some("FOTY0012"),
497            XPathError::FONS0005 => Some("FONS0005"),
498            XPathError::FORG0009 { .. } => Some("FORG0009"),
499            XPathError::FORX0001 { .. } => Some("FORX0001"),
500            XPathError::FORX0002 { .. } => Some("FORX0002"),
501            XPathError::FORX0003 { .. } => Some("FORX0003"),
502            XPathError::FORX0004 { .. } => Some("FORX0004"),
503            XPathError::BinaryOperatorNotDefined { .. } => None,
504            XPathError::UnaryOperatorNotDefined { .. } => None,
505            XPathError::Internal(_) => None,
506        }
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513
514    #[test]
515    fn test_error_codes() {
516        assert_eq!(
517            XPathError::context_undefined().error_code(),
518            Some("XPDY0002")
519        );
520        assert_eq!(
521            XPathError::more_than_one_item().error_code(),
522            Some("XPDY0050")
523        );
524        assert_eq!(
525            XPathError::type_mismatch("xs:integer", "xs:string").error_code(),
526            Some("XPTY0004")
527        );
528    }
529
530    #[test]
531    fn test_error_display() {
532        let err = XPathError::type_mismatch("xs:integer", "xs:string");
533        assert!(err.to_string().contains("XPTY0004"));
534        assert!(err.to_string().contains("xs:integer"));
535        assert!(err.to_string().contains("xs:string"));
536    }
537
538    #[test]
539    fn test_internal_error() {
540        let err = XPathError::internal("something went wrong");
541        assert!(err.to_string().contains("something went wrong"));
542        assert_eq!(err.error_code(), None);
543    }
544}