Skip to main content

modo/db/
error.rs

1use crate::error::Error;
2
3/// Converts `libsql::Error` into [`modo::Error`](crate::Error) with
4/// appropriate HTTP status codes:
5///
6/// - Unique/primary-key constraint violations become `409 Conflict`.
7/// - Foreign-key violations become `400 Bad Request`.
8/// - `QueryReturnedNoRows` becomes `404 Not Found`.
9/// - Everything else becomes `500 Internal Server Error`.
10impl From<libsql::Error> for Error {
11    fn from(err: libsql::Error) -> Self {
12        match &err {
13            libsql::Error::SqliteFailure(code, msg) => {
14                // SQLite extended error codes
15                // SQLITE_CONSTRAINT_UNIQUE = 2067
16                // SQLITE_CONSTRAINT_FOREIGNKEY = 787
17                // SQLITE_CONSTRAINT_PRIMARYKEY = 1555
18                match *code {
19                    2067 | 1555 => Error::conflict("record already exists").chain(err),
20                    787 => Error::bad_request("foreign key violation").chain(err),
21                    _ => Error::internal(format!("database error: {msg}")).chain(err),
22                }
23            }
24            libsql::Error::QueryReturnedNoRows => Error::not_found("record not found"),
25            libsql::Error::NullValue => Error::bad_request("unexpected null value"),
26            libsql::Error::ConnectionFailed(msg) => {
27                Error::internal(format!("database connection failed: {msg}"))
28            }
29            libsql::Error::InvalidColumnIndex => Error::internal("invalid column index"),
30            libsql::Error::InvalidColumnType => Error::internal("invalid column type"),
31            _ => Error::internal("database error").chain(err),
32        }
33    }
34}