1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum AstQueryError {
8 #[error("Parse error: {0}")]
10 ParseError(String),
11
12 #[error(
14 "Unknown predicate: '{predicate}'. Available: kind, name~=, parent, in, depth, path, lang"
15 )]
16 UnknownPredicate {
17 predicate: String,
19 },
20
21 #[error("Invalid regex pattern: '{pattern}'")]
23 InvalidRegex {
24 pattern: String,
26 #[source]
28 source: regex::Error,
29 },
30
31 #[error("Invalid depth value: '{value}' (must be a positive number)")]
33 InvalidDepth {
34 value: String,
36 },
37
38 #[error("Unexpected token: expected {expected}, got {actual}")]
40 UnexpectedToken {
41 expected: String,
43 actual: String,
45 },
46
47 #[error("Context extraction failed: {0}")]
49 ContextExtraction(String),
50
51 #[error(transparent)]
53 Io(#[from] std::io::Error),
54}
55
56pub type Result<T> = std::result::Result<T, AstQueryError>;
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::error::Error;
63
64 #[test]
65 fn test_error_display_messages() {
66 let cases = vec![
67 (
68 AstQueryError::UnknownPredicate {
69 predicate: "foo".to_string(),
70 },
71 "Unknown predicate: 'foo'",
72 ),
73 (
74 AstQueryError::InvalidDepth {
75 value: "abc".to_string(),
76 },
77 "Invalid depth value: 'abc'",
78 ),
79 (
80 AstQueryError::ParseError("test".to_string()),
81 "Parse error: test",
82 ),
83 ];
84
85 for (error, expected) in cases {
86 assert!(
87 error.to_string().contains(expected),
88 "Error message '{error}' should contain '{expected}'"
89 );
90 }
91 }
92
93 #[test]
94 #[allow(clippy::invalid_regex)]
95 fn test_error_source_chaining() {
96 let regex_err = regex::Regex::new("[invalid").unwrap_err();
97 let ast_err = AstQueryError::InvalidRegex {
98 pattern: "[invalid".to_string(),
99 source: regex_err,
100 };
101
102 assert!(ast_err.source().is_some(), "Error should have source");
103 }
104}