use thiserror::Error;
#[derive(Debug, Error)]
pub enum AstQueryError {
#[error("Parse error: {0}")]
ParseError(String),
#[error(
"Unknown predicate: '{predicate}'. Available: kind, name~=, parent, in, depth, path, lang"
)]
UnknownPredicate {
predicate: String,
},
#[error("Invalid regex pattern: '{pattern}'")]
InvalidRegex {
pattern: String,
#[source]
source: regex::Error,
},
#[error("Invalid depth value: '{value}' (must be a positive number)")]
InvalidDepth {
value: String,
},
#[error("Unexpected token: expected {expected}, got {actual}")]
UnexpectedToken {
expected: String,
actual: String,
},
#[error("Context extraction failed: {0}")]
ContextExtraction(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, AstQueryError>;
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_error_display_messages() {
let cases = vec![
(
AstQueryError::UnknownPredicate {
predicate: "foo".to_string(),
},
"Unknown predicate: 'foo'",
),
(
AstQueryError::InvalidDepth {
value: "abc".to_string(),
},
"Invalid depth value: 'abc'",
),
(
AstQueryError::ParseError("test".to_string()),
"Parse error: test",
),
];
for (error, expected) in cases {
assert!(
error.to_string().contains(expected),
"Error message '{error}' should contain '{expected}'"
);
}
}
#[test]
#[allow(clippy::invalid_regex)]
fn test_error_source_chaining() {
let regex_err = regex::Regex::new("[invalid").unwrap_err();
let ast_err = AstQueryError::InvalidRegex {
pattern: "[invalid".to_string(),
source: regex_err,
};
assert!(ast_err.source().is_some(), "Error should have source");
}
}