1use super::traits::SourcePosition;
7use crate::error::Error;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[derive(Error, Debug, Clone)]
13pub enum ParserError {
14 #[error("Syntax error at {position}: {message}")]
16 SyntaxError {
17 message: String,
18 position: SourcePosition,
19 expected: Option<Vec<String>>,
20 },
21
22 #[error("Semantic error: {message}")]
24 SemanticError {
25 message: String,
26 position: Option<SourcePosition>,
27 },
28
29 #[error("Lexical error at {position}: {message}")]
31 LexicalError {
32 message: String,
33 position: SourcePosition,
34 },
35
36 #[error("Parser backend error ({backend}): {message}")]
38 BackendError {
39 backend: String,
40 message: String,
41 position: Option<SourcePosition>,
42 },
43
44 #[error("Type error: {message}")]
46 TypeError {
47 message: String,
48 expected_type: Option<String>,
49 actual_type: Option<String>,
50 position: Option<SourcePosition>,
51 },
52
53 #[error("Configuration error: {message}")]
55 ConfigurationError { message: String },
56
57 #[error("Unsupported feature '{feature}' for backend '{backend}': {message}")]
59 UnsupportedFeature {
60 backend: String,
61 feature: String,
62 message: String,
63 },
64
65 #[error("Parsing timeout: {message}")]
67 Timeout {
68 message: String,
69 timeout_duration: std::time::Duration,
70 },
71
72 #[error("Resource limit exceeded: {message}")]
74 ResourceLimit {
75 message: String,
76 limit_type: String,
77 current_value: u64,
78 max_value: u64,
79 },
80
81 #[error("Internal parser error: {message}")]
83 InternalError {
84 message: String,
85 cause: Option<String>,
86 },
87}
88
89impl ParserError {
90 pub fn syntax(message: impl Into<String>, position: SourcePosition) -> Self {
92 Self::SyntaxError {
93 message: message.into(),
94 position,
95 expected: None,
96 }
97 }
98
99 pub fn syntax_with_expected(
101 message: impl Into<String>,
102 position: SourcePosition,
103 expected: Vec<String>,
104 ) -> Self {
105 Self::SyntaxError {
106 message: message.into(),
107 position,
108 expected: Some(expected),
109 }
110 }
111
112 pub fn semantic(message: impl Into<String>) -> Self {
114 Self::SemanticError {
115 message: message.into(),
116 position: None,
117 }
118 }
119
120 pub fn semantic_at(message: impl Into<String>, position: SourcePosition) -> Self {
122 Self::SemanticError {
123 message: message.into(),
124 position: Some(position),
125 }
126 }
127
128 pub fn lexical(message: impl Into<String>, position: SourcePosition) -> Self {
130 Self::LexicalError {
131 message: message.into(),
132 position,
133 }
134 }
135
136 pub fn backend(backend: impl Into<String>, message: impl Into<String>) -> Self {
138 Self::BackendError {
139 backend: backend.into(),
140 message: message.into(),
141 position: None,
142 }
143 }
144
145 pub fn backend_at(
147 backend: impl Into<String>,
148 message: impl Into<String>,
149 position: SourcePosition,
150 ) -> Self {
151 Self::BackendError {
152 backend: backend.into(),
153 message: message.into(),
154 position: Some(position),
155 }
156 }
157
158 pub fn type_error(message: impl Into<String>) -> Self {
160 Self::TypeError {
161 message: message.into(),
162 expected_type: None,
163 actual_type: None,
164 position: None,
165 }
166 }
167
168 pub fn type_mismatch(
170 expected: impl Into<String>,
171 actual: impl Into<String>,
172 position: Option<SourcePosition>,
173 ) -> Self {
174 let expected_str = expected.into();
175 let actual_str = actual.into();
176 Self::TypeError {
177 message: format!("Expected {}, found {}", expected_str, actual_str),
178 expected_type: Some(expected_str),
179 actual_type: Some(actual_str),
180 position,
181 }
182 }
183
184 pub fn configuration(message: impl Into<String>) -> Self {
186 Self::ConfigurationError {
187 message: message.into(),
188 }
189 }
190
191 pub fn unsupported_feature(backend: impl Into<String>, feature: impl Into<String>) -> Self {
193 let backend = backend.into();
194 let feature = feature.into();
195 let message = format!(
196 "Feature '{}' is not supported by backend '{}'",
197 feature, backend
198 );
199 Self::UnsupportedFeature {
200 backend,
201 feature,
202 message,
203 }
204 }
205
206 pub fn internal(message: impl Into<String>) -> Self {
208 Self::InternalError {
209 message: message.into(),
210 cause: None,
211 }
212 }
213
214 pub fn internal_with_cause(message: impl Into<String>, cause: impl std::fmt::Display) -> Self {
216 Self::InternalError {
217 message: message.into(),
218 cause: Some(cause.to_string()),
219 }
220 }
221
222 pub fn timeout(duration_ms: u64) -> Self {
224 Self::Timeout {
225 message: format!("Parser timeout after {}ms", duration_ms),
226 timeout_duration: std::time::Duration::from_millis(duration_ms),
227 }
228 }
229
230 pub fn resource_limit(resource: impl Into<String>, limit: u64, actual: u64) -> Self {
232 let limit_type = resource.into();
233 let message = format!("Resource '{}' limit exceeded", limit_type);
234 Self::ResourceLimit {
235 message,
236 limit_type,
237 current_value: actual,
238 max_value: limit,
239 }
240 }
241
242 pub fn position(&self) -> Option<&SourcePosition> {
244 match self {
245 Self::SyntaxError { position, .. } => Some(position),
246 Self::SemanticError { position, .. } => position.as_ref(),
247 Self::LexicalError { position, .. } => Some(position),
248 Self::BackendError { position, .. } => position.as_ref(),
249 Self::TypeError { position, .. } => position.as_ref(),
250 _ => None,
251 }
252 }
253
254 pub fn message(&self) -> String {
256 match self {
257 Self::SyntaxError { message, .. } => message.clone(),
258 Self::SemanticError { message, .. } => message.clone(),
259 Self::LexicalError { message, .. } => message.clone(),
260 Self::BackendError { message, .. } => message.clone(),
261 Self::TypeError { message, .. } => message.clone(),
262 Self::ConfigurationError { message } => message.clone(),
263 Self::UnsupportedFeature { message, .. } => message.clone(),
264 Self::InternalError { message, .. } => message.clone(),
265 Self::Timeout { message, .. } => message.clone(),
266 Self::ResourceLimit { message, .. } => message.clone(),
267 }
268 }
269
270 pub fn is_recoverable(&self) -> bool {
275 matches!(
276 self,
277 Self::BackendError { .. }
278 | Self::ConfigurationError { .. }
279 | Self::UnsupportedFeature { .. }
280 | Self::Timeout { .. }
281 | Self::ResourceLimit { .. }
282 )
283 }
284
285 pub fn category(&self) -> &ErrorCategory {
287 match self {
288 Self::SyntaxError { .. } | Self::LexicalError { .. } => &ErrorCategory::Syntax,
289 Self::SemanticError { .. } => &ErrorCategory::Semantic,
290 Self::TypeError { .. } => &ErrorCategory::Type,
291 Self::ConfigurationError { .. } => &ErrorCategory::Configuration,
292 Self::BackendError { .. } | Self::UnsupportedFeature { .. } => &ErrorCategory::Backend,
293 Self::InternalError { .. } | Self::Timeout { .. } | Self::ResourceLimit { .. } => {
294 &ErrorCategory::Internal
295 }
296 }
297 }
298
299 pub fn severity(&self) -> &ErrorSeverity {
301 match self {
302 Self::SyntaxError { .. }
303 | Self::SemanticError { .. }
304 | Self::LexicalError { .. }
305 | Self::TypeError { .. } => &ErrorSeverity::Error,
306 Self::ConfigurationError { .. } | Self::UnsupportedFeature { .. } => {
307 &ErrorSeverity::Warning
308 }
309 Self::BackendError { .. } | Self::Timeout { .. } | Self::ResourceLimit { .. } => {
310 &ErrorSeverity::Error
311 }
312 Self::InternalError { .. } => &ErrorSeverity::Fatal,
313 }
314 }
315
316 pub fn recovery_suggestions(&self) -> Vec<String> {
318 match self {
319 Self::BackendError { backend, .. } => {
320 vec![format!(
321 "Try switching from '{}' parser backend to another",
322 backend
323 )]
324 }
325 Self::UnsupportedFeature {
326 backend, feature, ..
327 } => {
328 vec![
329 format!(
330 "Switch from '{}' backend to one that supports '{}'",
331 backend, feature
332 ),
333 format!("Remove or modify the '{}' feature usage", feature),
334 ]
335 }
336 Self::Timeout {
337 timeout_duration, ..
338 } => {
339 vec![
340 format!(
341 "Increase parser timeout (current: {}ms)",
342 timeout_duration.as_millis()
343 ),
344 "Simplify the query to reduce parsing complexity".to_string(),
345 ]
346 }
347 Self::ResourceLimit {
348 limit_type,
349 max_value,
350 ..
351 } => {
352 vec![
353 format!("Increase '{}' limit (current: {})", limit_type, max_value),
354 format!("Reduce usage of '{}' in the query", limit_type),
355 ]
356 }
357 Self::ConfigurationError { .. } => {
358 vec!["Check parser configuration settings".to_string()]
359 }
360 _ => vec![],
361 }
362 }
363}
364
365impl From<ParserError> for Error {
366 fn from(err: ParserError) -> Self {
367 match err {
368 ParserError::SyntaxError { message, .. }
369 | ParserError::SemanticError { message, .. }
370 | ParserError::LexicalError { message, .. } => Error::cql_parse(message),
371 ParserError::BackendError { message, .. }
372 | ParserError::InternalError { message, .. } => Error::internal(message),
373 ParserError::TypeError { message, .. } => Error::type_conversion(message),
374 ParserError::ConfigurationError { message } => Error::configuration(message),
375 ParserError::UnsupportedFeature {
376 backend, feature, ..
377 } => Error::invalid_operation(format!(
378 "Feature '{}' not supported by backend '{}'",
379 feature, backend
380 )),
381 ParserError::Timeout {
382 timeout_duration, ..
383 } => Error::internal(format!(
384 "Parser timeout after {}ms",
385 timeout_duration.as_millis()
386 )),
387 ParserError::ResourceLimit {
388 limit_type,
389 current_value,
390 max_value,
391 ..
392 } => Error::internal(format!(
393 "Resource '{}' limit exceeded: {} > {}",
394 limit_type, current_value, max_value
395 )),
396 }
397 }
398}
399
400#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub enum ErrorSeverity {
403 Info,
405 Warning,
407 Error,
409 Fatal,
411}
412
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub enum ErrorCategory {
416 Syntax,
418 Semantic,
420 Type,
422 Configuration,
424 Backend,
426 Internal,
428}
429
430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
432pub struct ParserWarning {
433 pub message: String,
435 pub position: Option<SourcePosition>,
437 pub category: ErrorCategory,
439}
440
441impl ParserWarning {
442 pub fn new(message: String, category: ErrorCategory) -> Self {
444 Self {
445 message,
446 position: None,
447 category,
448 }
449 }
450
451 pub fn with_position(
453 message: String,
454 category: ErrorCategory,
455 position: SourcePosition,
456 ) -> Self {
457 Self {
458 message,
459 position: Some(position),
460 category,
461 }
462 }
463}
464
465pub type ParserResult<T> = std::result::Result<T, ParserError>;
467
468#[derive(Debug, Clone)]
470pub struct ErrorContext {
471 pub input: String,
473 pub backend: String,
475 pub config: Option<String>,
477 pub stack_trace: Option<Vec<String>>,
479}
480
481impl ErrorContext {
482 pub fn new(input: String, backend: String) -> Self {
484 Self {
485 input,
486 backend,
487 config: None,
488 stack_trace: None,
489 }
490 }
491
492 pub fn with_config(mut self, config: String) -> Self {
494 self.config = Some(config);
495 self
496 }
497
498 pub fn with_stack_trace(mut self, stack_trace: Vec<String>) -> Self {
500 self.stack_trace = Some(stack_trace);
501 self
502 }
503
504 pub fn get_error_snippet(&self, position: &SourcePosition, context_lines: usize) -> String {
506 let lines: Vec<&str> = self.input.lines().collect();
507 let error_line = position.line as usize;
508
509 if error_line == 0 || error_line > lines.len() {
510 return self.input.clone();
511 }
512
513 let start_line = error_line.saturating_sub(context_lines + 1);
514 let end_line = std::cmp::min(error_line + context_lines, lines.len());
515
516 let mut snippet = String::new();
517
518 for (i, line) in lines[start_line..end_line].iter().enumerate() {
519 let line_num = start_line + i + 1;
520 let marker = if line_num == error_line {
521 ">>> "
522 } else {
523 " "
524 };
525 snippet.push_str(&format!("{}{:4}: {}\n", marker, line_num, line));
526
527 if line_num == error_line {
528 let col = position.column as usize;
529 if col > 0 && col <= line.len() {
530 snippet.push_str(&format!("{} {}\n", marker, " ".repeat(col - 1) + "^"));
531 }
532 }
533 }
534
535 snippet
536 }
537}
538
539pub mod utils {
541 use super::*;
542
543 pub fn from_nom_error<I>(error: nom::Err<nom::error::Error<I>>, _input: &str) -> ParserError
545 where
546 I: std::fmt::Debug,
547 {
548 match error {
549 nom::Err::Error(e) | nom::Err::Failure(e) => {
550 ParserError::backend("nom", format!("Parse error: {:?}", e))
551 }
552 nom::Err::Incomplete(_) => ParserError::backend("nom", "Incomplete input"),
553 }
554 }
555
556 #[cfg(feature = "pest")]
558 pub fn from_pest_error(error: Box<dyn std::error::Error>) -> ParserError {
559 ParserError::backend("pest", format!("Parse error: {}", error))
560 }
561
562 pub fn create_contextual_error(error: ParserError, context: &ErrorContext) -> String {
564 let mut message = format!("Parser Error: {}\n", error.message());
565
566 if let Some(position) = error.position() {
567 message.push_str(&format!(
568 "Location: line {}, column {}\n",
569 position.line, position.column
570 ));
571
572 let snippet = context.get_error_snippet(position, 2);
573 if !snippet.is_empty() {
574 message.push_str("Context:\n");
575 message.push_str(&snippet);
576 }
577 }
578
579 message.push_str(&format!("Backend: {}\n", context.backend));
580
581 if let Some(config) = &context.config {
582 message.push_str(&format!("Configuration: {}\n", config));
583 }
584
585 let suggestions = error.recovery_suggestions();
586 if !suggestions.is_empty() {
587 message.push_str("Suggestions:\n");
588 for suggestion in suggestions {
589 message.push_str(&format!(" - {}\n", suggestion));
590 }
591 }
592
593 message
594 }
595
596 pub fn chain_errors(mut errors: Vec<ParserError>) -> ParserError {
598 match errors.len() {
599 0 => ParserError::internal("No errors to chain"),
600 1 => errors.remove(0),
601 _ => {
602 let messages: Vec<String> = errors.iter().map(|e| e.message()).collect();
603 ParserError::internal(format!("Multiple errors: {}", messages.join("; ")))
604 }
605 }
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612
613 #[test]
614 fn test_parser_error_creation() {
615 let pos = SourcePosition::new(10, 5, 100, 20);
616
617 let syntax_err = ParserError::syntax("Expected ';'", pos.clone());
618 assert!(matches!(syntax_err, ParserError::SyntaxError { .. }));
619 assert_eq!(syntax_err.position(), Some(&pos));
620
621 let semantic_err = ParserError::semantic("Table does not exist");
622 assert!(matches!(semantic_err, ParserError::SemanticError { .. }));
623 assert_eq!(semantic_err.position(), None);
624
625 let backend_err = ParserError::backend("nom", "Parse failed");
626 assert!(matches!(backend_err, ParserError::BackendError { .. }));
627 assert!(backend_err.is_recoverable());
628 }
629
630 #[test]
631 fn test_error_recovery_suggestions() {
632 let timeout_err = ParserError::timeout(5000);
633 let suggestions = timeout_err.recovery_suggestions();
634 assert!(!suggestions.is_empty());
635 assert!(suggestions[0].contains("timeout"));
636
637 let feature_err = ParserError::unsupported_feature("nom", "streaming");
638 let suggestions = feature_err.recovery_suggestions();
639 assert!(!suggestions.is_empty());
640 assert!(suggestions[0].contains("backend"));
641 }
642
643 #[test]
644 fn test_error_context() {
645 let input = "SELECT * FROM users\nWHERE id = ?".to_string();
646 let context = ErrorContext::new(input, "nom".to_string());
647
648 let pos = SourcePosition::new(2, 10, 25, 1);
649 let snippet = context.get_error_snippet(&pos, 1);
650
651 assert!(snippet.contains("WHERE"));
652 assert!(snippet.contains(">>>"));
653 assert!(snippet.contains("^"));
654 }
655
656 #[test]
657 fn test_error_conversion() {
658 let parser_err = ParserError::syntax("Expected token", SourcePosition::start());
659 let core_err: Error = parser_err.into();
660
661 assert!(matches!(core_err, Error::CqlParse(_)));
662 }
663}