- 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
75 lines
2.1 KiB
Rust
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}"))
|
|
}
|
|
}
|