error.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 use anyhow::{anyhow, Error as AnyhowError}; 20 use axum::{ 21 extract::rejection::JsonRejection, 22 http::{header::CONTENT_TYPE, StatusCode}, 23 response::{IntoResponse, Response}, 24 }; 25 use serde::{Deserialize, Serialize}; 26 27 /// An enum of error handlers for the REST API server. 28 #[derive(Debug)] 29 pub enum RestError { 30 /// 400 Bad Request - Invalid input, malformed parameters, validation errors 31 BadRequest(AnyhowError), 32 /// 404 Not Found - Resource not found 33 NotFound(AnyhowError), 34 /// 422 Unprocessable Entity - Business logic validation errors 35 UnprocessableEntity(AnyhowError), 36 /// 429 Too Many Requests - Rate limiting 37 TooManyRequests(AnyhowError), 38 /// 503 Service Unavailable - Temporary service issues (node syncing, feature unavailable) 39 ServiceUnavailable(AnyhowError), 40 /// 500 Internal Server Error - Actual server errors, unexpected failures 41 InternalServerError(AnyhowError), 42 } 43 44 /// The serialized REST error sent over the network. 45 #[derive(Debug, Serialize, Deserialize)] 46 pub struct SerializedRestError { 47 pub message: String, 48 pub error_type: String, 49 /// Does not include error chain in message if it is empty, and generates an empty error chain if none is given. 50 #[serde(skip_serializing_if = "Vec::is_empty", default)] 51 pub chain: Vec<String>, 52 } 53 54 impl RestError { 55 /// Create a BadRequest error 56 pub fn bad_request(inner: anyhow::Error) -> Self { 57 Self::BadRequest(inner) 58 } 59 60 /// Create a NotFound error 61 pub fn not_found(inner: anyhow::Error) -> Self { 62 Self::NotFound(inner) 63 } 64 65 /// Create an UnprocessableEntity error 66 pub fn unprocessable_entity(inner: anyhow::Error) -> Self { 67 Self::UnprocessableEntity(inner) 68 } 69 70 /// Create a TooManyRequests error 71 pub fn too_many_requests(inner: anyhow::Error) -> Self { 72 Self::TooManyRequests(inner) 73 } 74 75 /// Create a ServiceUnavailable error 76 pub fn service_unavailable(inner: anyhow::Error) -> Self { 77 Self::ServiceUnavailable(inner) 78 } 79 80 /// Create an InternalServerError error 81 pub fn internal_server_error(inner: anyhow::Error) -> Self { 82 Self::InternalServerError(inner) 83 } 84 85 /// Extract the full chain of errors from the `anyhow::Error`. 86 /// (excludes the top-level error) 87 fn error_chain(error: &AnyhowError) -> Vec<String> { 88 let mut chain = vec![]; 89 let mut source = error.source(); 90 while let Some(err) = source { 91 chain.push(err.to_string()); 92 source = err.source(); 93 } 94 chain 95 } 96 } 97 98 impl IntoResponse for RestError { 99 fn into_response(self) -> Response { 100 let (status, error_type, error) = match self { 101 RestError::BadRequest(err) => (StatusCode::BAD_REQUEST, "bad_request", err), 102 RestError::NotFound(err) => (StatusCode::NOT_FOUND, "not_found", err), 103 RestError::UnprocessableEntity(err) => (StatusCode::UNPROCESSABLE_ENTITY, "unprocessable_entity", err), 104 RestError::TooManyRequests(err) => (StatusCode::TOO_MANY_REQUESTS, "too_many_requests", err), 105 RestError::ServiceUnavailable(err) => (StatusCode::SERVICE_UNAVAILABLE, "service_unavailable", err), 106 RestError::InternalServerError(err) => (StatusCode::INTERNAL_SERVER_ERROR, "internal_server_error", err), 107 }; 108 109 // Convert to JSON and include the chain of causes (if any). 110 let json_body = serde_json::to_string(&SerializedRestError { 111 message: error.to_string(), 112 error_type: error_type.to_string(), 113 chain: Self::error_chain(&error), 114 }) 115 .unwrap_or_else(|err| format!("Failed to serialize error: {err}")); 116 117 info!("Returning REST error: {json_body}"); 118 119 let mut response = Response::new(json_body.into()); 120 *response.status_mut() = status; 121 response.headers_mut().insert(CONTENT_TYPE, "application/json".parse().unwrap()); 122 response 123 } 124 } 125 126 impl From<anyhow::Error> for RestError { 127 fn from(err: anyhow::Error) -> Self { 128 // Default to 500 Internal Server Error 129 Self::InternalServerError(err) 130 } 131 } 132 133 impl From<String> for RestError { 134 fn from(msg: String) -> Self { 135 // Default to 500 Internal Server Error 136 Self::InternalServerError(anyhow::anyhow!(msg)) 137 } 138 } 139 140 impl From<&str> for RestError { 141 fn from(msg: &str) -> Self { 142 // Default to 500 Internal Server Error 143 Self::InternalServerError(anyhow::anyhow!(msg.to_string())) 144 } 145 } 146 147 /// Implement `From<JsonRejection>` for `RestError` to enable automatic conversion 148 impl From<JsonRejection> for RestError { 149 fn from(rejection: JsonRejection) -> Self { 150 match rejection { 151 JsonRejection::JsonDataError(err) => { 152 RestError::bad_request(anyhow!(err).context("Invalid JSON data in request body")) 153 } 154 JsonRejection::JsonSyntaxError(err) => { 155 RestError::bad_request(anyhow!(err).context("Invalid JSON syntax in request body")) 156 } 157 JsonRejection::MissingJsonContentType(_) => { 158 RestError::bad_request(anyhow!("Content-Type must be `application/json`")) 159 } 160 JsonRejection::BytesRejection(err) => { 161 RestError::bad_request(anyhow!(err).context("Failed to read request body")) 162 } 163 _ => RestError::bad_request(anyhow!("Invalid JSON request")), 164 } 165 } 166 }