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>
This commit is contained in:
commit
2ac6c81dfe
140 files changed
+31342
No files matched your search
@@ -0,0 +1,125 @@
|
||||
//! 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()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user