/ node / rest / src / helpers / error.rs
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  }