Files
turso/core/error.rs
Samyak S Sarnayak c09a0bcbf3 Nicer parse errors using miette
I noticed that the parse errors were a bit hard to read - only the nearest token and the line/col offsets were printed.

I made a first attempt at improving the errors using [miette](https://github.com/zkat/miette).
- Added derive for `miette::Diagnostic` to both the parser's error type and LimboError.
- Added miette dependency to both sqlite3_parser and core. The `fancy` feature is only enabled for CLI.

Some future improvements that can be made further:
- Add spans to AST nodes so that errors can better point to the correct token. See upstream issue: https://github.com/gwenn/lemon-rs/issues/33
- Construct more errors with offset information. I noticed that most parser errors are constructed with `None` as the offset.

Comparisons.
Before:
```
❯ cargo run --package limbo --bin limbo database.db --output-mode pretty
...
limbo> selet * from a;
[2025-01-05T11:22:55Z ERROR sqlite3Parser] near "Token([115, 101, 108, 101, 116])": syntax error
Parse error: near "selet": syntax error at (1, 6)
```

After:
```
❯ cargo run --package limbo --bin limbo database.db --output-mode pretty
...
limbo> selet * from a;
[2025-01-05T12:25:52Z ERROR sqlite3Parser] near "Token([115, 101, 108, 101, 116])": syntax error

  × near "selet": syntax error at (1, 6)
   ╭────
 1 │ selet * from a
   ·     ▲
   ·     ╰── syntax error
   ╰────

```
2025-01-05 17:56:59 +05:30

67 lines
1.9 KiB
Rust

use thiserror::Error;
#[derive(Debug, Error, miette::Diagnostic)]
pub enum LimboError {
#[error("Corrupt database: {0}")]
Corrupt(String),
#[error("File is not a database")]
NotADB,
#[error("Internal error: {0}")]
InternalError(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error(transparent)]
#[diagnostic(transparent)]
LexerError(#[from] sqlite3_parser::lexer::sql::Error),
#[error("Conversion error: {0}")]
ConversionError(String),
#[error("Env variable error: {0}")]
EnvVarError(#[from] std::env::VarError),
#[error("I/O error: {0}")]
IOError(#[from] std::io::Error),
#[cfg(target_os = "linux")]
#[error("I/O error: {0}")]
LinuxIOError(String),
#[error("Locking error: {0}")]
LockingError(String),
#[cfg(target_os = "macos")]
#[error("I/O error: {0}")]
RustixIOError(#[from] rustix::io::Errno),
#[error("Parse error: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("Parse error: {0}")]
ParseFloatError(#[from] std::num::ParseFloatError),
#[error("Parse error: {0}")]
InvalidDate(String),
#[error("Parse error: {0}")]
InvalidTime(String),
#[error("Modifier parsing error: {0}")]
InvalidModifier(String),
#[error("Runtime error: {0}")]
Constraint(String),
}
#[macro_export]
macro_rules! bail_parse_error {
($($arg:tt)*) => {
return Err($crate::error::LimboError::ParseError(format!($($arg)*)))
};
}
#[macro_export]
macro_rules! bail_corrupt_error {
($($arg:tt)*) => {
return Err($crate::error::LimboError::Corrupt(format!($($arg)*)))
};
}
#[macro_export]
macro_rules! bail_constraint_error {
($($arg:tt)*) => {
return Err($crate::error::LimboError::Constraint(format!($($arg)*)))
};
}
pub const SQLITE_CONSTRAINT: usize = 19;
pub const SQLITE_CONSTRAINT_PRIMARYKEY: usize = SQLITE_CONSTRAINT | (6 << 8);