Skip to main content

rest_sql/
error.rs

1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum RestSqlError {
6    #[error("parse error: {0}")]
7    ParseError(ParseError),
8    #[error("validation error: {0:?}")]
9    ValidationError(Vec<ValidationError>),
10}
11
12/// A structured parse error with source location and visual context.
13///
14/// Designed to be shown directly to end users (e.g. REST API consumers).
15/// Example output:
16/// ```text
17/// parse error at 1:5 — expected an operator (==, !=, =in=, ...)
18///   name=bad=value
19///       ^
20/// ```
21#[derive(Debug, Clone, PartialEq)]
22pub struct ParseErrorAt {
23    /// 1-based line number.
24    pub line: usize,
25    /// 1-based column number (byte-based; accurate for ASCII input).
26    pub col: usize,
27    /// Number of characters to underline with '^'. At least 1.
28    pub span_len: usize,
29    /// The full source line containing the error.
30    pub snippet: String,
31    /// Human-readable description of what went wrong.
32    pub message: String,
33}
34
35impl fmt::Display for ParseErrorAt {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        let caret = " ".repeat(self.col - 1) + &"^".repeat(self.span_len.max(1));
38        write!(
39            f,
40            "parse error at {}:{} — {}\n  {}\n  {}",
41            self.line, self.col, self.message, self.snippet, caret
42        )
43    }
44}
45
46#[derive(Debug, Error)]
47pub enum ParseError {
48    /// Plain message error, used by winnow and peg parsers.
49    #[error("parse error: {0}")]
50    Message(String),
51
52    /// Structured error with source location. Produced by the canonical parser.
53    #[error("{0}")]
54    At(ParseErrorAt),
55}
56
57#[derive(Debug, Error)]
58pub enum ValidationError {
59    #[error("field '{0}' is not allowed")]
60    ForbiddenField(String),
61    #[error("operator {operator:?} requires a list argument on field '{field}'")]
62    ExpectedList { field: String, operator: String },
63    #[error("operator {operator:?} requires exactly 2 values on field '{field}'")]
64    BetweenArity { field: String, operator: String },
65    #[error("operator {operator:?} does not accept a list on field '{field}'")]
66    UnexpectedList { field: String, operator: String },
67}