anvil/server/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 410 Gone error for deprecated endpoints
49    pub fn deprecated_endpoint_with_hint(hint: impl Display) -> Self {
50        Self::new(BeaconErrorCode::Gone, format!("This endpoint is deprecated. {hint}"))
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    Gone = 410,
90    InternalError = 500,
91}
92
93impl BeaconErrorCode {
94    /// Returns the HTTP status code for this error
95    pub const fn status_code(&self) -> u16 {
96        *self as u16
97    }
98
99    /// Returns a string representation of the error code
100    pub const fn as_str(&self) -> &'static str {
101        match self {
102            Self::BadRequest => "Bad Request",
103            Self::NotFound => "Not Found",
104            Self::Gone => "Gone",
105            Self::InternalError => "Internal Server Error",
106        }
107    }
108}
109
110impl fmt::Display for BeaconErrorCode {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(f, "{}", self.as_str())
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_beacon_error_codes() {
122        assert_eq!(BeaconErrorCode::BadRequest.status_code(), 400);
123        assert_eq!(BeaconErrorCode::NotFound.status_code(), 404);
124        assert_eq!(BeaconErrorCode::InternalError.status_code(), 500);
125    }
126
127    #[test]
128    fn test_beacon_error_display() {
129        let err = BeaconError::invalid_block_id("current");
130        assert_eq!(err.to_string(), "Bad Request: Invalid block ID: current");
131    }
132}