Axum server entry point, AppConfig, AppState, ApiError, all model structs (auth, license, server, wipe), and the PanelAdapter trait that abstracts AMP/Pterodactyl/companion connections. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
112 lines
3.2 KiB
Rust
112 lines
3.2 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use serde::Serialize;
|
|
|
|
/// Unified API error type
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ApiError {
|
|
#[error("Not found: {0}")]
|
|
NotFound(String),
|
|
|
|
#[error("Bad request: {0}")]
|
|
BadRequest(String),
|
|
|
|
#[error("Unauthorized")]
|
|
Unauthorized,
|
|
|
|
#[error("Forbidden")]
|
|
Forbidden,
|
|
|
|
#[error("Conflict: {0}")]
|
|
Conflict(String),
|
|
|
|
#[error("License invalid or expired")]
|
|
LicenseInvalid,
|
|
|
|
#[error("Module not enabled: {0}")]
|
|
ModuleNotEnabled(String),
|
|
|
|
#[error("Rate limited")]
|
|
RateLimited,
|
|
|
|
#[error("Internal error: {0}")]
|
|
Internal(String),
|
|
|
|
#[error(transparent)]
|
|
Database(#[from] sqlx::Error),
|
|
|
|
#[error(transparent)]
|
|
Anyhow(#[from] anyhow::Error),
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct ErrorResponse {
|
|
error: String,
|
|
message: String,
|
|
}
|
|
|
|
impl IntoResponse for ApiError {
|
|
fn into_response(self) -> Response {
|
|
let (status, error_type, message) = match &self {
|
|
ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, "not_found", msg.clone()),
|
|
ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, "bad_request", msg.clone()),
|
|
ApiError::Unauthorized => {
|
|
(StatusCode::UNAUTHORIZED, "unauthorized", "Unauthorized".to_string())
|
|
}
|
|
ApiError::Forbidden => {
|
|
(StatusCode::FORBIDDEN, "forbidden", "Forbidden".to_string())
|
|
}
|
|
ApiError::Conflict(msg) => (StatusCode::CONFLICT, "conflict", msg.clone()),
|
|
ApiError::LicenseInvalid => (
|
|
StatusCode::PAYMENT_REQUIRED,
|
|
"license_invalid",
|
|
"License invalid or expired".to_string(),
|
|
),
|
|
ApiError::ModuleNotEnabled(module) => (
|
|
StatusCode::PAYMENT_REQUIRED,
|
|
"module_not_enabled",
|
|
format!("Module not enabled: {module}"),
|
|
),
|
|
ApiError::RateLimited => (
|
|
StatusCode::TOO_MANY_REQUESTS,
|
|
"rate_limited",
|
|
"Too many requests".to_string(),
|
|
),
|
|
ApiError::Internal(msg) => {
|
|
tracing::error!("Internal error: {msg}");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal_error",
|
|
"Internal server error".to_string(),
|
|
)
|
|
}
|
|
ApiError::Database(e) => {
|
|
tracing::error!("Database error: {e}");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal_error",
|
|
"Internal server error".to_string(),
|
|
)
|
|
}
|
|
ApiError::Anyhow(e) => {
|
|
tracing::error!("Unexpected error: {e}");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal_error",
|
|
"Internal server error".to_string(),
|
|
)
|
|
}
|
|
};
|
|
|
|
let body = ErrorResponse {
|
|
error: error_type.to_string(),
|
|
message,
|
|
};
|
|
|
|
(status, axum::Json(body)).into_response()
|
|
}
|
|
}
|
|
|
|
/// Result alias for API handlers
|
|
pub type ApiResult<T> = Result<T, ApiError>;
|