vault-link/sync-server/src/errors.rs
Andras Schmelczer a9ce09b59d split: server foundation (Cargo, config, errors, utils, main)
Cargo.{toml,lock} bumps, build.rs, config-e2e.yml, rust-toolchain.toml,
src/config/* (database/logging/server/user configs), src/consts.rs,
src/errors.rs, src/main.rs, and src/utils/* (dedup_paths,
find_first_available_path, rotating_file_writer, sanitize_path).
2026-05-08 21:35:18 +01:00

180 lines
5.5 KiB
Rust

use std::fmt::Display;
use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use log::{debug, error, warn};
use serde::Serialize;
use thiserror::Error;
use ts_rs::TS;
#[derive(Error, Debug)]
pub enum SyncServerError {
#[error("Initialisation error: {0}")]
InitError(#[source] anyhow::Error),
#[error("Client error: {0:?}")]
ClientError(#[source] anyhow::Error),
#[error("Server error: {0:?}")]
ServerError(#[source] anyhow::Error),
#[error("Not found: {0}")]
NotFound(#[source] anyhow::Error),
#[error("Unauthorized: {0}")]
Unauthenticated(#[source] anyhow::Error),
#[error("Permission denied error: {0}")]
PermissionDeniedError(#[source] anyhow::Error),
#[error("Too many requests: {0}")]
TooManyRequests(#[source] anyhow::Error),
}
impl SyncServerError {
pub fn serialize(&self) -> SerializedError {
match self {
Self::InitError(error)
| Self::ClientError(error)
| Self::ServerError(error)
| Self::NotFound(error)
| Self::Unauthenticated(error)
| Self::PermissionDeniedError(error)
| Self::TooManyRequests(error) => error.into(),
}
}
}
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct SerializedError {
pub error_type: &'static str,
pub message: String,
pub causes: Vec<String>,
}
impl Display for SerializedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.error_type, self.message)?;
if !self.causes.is_empty() {
write!(f, "\nCauses:\n")?;
for cause in &self.causes {
write!(f, "{}", &format!("- {cause}\n"))?;
}
}
Ok(())
}
}
impl IntoResponse for SyncServerError {
fn into_response(self) -> Response {
let serialized = self.serialize();
match &self {
Self::InitError(_) | Self::ServerError(_) => {
error!("{serialized}");
}
Self::ClientError(_) | Self::NotFound(_) => {
warn!("{serialized}");
}
Self::TooManyRequests(_) => {
warn!("{serialized}");
}
Self::Unauthenticated(_) | Self::PermissionDeniedError(_) => {}
}
let body = Json(serialized);
match self {
Self::InitError(_) | Self::ServerError(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
}
Self::ClientError(_) => (StatusCode::BAD_REQUEST, body).into_response(),
Self::NotFound(_) => (StatusCode::NOT_FOUND, body).into_response(),
Self::Unauthenticated(_) => (StatusCode::UNAUTHORIZED, body).into_response(),
Self::PermissionDeniedError(_) => (StatusCode::FORBIDDEN, body).into_response(),
Self::TooManyRequests(_) => (StatusCode::TOO_MANY_REQUESTS, body).into_response(),
}
}
}
impl From<&anyhow::Error> for SerializedError {
fn from(error: &anyhow::Error) -> SerializedError {
let mut causes = vec![];
let mut current_error = error.source();
while let Some(error) = current_error {
causes.push(error.to_string());
current_error = error.source();
}
SerializedError {
error_type: error.downcast_ref::<SyncServerError>().map_or(
"UnknownError",
|e| match e {
SyncServerError::InitError(_) => "InitError",
SyncServerError::ClientError(_) => "ClientError",
SyncServerError::ServerError(_) => "ServerError",
SyncServerError::NotFound(_) => "NotFound",
SyncServerError::Unauthenticated(_) => "Unauthenticated",
SyncServerError::PermissionDeniedError(_) => "PermissionDeniedError",
SyncServerError::TooManyRequests(_) => "TooManyRequests",
},
),
message: error.to_string(),
causes,
}
}
}
pub fn init_error(error: anyhow::Error) -> SyncServerError {
debug!("Initialization error: {error:?}");
SyncServerError::InitError(error)
}
pub fn server_error(error: anyhow::Error) -> SyncServerError {
debug!("Server error: {error:?}");
SyncServerError::ServerError(error)
}
pub fn client_error(error: anyhow::Error) -> SyncServerError {
debug!("Client error: {error:?}");
SyncServerError::ClientError(error)
}
pub fn not_found_error(error: anyhow::Error) -> SyncServerError {
debug!("Not found: {error:?}");
SyncServerError::NotFound(error)
}
pub fn unauthenticated_error(error: anyhow::Error) -> SyncServerError {
debug!("Unauthenticated user: {error:?}");
SyncServerError::Unauthenticated(error)
}
pub fn permission_denied_error(error: anyhow::Error) -> SyncServerError {
debug!("Permission denied: {error:?}");
SyncServerError::PermissionDeniedError(error)
}
pub fn too_many_requests_error(error: anyhow::Error) -> SyncServerError {
debug!("Too many requests: {error:?}");
SyncServerError::TooManyRequests(error)
}
/// Maps a `create_write_transaction` error to 429 if the database is busy,
/// or 500 for all other failures.
pub fn write_transaction_error(error: anyhow::Error) -> SyncServerError {
if error
.downcast_ref::<crate::app_state::database::WriteBusyError>()
.is_some()
{
too_many_requests_error(error)
} else {
server_error(error)
}
}