use super::traits::SourcePosition;
use crate::error::Error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug, Clone)]
pub enum ParserError {
#[error("Syntax error at {position}: {message}")]
SyntaxError {
message: String,
position: SourcePosition,
expected: Option<Vec<String>>,
},
#[error("Semantic error: {message}")]
SemanticError {
message: String,
position: Option<SourcePosition>,
},
#[error("Lexical error at {position}: {message}")]
LexicalError {
message: String,
position: SourcePosition,
},
#[error("Parser backend error ({backend}): {message}")]
BackendError {
backend: String,
message: String,
position: Option<SourcePosition>,
},
#[error("Type error: {message}")]
TypeError {
message: String,
expected_type: Option<String>,
actual_type: Option<String>,
position: Option<SourcePosition>,
},
#[error("Configuration error: {message}")]
ConfigurationError { message: String },
#[error("Unsupported feature '{feature}' for backend '{backend}': {message}")]
UnsupportedFeature {
backend: String,
feature: String,
message: String,
},
#[error("Parsing timeout: {message}")]
Timeout {
message: String,
timeout_duration: std::time::Duration,
},
#[error("Resource limit exceeded: {message}")]
ResourceLimit {
message: String,
limit_type: String,
current_value: u64,
max_value: u64,
},
#[error("Internal parser error: {message}")]
InternalError {
message: String,
cause: Option<String>,
},
}
impl ParserError {
pub fn syntax(message: impl Into<String>, position: SourcePosition) -> Self {
Self::SyntaxError {
message: message.into(),
position,
expected: None,
}
}
pub fn syntax_with_expected(
message: impl Into<String>,
position: SourcePosition,
expected: Vec<String>,
) -> Self {
Self::SyntaxError {
message: message.into(),
position,
expected: Some(expected),
}
}
pub fn semantic(message: impl Into<String>) -> Self {
Self::SemanticError {
message: message.into(),
position: None,
}
}
pub fn semantic_at(message: impl Into<String>, position: SourcePosition) -> Self {
Self::SemanticError {
message: message.into(),
position: Some(position),
}
}
pub fn lexical(message: impl Into<String>, position: SourcePosition) -> Self {
Self::LexicalError {
message: message.into(),
position,
}
}
pub fn backend(backend: impl Into<String>, message: impl Into<String>) -> Self {
Self::BackendError {
backend: backend.into(),
message: message.into(),
position: None,
}
}
pub fn backend_at(
backend: impl Into<String>,
message: impl Into<String>,
position: SourcePosition,
) -> Self {
Self::BackendError {
backend: backend.into(),
message: message.into(),
position: Some(position),
}
}
pub fn type_error(message: impl Into<String>) -> Self {
Self::TypeError {
message: message.into(),
expected_type: None,
actual_type: None,
position: None,
}
}
pub fn type_mismatch(
expected: impl Into<String>,
actual: impl Into<String>,
position: Option<SourcePosition>,
) -> Self {
let expected_str = expected.into();
let actual_str = actual.into();
Self::TypeError {
message: format!("Expected {}, found {}", expected_str, actual_str),
expected_type: Some(expected_str),
actual_type: Some(actual_str),
position,
}
}
pub fn configuration(message: impl Into<String>) -> Self {
Self::ConfigurationError {
message: message.into(),
}
}
pub fn unsupported_feature(backend: impl Into<String>, feature: impl Into<String>) -> Self {
let backend = backend.into();
let feature = feature.into();
let message = format!(
"Feature '{}' is not supported by backend '{}'",
feature, backend
);
Self::UnsupportedFeature {
backend,
feature,
message,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self::InternalError {
message: message.into(),
cause: None,
}
}
pub fn internal_with_cause(message: impl Into<String>, cause: impl std::fmt::Display) -> Self {
Self::InternalError {
message: message.into(),
cause: Some(cause.to_string()),
}
}
pub fn timeout(duration_ms: u64) -> Self {
Self::Timeout {
message: format!("Parser timeout after {}ms", duration_ms),
timeout_duration: std::time::Duration::from_millis(duration_ms),
}
}
pub fn resource_limit(resource: impl Into<String>, limit: u64, actual: u64) -> Self {
let limit_type = resource.into();
let message = format!("Resource '{}' limit exceeded", limit_type);
Self::ResourceLimit {
message,
limit_type,
current_value: actual,
max_value: limit,
}
}
pub fn position(&self) -> Option<&SourcePosition> {
match self {
Self::SyntaxError { position, .. } => Some(position),
Self::SemanticError { position, .. } => position.as_ref(),
Self::LexicalError { position, .. } => Some(position),
Self::BackendError { position, .. } => position.as_ref(),
Self::TypeError { position, .. } => position.as_ref(),
_ => None,
}
}
pub fn message(&self) -> String {
match self {
Self::SyntaxError { message, .. } => message.clone(),
Self::SemanticError { message, .. } => message.clone(),
Self::LexicalError { message, .. } => message.clone(),
Self::BackendError { message, .. } => message.clone(),
Self::TypeError { message, .. } => message.clone(),
Self::ConfigurationError { message } => message.clone(),
Self::UnsupportedFeature { message, .. } => message.clone(),
Self::InternalError { message, .. } => message.clone(),
Self::Timeout { message, .. } => message.clone(),
Self::ResourceLimit { message, .. } => message.clone(),
}
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::BackendError { .. }
| Self::ConfigurationError { .. }
| Self::UnsupportedFeature { .. }
| Self::Timeout { .. }
| Self::ResourceLimit { .. }
)
}
pub fn category(&self) -> &ErrorCategory {
match self {
Self::SyntaxError { .. } | Self::LexicalError { .. } => &ErrorCategory::Syntax,
Self::SemanticError { .. } => &ErrorCategory::Semantic,
Self::TypeError { .. } => &ErrorCategory::Type,
Self::ConfigurationError { .. } => &ErrorCategory::Configuration,
Self::BackendError { .. } | Self::UnsupportedFeature { .. } => &ErrorCategory::Backend,
Self::InternalError { .. } | Self::Timeout { .. } | Self::ResourceLimit { .. } => {
&ErrorCategory::Internal
}
}
}
pub fn severity(&self) -> &ErrorSeverity {
match self {
Self::SyntaxError { .. }
| Self::SemanticError { .. }
| Self::LexicalError { .. }
| Self::TypeError { .. } => &ErrorSeverity::Error,
Self::ConfigurationError { .. } | Self::UnsupportedFeature { .. } => {
&ErrorSeverity::Warning
}
Self::BackendError { .. } | Self::Timeout { .. } | Self::ResourceLimit { .. } => {
&ErrorSeverity::Error
}
Self::InternalError { .. } => &ErrorSeverity::Fatal,
}
}
pub fn recovery_suggestions(&self) -> Vec<String> {
match self {
Self::BackendError { backend, .. } => {
vec![format!(
"Try switching from '{}' parser backend to another",
backend
)]
}
Self::UnsupportedFeature {
backend, feature, ..
} => {
vec![
format!(
"Switch from '{}' backend to one that supports '{}'",
backend, feature
),
format!("Remove or modify the '{}' feature usage", feature),
]
}
Self::Timeout {
timeout_duration, ..
} => {
vec![
format!(
"Increase parser timeout (current: {}ms)",
timeout_duration.as_millis()
),
"Simplify the query to reduce parsing complexity".to_string(),
]
}
Self::ResourceLimit {
limit_type,
max_value,
..
} => {
vec![
format!("Increase '{}' limit (current: {})", limit_type, max_value),
format!("Reduce usage of '{}' in the query", limit_type),
]
}
Self::ConfigurationError { .. } => {
vec!["Check parser configuration settings".to_string()]
}
_ => vec![],
}
}
}
impl From<ParserError> for Error {
fn from(err: ParserError) -> Self {
match err {
ParserError::SyntaxError { message, .. }
| ParserError::SemanticError { message, .. }
| ParserError::LexicalError { message, .. } => Error::cql_parse(message),
ParserError::BackendError { message, .. }
| ParserError::InternalError { message, .. } => Error::internal(message),
ParserError::TypeError { message, .. } => Error::type_conversion(message),
ParserError::ConfigurationError { message } => Error::configuration(message),
ParserError::UnsupportedFeature {
backend, feature, ..
} => Error::invalid_operation(format!(
"Feature '{}' not supported by backend '{}'",
feature, backend
)),
ParserError::Timeout {
timeout_duration, ..
} => Error::internal(format!(
"Parser timeout after {}ms",
timeout_duration.as_millis()
)),
ParserError::ResourceLimit {
limit_type,
current_value,
max_value,
..
} => Error::internal(format!(
"Resource '{}' limit exceeded: {} > {}",
limit_type, current_value, max_value
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ErrorSeverity {
Info,
Warning,
Error,
Fatal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ErrorCategory {
Syntax,
Semantic,
Type,
Configuration,
Backend,
Internal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParserWarning {
pub message: String,
pub position: Option<SourcePosition>,
pub category: ErrorCategory,
}
impl ParserWarning {
pub fn new(message: String, category: ErrorCategory) -> Self {
Self {
message,
position: None,
category,
}
}
pub fn with_position(
message: String,
category: ErrorCategory,
position: SourcePosition,
) -> Self {
Self {
message,
position: Some(position),
category,
}
}
}
pub type ParserResult<T> = std::result::Result<T, ParserError>;
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub input: String,
pub backend: String,
pub config: Option<String>,
pub stack_trace: Option<Vec<String>>,
}
impl ErrorContext {
pub fn new(input: String, backend: String) -> Self {
Self {
input,
backend,
config: None,
stack_trace: None,
}
}
pub fn with_config(mut self, config: String) -> Self {
self.config = Some(config);
self
}
pub fn with_stack_trace(mut self, stack_trace: Vec<String>) -> Self {
self.stack_trace = Some(stack_trace);
self
}
pub fn get_error_snippet(&self, position: &SourcePosition, context_lines: usize) -> String {
let lines: Vec<&str> = self.input.lines().collect();
let error_line = position.line as usize;
if error_line == 0 || error_line > lines.len() {
return self.input.clone();
}
let start_line = error_line.saturating_sub(context_lines + 1);
let end_line = std::cmp::min(error_line + context_lines, lines.len());
let mut snippet = String::new();
for (i, line) in lines[start_line..end_line].iter().enumerate() {
let line_num = start_line + i + 1;
let marker = if line_num == error_line {
">>> "
} else {
" "
};
snippet.push_str(&format!("{}{:4}: {}\n", marker, line_num, line));
if line_num == error_line {
let col = position.column as usize;
if col > 0 && col <= line.len() {
snippet.push_str(&format!("{} {}\n", marker, " ".repeat(col - 1) + "^"));
}
}
}
snippet
}
}
pub mod utils {
use super::*;
pub fn from_nom_error<I>(error: nom::Err<nom::error::Error<I>>, _input: &str) -> ParserError
where
I: std::fmt::Debug,
{
match error {
nom::Err::Error(e) | nom::Err::Failure(e) => {
ParserError::backend("nom", format!("Parse error: {:?}", e))
}
nom::Err::Incomplete(_) => ParserError::backend("nom", "Incomplete input"),
}
}
#[cfg(feature = "pest")]
pub fn from_pest_error(error: Box<dyn std::error::Error>) -> ParserError {
ParserError::backend("pest", format!("Parse error: {}", error))
}
pub fn create_contextual_error(error: ParserError, context: &ErrorContext) -> String {
let mut message = format!("Parser Error: {}\n", error.message());
if let Some(position) = error.position() {
message.push_str(&format!(
"Location: line {}, column {}\n",
position.line, position.column
));
let snippet = context.get_error_snippet(position, 2);
if !snippet.is_empty() {
message.push_str("Context:\n");
message.push_str(&snippet);
}
}
message.push_str(&format!("Backend: {}\n", context.backend));
if let Some(config) = &context.config {
message.push_str(&format!("Configuration: {}\n", config));
}
let suggestions = error.recovery_suggestions();
if !suggestions.is_empty() {
message.push_str("Suggestions:\n");
for suggestion in suggestions {
message.push_str(&format!(" - {}\n", suggestion));
}
}
message
}
pub fn chain_errors(mut errors: Vec<ParserError>) -> ParserError {
match errors.len() {
0 => ParserError::internal("No errors to chain"),
1 => errors.remove(0),
_ => {
let messages: Vec<String> = errors.iter().map(|e| e.message()).collect();
ParserError::internal(format!("Multiple errors: {}", messages.join("; ")))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_error_creation() {
let pos = SourcePosition::new(10, 5, 100, 20);
let syntax_err = ParserError::syntax("Expected ';'", pos.clone());
assert!(matches!(syntax_err, ParserError::SyntaxError { .. }));
assert_eq!(syntax_err.position(), Some(&pos));
let semantic_err = ParserError::semantic("Table does not exist");
assert!(matches!(semantic_err, ParserError::SemanticError { .. }));
assert_eq!(semantic_err.position(), None);
let backend_err = ParserError::backend("nom", "Parse failed");
assert!(matches!(backend_err, ParserError::BackendError { .. }));
assert!(backend_err.is_recoverable());
}
#[test]
fn test_error_recovery_suggestions() {
let timeout_err = ParserError::timeout(5000);
let suggestions = timeout_err.recovery_suggestions();
assert!(!suggestions.is_empty());
assert!(suggestions[0].contains("timeout"));
let feature_err = ParserError::unsupported_feature("nom", "streaming");
let suggestions = feature_err.recovery_suggestions();
assert!(!suggestions.is_empty());
assert!(suggestions[0].contains("backend"));
}
#[test]
fn test_error_context() {
let input = "SELECT * FROM users\nWHERE id = ?".to_string();
let context = ErrorContext::new(input, "nom".to_string());
let pos = SourcePosition::new(2, 10, 25, 1);
let snippet = context.get_error_snippet(&pos, 1);
assert!(snippet.contains("WHERE"));
assert!(snippet.contains(">>>"));
assert!(snippet.contains("^"));
}
#[test]
fn test_error_conversion() {
let parser_err = ParserError::syntax("Expected token", SourcePosition::start());
let core_err: Error = parser_err.into();
assert!(matches!(core_err, Error::CqlParse(_)));
}
}