use axum::Json; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; #[derive(Debug, Clone, PartialEq)] pub struct XrpcError { pub status: StatusCode, pub error: String, pub message: String, } impl XrpcError { pub fn new(status: StatusCode, error: impl Into, message: impl Into) -> Self { XrpcError { status, error: error.into(), message: message.into(), } } pub fn bad_request(error: impl Into, message: impl Into) -> Self { Self::new(StatusCode::BAD_REQUEST, error, message) } pub fn invalid_request(message: impl Into) -> Self { Self::bad_request("InvalidRequest", message) } pub fn auth_required(message: impl Into) -> Self { Self::new(StatusCode::UNAUTHORIZED, "AuthRequired", message) } pub fn invalid_token(message: impl Into) -> Self { Self::new(StatusCode::UNAUTHORIZED, "InvalidToken", message) } pub fn forbidden(error: impl Into, message: impl Into) -> Self { Self::new(StatusCode::FORBIDDEN, error, message) } pub fn not_found(error: impl Into, message: impl Into) -> Self { Self::new(StatusCode::NOT_FOUND, error, message) } pub fn upstream(message: impl Into) -> Self { Self::new(StatusCode::BAD_GATEWAY, "UpstreamFailure", message) } pub fn internal(message: impl Into) -> Self { Self::new( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", message, ) } } impl std::fmt::Display for XrpcError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{} {}: {}", self.status.as_u16(), self.error, self.message ) } } impl std::error::Error for XrpcError {} impl From for XrpcError { fn from(error: anyhow::Error) -> Self { match error.downcast::() { Ok(xrpc) => xrpc, Err(error) => { tracing::warn!("internal error: {error:#}"); Self::internal(format!("{error:#}")) } } } } impl IntoResponse for XrpcError { fn into_response(self) -> Response { ( self.status, Json(serde_json::json!({ "error": self.error, "message": self.message })), ) .into_response() } }