Files
SCOPENET-MC/panel/server/src/error.rs
T
Claude 8cdf616a94 Add launcher engine (scopenet-core) and admin panel server
- scopenet-shared: wire types for branding, auth, instances, manifests
- scopenet-core: Mojang versions/libraries/assets, automatic Java runtimes,
  Fabric/Quilt/Forge/NeoForge installation, file sync, launch command
  builder, Microsoft auth, servers.dat injection, server list ping
- scopenet-panel: Axum + SQLite admin API with JWT auth, users/groups,
  branding, settings, instances, Modrinth/CurseForge/zip import, file
  hosting and launch stats

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
2026-09-28 04:22:17 +00:00

75 lines
2.1 KiB
Rust

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde_json::json;
/// Every handler returns `Result<_, AppError>`; errors become
/// `{"error": "..."}` with a sensible status code.
#[derive(Debug)]
pub struct AppError {
pub status: StatusCode,
pub message: String,
}
pub type AppResult<T> = Result<T, AppError>;
impl AppError {
pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
Self { status, message: message.into() }
}
pub fn bad_request(m: impl Into<String>) -> Self {
Self::new(StatusCode::BAD_REQUEST, m)
}
pub fn unauthorized(m: impl Into<String>) -> Self {
Self::new(StatusCode::UNAUTHORIZED, m)
}
pub fn forbidden(m: impl Into<String>) -> Self {
Self::new(StatusCode::FORBIDDEN, m)
}
pub fn not_found(m: impl Into<String>) -> Self {
Self::new(StatusCode::NOT_FOUND, m)
}
pub fn conflict(m: impl Into<String>) -> Self {
Self::new(StatusCode::CONFLICT, m)
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
if self.status.is_server_error() {
tracing::error!("{}", self.message);
}
(self.status, Json(json!({ "error": self.message }))).into_response()
}
}
impl From<anyhow::Error> for AppError {
fn from(e: anyhow::Error) -> Self {
Self::new(StatusCode::BAD_GATEWAY, format!("{e:#}"))
}
}
impl From<sqlx::Error> for AppError {
fn from(e: sqlx::Error) -> Self {
Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("database error: {e}"))
}
}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("io error: {e}"))
}
}
impl From<serde_json::Error> for AppError {
fn from(e: serde_json::Error) -> Self {
Self::bad_request(format!("invalid JSON: {e}"))
}
}
impl From<axum::extract::multipart::MultipartError> for AppError {
fn from(e: axum::extract::multipart::MultipartError) -> Self {
Self::bad_request(format!("upload failed: {e}"))
}
}