use rusqlite::{Error as SqliteError, ErrorCode};
use serde_json::{Value, json};
use std::fmt;
pub type Result<T> = std::result::Result<T, AppError>;
#[derive(Debug)]
pub struct AppError {
pub code: &'static str,
pub message: String,
pub details: Option<Value>,
}
impl AppError {
pub fn new(code: &'static str, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
details: None,
}
}
pub fn with_details(mut self, details: Value) -> Self {
self.details = Some(details);
self
}
}
impl fmt::Display for AppError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for AppError {}
impl From<std::io::Error> for AppError {
fn from(error: std::io::Error) -> Self {
Self::new("io_error", error.to_string())
}
}
impl From<rusqlite::Error> for AppError {
fn from(error: rusqlite::Error) -> Self {
if matches!(
&error,
SqliteError::SqliteFailure(inner, _)
if matches!(inner.code, ErrorCode::DatabaseBusy | ErrorCode::DatabaseLocked)
) {
return Self::new(
"database_busy",
"another short Wiki write is active; retry this exact operation",
)
.with_details(json!({
"retryable": true,
"retry_after_ms": 100,
"work_command": "lwc work list",
}));
}
Self::new("database_error", error.to_string())
}
}