Skip to main content

cqlite_core/cql/
error.rs

1//! Parser-specific error types and utilities
2//!
3//! This module defines error types that are specific to the parser subsystem,
4//! providing detailed information about parsing failures with context.
5
6use super::traits::SourcePosition;
7use crate::error::Error;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Parser-specific error type
12#[derive(Error, Debug, Clone)]
13pub enum ParserError {
14    /// Syntax error during parsing
15    #[error("Syntax error at {position}: {message}")]
16    SyntaxError {
17        message: String,
18        position: SourcePosition,
19        expected: Option<Vec<String>>,
20    },
21
22    /// Semantic validation error
23    #[error("Semantic error: {message}")]
24    SemanticError {
25        message: String,
26        position: Option<SourcePosition>,
27    },
28
29    /// Lexical analysis error
30    #[error("Lexical error at {position}: {message}")]
31    LexicalError {
32        message: String,
33        position: SourcePosition,
34    },
35
36    /// Parser backend error
37    #[error("Parser backend error ({backend}): {message}")]
38    BackendError {
39        backend: String,
40        message: String,
41        position: Option<SourcePosition>,
42    },
43
44    /// Type validation error
45    #[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    /// Configuration error
54    #[error("Configuration error: {message}")]
55    ConfigurationError { message: String },
56
57    /// Unsupported feature error
58    #[error("Unsupported feature '{feature}' for backend '{backend}': {message}")]
59    UnsupportedFeature {
60        backend: String,
61        feature: String,
62        message: String,
63    },
64
65    /// Timeout error
66    #[error("Parsing timeout: {message}")]
67    Timeout {
68        message: String,
69        timeout_duration: std::time::Duration,
70    },
71
72    /// Resource limit exceeded
73    #[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    /// Internal parser error
82    #[error("Internal parser error: {message}")]
83    InternalError {
84        message: String,
85        cause: Option<String>,
86    },
87}
88
89impl ParserError {
90    /// Create a syntax error
91    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    /// Create a syntax error with expected tokens
100    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    /// Create a semantic error
113    pub fn semantic(message: impl Into<String>) -> Self {
114        Self::SemanticError {
115            message: message.into(),
116            position: None,
117        }
118    }
119
120    /// Create a semantic error with position
121    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    /// Create a lexical error
129    pub fn lexical(message: impl Into<String>, position: SourcePosition) -> Self {
130        Self::LexicalError {
131            message: message.into(),
132            position,
133        }
134    }
135
136    /// Create a backend error
137    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    /// Create a backend error with position
146    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    /// Create a type error
159    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    /// Create a type error with expected and actual types
169    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    /// Create a configuration error
185    pub fn configuration(message: impl Into<String>) -> Self {
186        Self::ConfigurationError {
187            message: message.into(),
188        }
189    }
190
191    /// Create an unsupported feature error
192    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    /// Create an internal error
207    pub fn internal(message: impl Into<String>) -> Self {
208        Self::InternalError {
209            message: message.into(),
210            cause: None,
211        }
212    }
213
214    /// Create an internal error with cause
215    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    /// Create a timeout error
223    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    /// Create a resource limit exceeded error
231    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    /// Get the position associated with this error (if any)
243    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    /// Get the error message
255    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    /// Check if this error is recoverable.
271    ///
272    /// Recoverable errors include those where switching backends, retrying with
273    /// a longer timeout, or raising a resource limit may allow progress.
274    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    /// Get the error category
286    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    /// Get the error severity
300    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    /// Get suggested recovery actions
317    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/// Error severity levels
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub enum ErrorSeverity {
403    /// Information level
404    Info,
405    /// Warning level
406    Warning,
407    /// Error level
408    Error,
409    /// Fatal error level
410    Fatal,
411}
412
413/// Error categories
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub enum ErrorCategory {
416    /// Syntax errors
417    Syntax,
418    /// Semantic errors
419    Semantic,
420    /// Type errors
421    Type,
422    /// Configuration errors
423    Configuration,
424    /// Backend errors
425    Backend,
426    /// Internal errors
427    Internal,
428}
429
430/// Parser warning type
431#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
432pub struct ParserWarning {
433    /// Warning message
434    pub message: String,
435    /// Position in source (if available)
436    pub position: Option<SourcePosition>,
437    /// Warning category
438    pub category: ErrorCategory,
439}
440
441impl ParserWarning {
442    /// Create a new warning
443    pub fn new(message: String, category: ErrorCategory) -> Self {
444        Self {
445            message,
446            position: None,
447            category,
448        }
449    }
450
451    /// Create a warning with position
452    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
465/// Specialized result type for parser operations
466pub type ParserResult<T> = std::result::Result<T, ParserError>;
467
468/// Error context for providing additional information about parsing failures
469#[derive(Debug, Clone)]
470pub struct ErrorContext {
471    /// Input text that was being parsed
472    pub input: String,
473    /// Current parser backend
474    pub backend: String,
475    /// Parser configuration at time of error
476    pub config: Option<String>,
477    /// Stack trace or call stack if available
478    pub stack_trace: Option<Vec<String>>,
479}
480
481impl ErrorContext {
482    /// Create a new error context
483    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    /// Add configuration information
493    pub fn with_config(mut self, config: String) -> Self {
494        self.config = Some(config);
495        self
496    }
497
498    /// Add stack trace information
499    pub fn with_stack_trace(mut self, stack_trace: Vec<String>) -> Self {
500        self.stack_trace = Some(stack_trace);
501        self
502    }
503
504    /// Get a snippet of the input around the error position
505    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
539/// Utility functions for error handling
540pub mod utils {
541    use super::*;
542
543    /// Convert nom parsing errors to ParserError
544    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    /// Convert pest parsing errors to ParserError
557    #[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    /// Create a helpful error message with context
563    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    /// Chain multiple parser errors into a single error
597    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}