- 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>
126 lines
4.0 KiB
Rust
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()
|
|
}
|
|
}
|