Skip to main content

alopex_sql/
error.rs

1use std::fmt;
2
3/// Parser errors for the Alopex SQL dialect.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum ParserError {
6    /// ALOPEX-P001: An unexpected token was encountered.
7    UnexpectedToken {
8        line: u64,
9        column: u64,
10        expected: String,
11        found: String,
12    },
13
14    /// ALOPEX-P002: A required token was missing.
15    ExpectedToken {
16        line: u64,
17        column: u64,
18        expected: String,
19        found: String,
20    },
21
22    /// ALOPEX-P003: String literal was not closed.
23    UnterminatedString { line: u64, column: u64 },
24
25    /// ALOPEX-P004: Number literal is malformed.
26    InvalidNumber {
27        line: u64,
28        column: u64,
29        value: String,
30    },
31
32    /// ALOPEX-P005: Vector literal is malformed.
33    InvalidVector { line: u64, column: u64 },
34
35    /// ALOPEX-P006: Parser exceeded maximum recursion depth.
36    RecursionLimitExceeded { depth: usize },
37
38    /// ALOPEX-P007: The Nim FFI parser hit an internal invariant violation
39    /// (a Nim `Defect`, e.g. `IndexDefect`/`FieldDefect`) rather than a
40    /// normal syntax error. This indicates a parser bug, not bad input, and
41    /// must stay distinguishable from `UnexpectedToken` so operators do not
42    /// mistake it for user error. See `nim-sql-parser/src/alopex_sql_parser.nim`
43    /// `alopex_parse_sql`'s `except ... Defect` branch.
44    InternalParserDefect { message: String },
45}
46
47impl fmt::Display for ParserError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::UnexpectedToken {
51                line,
52                column,
53                expected,
54                found,
55            } => write!(
56                f,
57                "error[ALOPEX-P001]: unexpected token at line {line}, column {column}: expected {expected}, found {found}"
58            ),
59            Self::ExpectedToken {
60                line,
61                column,
62                expected,
63                found,
64            } => write!(
65                f,
66                "error[ALOPEX-P002]: expected {expected} but found {found} at line {line}, column {column}"
67            ),
68            Self::UnterminatedString { line, column } => write!(
69                f,
70                "error[ALOPEX-P003]: unterminated string literal starting at line {line}, column {column}"
71            ),
72            Self::InvalidNumber {
73                line,
74                column,
75                value,
76            } => write!(
77                f,
78                "error[ALOPEX-P004]: invalid number literal '{value}' at line {line}, column {column}"
79            ),
80            Self::InvalidVector { line, column } => write!(
81                f,
82                "error[ALOPEX-P005]: invalid vector literal at line {line}, column {column}"
83            ),
84            Self::RecursionLimitExceeded { depth } => write!(
85                f,
86                "error[ALOPEX-P006]: recursion limit exceeded (depth: {depth})"
87            ),
88            Self::InternalParserDefect { message } => write!(
89                f,
90                "error[ALOPEX-P007]: internal parser defect (this is a parser bug, not invalid SQL): {message}"
91            ),
92        }
93    }
94}
95
96impl std::error::Error for ParserError {}
97
98/// Convenience result type for parser operations.
99pub type Result<T> = std::result::Result<T, ParserError>;
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn internal_parser_defect_is_labeled_with_its_own_error_code() {
107        // ALOPEX-P007 は ALOPEX-P001 (UnexpectedToken) と機械的に区別できな
108        // ければならない。運用者がパーサーのバグ (Nim FFI 境界の Defect) と
109        // ユーザーの SQL 誤りを混同しないための契約。
110        let defect = ParserError::InternalParserDefect {
111            message: "field 'children' is not accessible".to_string(),
112        };
113        let rendered = defect.to_string();
114        assert!(rendered.contains("ALOPEX-P007"));
115        assert!(rendered.contains("field 'children' is not accessible"));
116
117        let token = ParserError::UnexpectedToken {
118            line: 1,
119            column: 2,
120            expected: "expr".to_string(),
121            found: "EOF".to_string(),
122        };
123        assert!(token.to_string().contains("ALOPEX-P001"));
124        assert_ne!(rendered, token.to_string());
125    }
126}