Files
nx9-wg/crates/nx9-wg-api/src/error.rs
T
thakaresandCopilot 2ac6c81dfe cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output
- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization.
- Create parent directories when an explicit --database path is provided.
- Redact printed generated administrator passwords; announce file path or redact instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-16 16:26:24 +05:30

126 lines
4.0 KiB
Rust

//! API error types and response structures.
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Unified API result type.
pub type ApiResult<T> = std::result::Result<T, ApiError>;
/// API-level errors.
#[derive(Debug, Error)]
pub enum ApiError {
#[error("unauthenticated: {0}")]
Unauthenticated(String),
#[error("forbidden: {0}")]
Forbidden(String),
#[error("not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("bad request: {0}")]
BadRequest(String),
#[error("validation error: {0}")]
Validation(String),
#[error("rate limited: {0}")]
RateLimited(String),
#[error("internal server error: {0}")]
Internal(String),
#[error("subsystem unavailable: {0}")]
Unavailable(String),
}
impl From<nx9_wg_db::DbError> for ApiError {
fn from(err: nx9_wg_db::DbError) -> Self {
match err {
nx9_wg_db::DbError::NotFound(msg) => Self::NotFound(msg),
nx9_wg_db::DbError::Conflict(msg) => Self::Conflict(msg),
nx9_wg_db::DbError::ConstraintViolation(msg) => Self::BadRequest(msg),
nx9_wg_db::DbError::Validation(msg) => Self::Validation(msg),
nx9_wg_db::DbError::Sqlx(e) => {
tracing::error!("database error: {e}");
Self::Internal("A database error occurred".to_string())
}
nx9_wg_db::DbError::Migration(msg) => Self::Internal(format!("Migration error: {msg}")),
nx9_wg_db::DbError::Internal(msg) => Self::Internal(msg),
}
}
}
impl From<nx9_wg_core::error::Nx9Error> for ApiError {
fn from(err: nx9_wg_core::error::Nx9Error) -> Self {
match err {
nx9_wg_core::error::Nx9Error::Validation(msg) => Self::Validation(msg),
nx9_wg_core::error::Nx9Error::Auth(msg) => Self::Unauthenticated(msg),
nx9_wg_core::error::Nx9Error::Crypto(msg) => Self::Internal(msg),
nx9_wg_core::error::Nx9Error::Config(msg) => Self::BadRequest(msg),
_ => Self::Internal(err.to_string()),
}
}
}
/// Standard JSON error envelope.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
pub error: ErrorBody,
}
/// Error details in the error response.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorBody {
pub code: String,
pub message: String,
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, code, message) = match &self {
Self::Unauthenticated(msg) => {
(StatusCode::UNAUTHORIZED, "UNAUTHENTICATED", msg.clone())
}
Self::Forbidden(msg) => (StatusCode::FORBIDDEN, "FORBIDDEN", msg.clone()),
Self::NotFound(msg) => (StatusCode::NOT_FOUND, "NOT_FOUND", msg.clone()),
Self::Conflict(msg) => (StatusCode::CONFLICT, "CONFLICT", msg.clone()),
Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, "BAD_REQUEST", msg.clone()),
Self::Validation(msg) => (
StatusCode::UNPROCESSABLE_ENTITY,
"VALIDATION_ERROR",
msg.clone(),
),
Self::RateLimited(msg) => (StatusCode::TOO_MANY_REQUESTS, "RATE_LIMITED", msg.clone()),
Self::Internal(msg) => {
tracing::error!("Internal server error: {msg}");
(
StatusCode::INTERNAL_SERVER_ERROR,
"INTERNAL_ERROR",
"An unexpected error occurred".to_string(),
)
}
Self::Unavailable(msg) => (
StatusCode::SERVICE_UNAVAILABLE,
"SUBSYSTEM_UNAVAILABLE",
msg.clone(),
),
};
let body = Json(ErrorResponse {
error: ErrorBody {
code: code.to_string(),
message,
},
});
(status, body).into_response()
}
}