Initial public release v0.1.0

This commit is contained in:
thakares committed 2026-06-21 20:05:29 +05:30
commit 6c39e0bfbf
87 files changed
+10867

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use serde_json::json;
use thiserror::Error;
/// Central application error type.
/// All handlers return `Result<T, AppError>`, which Axum maps to HTTP responses.
#[derive(Debug, Error)]
pub enum AppError {
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
#[error("resource not found")]
NotFound,
#[error("invalid credentials")]
Unauthorized,
#[error("insufficient permissions")]
Forbidden,
#[error("conflict: {0}")]
Conflict(String),
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("too many requests")]
RateLimited,
#[error("internal error")]
Internal,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, code) = match &self {
AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
AppError::NotFound => (StatusCode::NOT_FOUND, "not_found"),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized"),
AppError::Forbidden => (StatusCode::FORBIDDEN, "forbidden"),
AppError::Conflict(_) => (StatusCode::CONFLICT, "conflict"),
AppError::InvalidInput(_) => (StatusCode::UNPROCESSABLE_ENTITY, "invalid_input"),
AppError::RateLimited => (StatusCode::TOO_MANY_REQUESTS, "rate_limited"),
AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
};
// Log server-side errors for visibility
match &self {
AppError::Database(e) => {
tracing::error!(error = %e, "database error");
}
AppError::Internal => {
tracing::error!("internal error");
}
_ => {}
}
let body = json!({
"error": self.to_string(),
"code": code,
});
(status, Json(body)).into_response()
}
}
/// Convenience alias used throughout the codebase.
pub type Result<T> = std::result::Result<T, AppError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_status_mapping() {
let err_not_found = AppError::NotFound;
let resp = err_not_found.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let err_unauthorized = AppError::Unauthorized;
let resp = err_unauthorized.into_response();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
let err_forbidden = AppError::Forbidden;
let resp = err_forbidden.into_response();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
let err_conflict = AppError::Conflict("already exists".into());
let resp = err_conflict.into_response();
assert_eq!(resp.status(), StatusCode::CONFLICT);
let err_invalid = AppError::InvalidInput("bad value".into());
let resp = err_invalid.into_response();
assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
let err_rate = AppError::RateLimited;
let resp = err_rate.into_response();
assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);
}
}