use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::Serialize;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Triple not found: {0}")]
NotFound(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Validation failed: {0}")]
ValidationError(String),
#[error("Authentication failed: {0}")]
AuthError(String),
#[error("Not authorized: {0}")]
Forbidden(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Query error: {0}")]
QueryError(String),
#[error("SPARQL parse error: {0}")]
SparqlParseError(String),
#[error("Unbound variable: {0}")]
UnboundVariable(String),
#[error("Unsupported expression")]
UnsupportedExpression,
#[error("Invalid regex: {0}")]
InvalidRegex(String),
#[error("Proof not found: {0}")]
ProofNotFound(String),
#[error("Proof verification failed: {0}")]
ProofVerificationFailed(String),
#[error("Graph error: {0}")]
GraphError(#[from] aingle_graph::Error),
#[error("Logic error: {0}")]
LogicError(#[from] aingle_logic::Error),
#[error("Internal error: {0}")]
Internal(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Operation timed out: {0}")]
Timeout(String),
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Redirect to {0}")]
Redirect(String),
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
}
impl Error {
pub fn status_code(&self) -> StatusCode {
match self {
Error::NotFound(_) => StatusCode::NOT_FOUND,
Error::InvalidInput(_) => StatusCode::BAD_REQUEST,
Error::ValidationError(_) => StatusCode::UNPROCESSABLE_ENTITY,
Error::AuthError(_) => StatusCode::UNAUTHORIZED,
Error::Forbidden(_) => StatusCode::FORBIDDEN,
Error::RateLimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
Error::QueryError(_) => StatusCode::BAD_REQUEST,
Error::SparqlParseError(_) => StatusCode::BAD_REQUEST,
Error::UnboundVariable(_) => StatusCode::BAD_REQUEST,
Error::UnsupportedExpression => StatusCode::BAD_REQUEST,
Error::InvalidRegex(_) => StatusCode::BAD_REQUEST,
Error::ProofNotFound(_) => StatusCode::NOT_FOUND,
Error::ProofVerificationFailed(_) => StatusCode::UNPROCESSABLE_ENTITY,
Error::GraphError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::LogicError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Serialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Timeout(_) => StatusCode::REQUEST_TIMEOUT,
Error::BadRequest(_) => StatusCode::BAD_REQUEST,
Error::Conflict(_) => StatusCode::CONFLICT,
Error::Redirect(_) => StatusCode::TEMPORARY_REDIRECT,
}
}
pub fn error_code(&self) -> &'static str {
match self {
Error::NotFound(_) => "NOT_FOUND",
Error::InvalidInput(_) => "INVALID_INPUT",
Error::ValidationError(_) => "VALIDATION_ERROR",
Error::AuthError(_) => "AUTH_ERROR",
Error::Forbidden(_) => "FORBIDDEN",
Error::RateLimitExceeded(_) => "RATE_LIMIT_EXCEEDED",
Error::QueryError(_) => "QUERY_ERROR",
Error::SparqlParseError(_) => "SPARQL_PARSE_ERROR",
Error::UnboundVariable(_) => "UNBOUND_VARIABLE",
Error::UnsupportedExpression => "UNSUPPORTED_EXPRESSION",
Error::InvalidRegex(_) => "INVALID_REGEX",
Error::ProofNotFound(_) => "PROOF_NOT_FOUND",
Error::ProofVerificationFailed(_) => "PROOF_VERIFICATION_FAILED",
Error::GraphError(_) => "GRAPH_ERROR",
Error::LogicError(_) => "LOGIC_ERROR",
Error::Internal(_) => "INTERNAL_ERROR",
Error::Io(_) => "IO_ERROR",
Error::Serialization(_) => "SERIALIZATION_ERROR",
Error::Timeout(_) => "TIMEOUT",
Error::BadRequest(_) => "BAD_REQUEST",
Error::Conflict(_) => "CONFLICT",
Error::Redirect(_) => "REDIRECT",
}
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
let status = self.status_code();
if let Error::Redirect(ref location) = self {
return (
status,
[(axum::http::header::LOCATION, location.as_str())],
"Redirecting to leader",
)
.into_response();
}
let body = ErrorResponse {
error: self.to_string(),
code: self.error_code().to_string(),
details: None,
};
(status, axum::Json(body)).into_response()
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Serialization(err.to_string())
}
}