anvil/eth/beacon/
error.rs

1//! Beacon API error types
2
3use axum::{
4    Json,
5    http::StatusCode,
6    response::{IntoResponse, Response},
7};
8use serde::{Deserialize, Serialize};
9use std::{
10    borrow::Cow,
11    fmt::{self, Display},
12};
13
14/// Represents a Beacon API error response
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct BeaconError {
17    /// HTTP status code
18    #[serde(skip)]
19    pub status_code: u16,
20    /// Error code
21    pub code: BeaconErrorCode,
22    /// Error message
23    pub message: Cow<'static, str>,
24}
25
26impl BeaconError {
27    /// Creates a new beacon error with the given code
28    pub fn new(code: BeaconErrorCode, message: impl Into<Cow<'static, str>>) -> Self {
29        let status_code = code.status_code();
30        Self { status_code, code, message: message.into() }
31    }
32
33    /// Helper function to create a 400 Bad Request error for invalid block ID
34    pub fn invalid_block_id(block_id: impl Display) -> Self {
35        Self::new(BeaconErrorCode::BadRequest, format!("Invalid block ID: {block_id}"))
36    }
37
38    /// Helper function to create a 404 Not Found error for block not found
39    pub fn block_not_found() -> Self {
40        Self::new(BeaconErrorCode::NotFound, "Block not found")
41    }
42
43    /// Helper function to create a 500 Internal Server Error
44    pub fn internal_error() -> Self {
45        Self::new(BeaconErrorCode::InternalError, "Internal server error")
46    }
47
48    /// Helper function to create a 500 Internal Server Error with the given details
49    pub fn internal_error_with_details(error: impl Display) -> Self {
50        Self::new(BeaconErrorCode::InternalError, format!("Internal server error: {error}"))
51    }
52
53    /// Converts to an Axum response
54    pub fn into_response(self) -> Response {
55        let status =
56            StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
57
58        (
59            status,
60            Json(serde_json::json!({
61                "code": self.code as u16,
62                "message": self.message,
63            })),
64        )
65            .into_response()
66    }
67}
68
69impl fmt::Display for BeaconError {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "{}: {}", self.code.as_str(), self.message)
72    }
73}
74
75impl std::error::Error for BeaconError {}
76
77impl IntoResponse for BeaconError {
78    fn into_response(self) -> Response {
79        Self::into_response(self)
80    }
81}
82
83/// Beacon API error codes following the beacon chain specification
84#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
85#[repr(u16)]
86pub enum BeaconErrorCode {
87    BadRequest = 400,
88    NotFound = 404,
89    InternalError = 500,
90}
91
92impl BeaconErrorCode {
93    /// Returns the HTTP status code for this error
94    pub const fn status_code(&self) -> u16 {
95        *self as u16
96    }
97
98    /// Returns a string representation of the error code
99    pub const fn as_str(&self) -> &'static str {
100        match self {
101            Self::BadRequest => "Bad Request",
102            Self::NotFound => "Not Found",
103            Self::InternalError => "Internal Server Error",
104        }
105    }
106}
107
108impl fmt::Display for BeaconErrorCode {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "{}", self.as_str())
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_beacon_error_codes() {
120        assert_eq!(BeaconErrorCode::BadRequest.status_code(), 400);
121        assert_eq!(BeaconErrorCode::NotFound.status_code(), 404);
122        assert_eq!(BeaconErrorCode::InternalError.status_code(), 500);
123    }
124
125    #[test]
126    fn test_beacon_error_display() {
127        let err = BeaconError::invalid_block_id("current");
128        assert_eq!(err.to_string(), "Bad Request: Invalid block ID: current");
129    }
130}