use std::path::PathBuf; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use crate::db::Db; #[derive(Clone)] pub struct AppState { pub db: Db, /// Canonicalized server root folder. pub root: PathBuf, /// Whether we sit behind a TLS-terminating reverse proxy. pub https: bool, } /// API error. `extra` is optionally merged into the JSON body (e.g. a list of /// conflicting file names on a 409). #[derive(Debug, thiserror::Error)] #[error("{1}")] pub struct ApiError(pub StatusCode, pub String, pub Option); impl ApiError { pub fn new(status: StatusCode, msg: impl Into) -> Self { Self(status, msg.into(), None) } pub fn with_extra(mut self, extra: serde_json::Value) -> Self { self.2 = Some(extra); self } } impl IntoResponse for ApiError { fn into_response(self) -> Response { let mut body = serde_json::json!({ "error": self.1 }); if let Some(extra) = self.2 { if let (Some(obj), Some(extra)) = (body.as_object_mut(), extra.as_object()) { for (k, v) in extra { obj.insert(k.clone(), v.clone()); } } } (self.0, axum::Json(body)).into_response() } } impl From for ApiError { fn from(e: std::io::Error) -> Self { tracing::error!(error = %e, "io error"); ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, "internal error") } } impl From for ApiError { fn from(e: rusqlite::Error) -> Self { tracing::error!(error = %e, "database error"); ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, "internal error") } }