Skip to main content

rustack_events_model/
error.rs

1//! EventBridge error types.
2//!
3//! EventBridge errors use JSON format with a `__type` field containing the
4//! short error type name (e.g., `ResourceNotFoundException`).
5
6use std::fmt;
7
8/// Well-known EventBridge error codes.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
10#[non_exhaustive]
11pub enum EventsErrorCode {
12    /// Resource not found.
13    ResourceNotFound,
14    /// Resource already exists.
15    ResourceAlreadyExists,
16    /// Invalid event pattern.
17    InvalidEventPattern,
18    /// Validation error.
19    #[default]
20    ValidationException,
21    /// Limit exceeded.
22    LimitExceeded,
23    /// Concurrent modification.
24    ConcurrentModification,
25    /// Internal error.
26    InternalException,
27    /// Invalid action (unrecognized operation).
28    InvalidAction,
29    /// Missing action header.
30    MissingAction,
31    /// Managed rule exception (cannot modify managed rules).
32    ManagedRuleException,
33    /// Operation not supported.
34    OperationDisabled,
35}
36
37impl EventsErrorCode {
38    /// Returns the short error type string for the JSON `__type` field.
39    #[must_use]
40    pub fn error_type(&self) -> &'static str {
41        self.as_str()
42    }
43
44    /// Returns the short error code string.
45    #[must_use]
46    pub fn as_str(&self) -> &'static str {
47        match self {
48            Self::ResourceNotFound => "ResourceNotFoundException",
49            Self::ResourceAlreadyExists => "ResourceAlreadyExistsException",
50            Self::InvalidEventPattern => "InvalidEventPatternException",
51            Self::ValidationException => "ValidationException",
52            Self::LimitExceeded => "LimitExceededException",
53            Self::ConcurrentModification => "ConcurrentModificationException",
54            Self::InternalException => "InternalException",
55            Self::InvalidAction => "InvalidAction",
56            Self::MissingAction => "MissingAction",
57            Self::ManagedRuleException => "ManagedRuleException",
58            Self::OperationDisabled => "OperationDisabledException",
59        }
60    }
61
62    /// Returns the default HTTP status code for this error.
63    #[must_use]
64    pub fn default_status_code(&self) -> http::StatusCode {
65        match self {
66            Self::InternalException => http::StatusCode::INTERNAL_SERVER_ERROR,
67            _ => http::StatusCode::BAD_REQUEST,
68        }
69    }
70}
71
72impl fmt::Display for EventsErrorCode {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_str(self.as_str())
75    }
76}
77
78/// An EventBridge error response.
79#[derive(Debug)]
80pub struct EventsError {
81    /// The error code.
82    pub code: EventsErrorCode,
83    /// A human-readable error message.
84    pub message: String,
85    /// The HTTP status code.
86    pub status_code: http::StatusCode,
87    /// The underlying source error, if any.
88    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
89}
90
91impl fmt::Display for EventsError {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "EventsError({}): {}", self.code, self.message)
94    }
95}
96
97impl std::error::Error for EventsError {
98    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99        self.source
100            .as_ref()
101            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
102    }
103}
104
105impl EventsError {
106    /// Create a new `EventsError` from an error code.
107    #[must_use]
108    pub fn new(code: EventsErrorCode) -> Self {
109        Self {
110            status_code: code.default_status_code(),
111            message: code.as_str().to_owned(),
112            code,
113            source: None,
114        }
115    }
116
117    /// Create a new `EventsError` with a custom message.
118    #[must_use]
119    pub fn with_message(code: EventsErrorCode, message: impl Into<String>) -> Self {
120        Self {
121            status_code: code.default_status_code(),
122            message: message.into(),
123            code,
124            source: None,
125        }
126    }
127
128    /// Returns the `__type` string for the JSON error response.
129    #[must_use]
130    pub fn error_type(&self) -> &'static str {
131        self.code.error_type()
132    }
133
134    /// Resource not found.
135    #[must_use]
136    pub fn resource_not_found(message: impl Into<String>) -> Self {
137        Self::with_message(EventsErrorCode::ResourceNotFound, message)
138    }
139
140    /// Resource already exists.
141    #[must_use]
142    pub fn resource_already_exists(message: impl Into<String>) -> Self {
143        Self::with_message(EventsErrorCode::ResourceAlreadyExists, message)
144    }
145
146    /// Invalid event pattern.
147    #[must_use]
148    pub fn invalid_event_pattern(message: impl Into<String>) -> Self {
149        Self::with_message(EventsErrorCode::InvalidEventPattern, message)
150    }
151
152    /// Validation error.
153    #[must_use]
154    pub fn validation(message: impl Into<String>) -> Self {
155        Self::with_message(EventsErrorCode::ValidationException, message)
156    }
157
158    /// Internal error.
159    #[must_use]
160    pub fn internal_error(message: impl Into<String>) -> Self {
161        Self::with_message(EventsErrorCode::InternalException, message)
162    }
163
164    /// Missing action header.
165    #[must_use]
166    pub fn missing_action() -> Self {
167        Self::with_message(
168            EventsErrorCode::MissingAction,
169            "Missing required header: X-Amz-Target",
170        )
171    }
172
173    /// Unknown operation.
174    #[must_use]
175    pub fn unknown_operation(target: &str) -> Self {
176        Self::with_message(
177            EventsErrorCode::InvalidAction,
178            format!("Operation {target} is not supported."),
179        )
180    }
181
182    /// Not implemented.
183    #[must_use]
184    pub fn not_implemented(operation: &str) -> Self {
185        Self::with_message(
186            EventsErrorCode::InternalException,
187            format!("Operation {operation} is not yet implemented"),
188        )
189    }
190
191    /// Limit exceeded.
192    #[must_use]
193    pub fn limit_exceeded(message: impl Into<String>) -> Self {
194        Self::with_message(EventsErrorCode::LimitExceeded, message)
195    }
196}
197
198/// Create an `EventsError` from an error code.
199///
200/// # Examples
201///
202/// ```
203/// use rustack_events_model::events_error;
204/// use rustack_events_model::error::EventsErrorCode;
205///
206/// let err = events_error!(ValidationException);
207/// assert_eq!(err.code, EventsErrorCode::ValidationException);
208///
209/// let err = events_error!(ResourceNotFound, "Event bus my-bus does not exist.");
210/// assert_eq!(err.code, EventsErrorCode::ResourceNotFound);
211/// ```
212#[macro_export]
213macro_rules! events_error {
214    ($code:ident) => {
215        $crate::error::EventsError::new($crate::error::EventsErrorCode::$code)
216    };
217    ($code:ident, $msg:expr) => {
218        $crate::error::EventsError::with_message($crate::error::EventsErrorCode::$code, $msg)
219    };
220}