Skip to main content

a3s_boot/
error.rs

1use thiserror::Error;
2
3/// Errors returned while building or serving a Boot application.
4#[derive(Debug, Error)]
5pub enum BootError {
6    #[error("module name cannot be empty")]
7    EmptyModuleName,
8    #[error("route path must start with '/': {0}")]
9    InvalidRoutePath(String),
10    #[error("host pattern is invalid: {0}")]
11    InvalidHostPattern(String),
12    #[error("route is already registered: {0}")]
13    DuplicateRoute(String),
14    #[error("route was not found: {0}")]
15    NotFound(String),
16    #[error("method is not allowed: {0}")]
17    MethodNotAllowed(String),
18    #[error("provider token is already registered: {0}")]
19    DuplicateProvider(String),
20    #[error("provider token is not registered: {0}")]
21    MissingProvider(String),
22    #[error("provider token has a different concrete type: {0}")]
23    ProviderTypeMismatch(String),
24    #[error("http exception {status}: {message}")]
25    HttpException { status: u16, message: String },
26    #[error("request was forbidden: {0}")]
27    Forbidden(String),
28    #[error("request was unauthorized: {0}")]
29    Unauthorized(String),
30    #[error("bad request: {0}")]
31    BadRequest(String),
32    #[error("request timed out: {0}")]
33    RequestTimeout(String),
34    #[error("resource conflict: {0}")]
35    Conflict(String),
36    #[error("resource is gone: {0}")]
37    Gone(String),
38    #[error("precondition failed: {0}")]
39    PreconditionFailed(String),
40    #[error("payload is too large: {0}")]
41    PayloadTooLarge(String),
42    #[error("unsupported media type: {0}")]
43    UnsupportedMediaType(String),
44    #[error("not acceptable: {0}")]
45    NotAcceptable(String),
46    #[error("I am a teapot: {0}")]
47    ImATeapot(String),
48    #[error("unprocessable entity: {0}")]
49    UnprocessableEntity(String),
50    #[error("too many requests: {0}")]
51    TooManyRequests(String),
52    #[error("internal server error: {0}")]
53    InternalServerError(String),
54    #[error("not implemented: {0}")]
55    NotImplemented(String),
56    #[error("bad gateway: {0}")]
57    BadGateway(String),
58    #[error("service unavailable: {0}")]
59    ServiceUnavailable(String),
60    #[error("gateway timeout: {0}")]
61    GatewayTimeout(String),
62    #[error("adapter error: {0}")]
63    Adapter(String),
64    #[error("internal error: {0}")]
65    Internal(String),
66    #[error("I/O error: {0}")]
67    Io(#[from] std::io::Error),
68}
69
70/// Stable category for a [`BootError`], useful for Nest-style catch filters.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72pub enum BootErrorKind {
73    EmptyModuleName,
74    InvalidRoutePath,
75    InvalidHostPattern,
76    DuplicateRoute,
77    NotFound,
78    MethodNotAllowed,
79    DuplicateProvider,
80    MissingProvider,
81    ProviderTypeMismatch,
82    HttpException,
83    Forbidden,
84    Unauthorized,
85    BadRequest,
86    RequestTimeout,
87    Conflict,
88    Gone,
89    PreconditionFailed,
90    PayloadTooLarge,
91    UnsupportedMediaType,
92    NotAcceptable,
93    ImATeapot,
94    UnprocessableEntity,
95    TooManyRequests,
96    InternalServerError,
97    NotImplemented,
98    BadGateway,
99    ServiceUnavailable,
100    GatewayTimeout,
101    Adapter,
102    Internal,
103    Io,
104}
105
106impl BootError {
107    /// Stable category for this error.
108    pub fn kind(&self) -> BootErrorKind {
109        match self {
110            Self::EmptyModuleName => BootErrorKind::EmptyModuleName,
111            Self::InvalidRoutePath(_) => BootErrorKind::InvalidRoutePath,
112            Self::InvalidHostPattern(_) => BootErrorKind::InvalidHostPattern,
113            Self::DuplicateRoute(_) => BootErrorKind::DuplicateRoute,
114            Self::NotFound(_) => BootErrorKind::NotFound,
115            Self::MethodNotAllowed(_) => BootErrorKind::MethodNotAllowed,
116            Self::DuplicateProvider(_) => BootErrorKind::DuplicateProvider,
117            Self::MissingProvider(_) => BootErrorKind::MissingProvider,
118            Self::ProviderTypeMismatch(_) => BootErrorKind::ProviderTypeMismatch,
119            Self::HttpException { .. } => BootErrorKind::HttpException,
120            Self::Forbidden(_) => BootErrorKind::Forbidden,
121            Self::Unauthorized(_) => BootErrorKind::Unauthorized,
122            Self::BadRequest(_) => BootErrorKind::BadRequest,
123            Self::RequestTimeout(_) => BootErrorKind::RequestTimeout,
124            Self::Conflict(_) => BootErrorKind::Conflict,
125            Self::Gone(_) => BootErrorKind::Gone,
126            Self::PreconditionFailed(_) => BootErrorKind::PreconditionFailed,
127            Self::PayloadTooLarge(_) => BootErrorKind::PayloadTooLarge,
128            Self::UnsupportedMediaType(_) => BootErrorKind::UnsupportedMediaType,
129            Self::NotAcceptable(_) => BootErrorKind::NotAcceptable,
130            Self::ImATeapot(_) => BootErrorKind::ImATeapot,
131            Self::UnprocessableEntity(_) => BootErrorKind::UnprocessableEntity,
132            Self::TooManyRequests(_) => BootErrorKind::TooManyRequests,
133            Self::InternalServerError(_) => BootErrorKind::InternalServerError,
134            Self::NotImplemented(_) => BootErrorKind::NotImplemented,
135            Self::BadGateway(_) => BootErrorKind::BadGateway,
136            Self::ServiceUnavailable(_) => BootErrorKind::ServiceUnavailable,
137            Self::GatewayTimeout(_) => BootErrorKind::GatewayTimeout,
138            Self::Adapter(_) => BootErrorKind::Adapter,
139            Self::Internal(_) => BootErrorKind::Internal,
140            Self::Io(_) => BootErrorKind::Io,
141        }
142    }
143
144    /// HTTP status code that adapters should use for this error.
145    pub fn http_status_code(&self) -> u16 {
146        match self {
147            Self::HttpException { status, .. } => *status,
148            Self::NotFound(_) => 404,
149            Self::MethodNotAllowed(_) => 405,
150            Self::Unauthorized(_) => 401,
151            Self::Forbidden(_) => 403,
152            Self::BadRequest(_) => 400,
153            Self::RequestTimeout(_) => 408,
154            Self::Conflict(_) => 409,
155            Self::Gone(_) => 410,
156            Self::PreconditionFailed(_) => 412,
157            Self::PayloadTooLarge(_) => 413,
158            Self::UnsupportedMediaType(_) => 415,
159            Self::NotAcceptable(_) => 406,
160            Self::ImATeapot(_) => 418,
161            Self::UnprocessableEntity(_) => 422,
162            Self::TooManyRequests(_) => 429,
163            Self::InternalServerError(_) => 500,
164            Self::NotImplemented(_) => 501,
165            Self::BadGateway(_) => 502,
166            Self::ServiceUnavailable(_) => 503,
167            Self::GatewayTimeout(_) => 504,
168            _ => 500,
169        }
170    }
171
172    /// Text response body that adapters should use for this error.
173    pub fn http_response_message(&self) -> String {
174        match self {
175            Self::HttpException { message, .. }
176            | Self::RequestTimeout(message)
177            | Self::Conflict(message)
178            | Self::Gone(message)
179            | Self::PreconditionFailed(message)
180            | Self::ImATeapot(message)
181            | Self::UnprocessableEntity(message)
182            | Self::InternalServerError(message)
183            | Self::NotImplemented(message)
184            | Self::BadGateway(message)
185            | Self::ServiceUnavailable(message)
186            | Self::GatewayTimeout(message)
187            | Self::TooManyRequests(message)
188            | Self::NotAcceptable(message)
189            | Self::UnsupportedMediaType(message)
190            | Self::PayloadTooLarge(message)
191            | Self::BadRequest(message)
192            | Self::Forbidden(message)
193            | Self::Unauthorized(message)
194            | Self::MethodNotAllowed(message)
195            | Self::NotFound(message) => message.clone(),
196            error => error.to_string(),
197        }
198    }
199
200    pub fn http_exception(status: u16, message: impl Into<String>) -> crate::Result<Self> {
201        if (100..600).contains(&status) {
202            return Ok(Self::from_http_status(status, message));
203        }
204
205        Err(Self::Internal(format!(
206            "invalid HTTP exception status {status}"
207        )))
208    }
209
210    pub fn from_http_status(status: u16, message: impl Into<String>) -> Self {
211        let message = message.into();
212        match status {
213            400 => Self::BadRequest(message),
214            401 => Self::Unauthorized(message),
215            403 => Self::Forbidden(message),
216            404 => Self::NotFound(message),
217            405 => Self::MethodNotAllowed(message),
218            406 => Self::NotAcceptable(message),
219            408 => Self::RequestTimeout(message),
220            409 => Self::Conflict(message),
221            410 => Self::Gone(message),
222            412 => Self::PreconditionFailed(message),
223            413 => Self::PayloadTooLarge(message),
224            415 => Self::UnsupportedMediaType(message),
225            418 => Self::ImATeapot(message),
226            422 => Self::UnprocessableEntity(message),
227            429 => Self::TooManyRequests(message),
228            500 => Self::InternalServerError(message),
229            501 => Self::NotImplemented(message),
230            502 => Self::BadGateway(message),
231            503 => Self::ServiceUnavailable(message),
232            504 => Self::GatewayTimeout(message),
233            status => Self::HttpException { status, message },
234        }
235    }
236
237    pub fn not_found(message: impl Into<String>) -> Self {
238        Self::NotFound(message.into())
239    }
240
241    pub fn method_not_allowed(message: impl Into<String>) -> Self {
242        Self::MethodNotAllowed(message.into())
243    }
244
245    pub fn unauthorized(message: impl Into<String>) -> Self {
246        Self::Unauthorized(message.into())
247    }
248
249    pub fn forbidden(message: impl Into<String>) -> Self {
250        Self::Forbidden(message.into())
251    }
252
253    pub fn bad_request(message: impl Into<String>) -> Self {
254        Self::BadRequest(message.into())
255    }
256
257    pub fn request_timeout(message: impl Into<String>) -> Self {
258        Self::RequestTimeout(message.into())
259    }
260
261    pub fn conflict(message: impl Into<String>) -> Self {
262        Self::Conflict(message.into())
263    }
264
265    pub fn gone(message: impl Into<String>) -> Self {
266        Self::Gone(message.into())
267    }
268
269    pub fn precondition_failed(message: impl Into<String>) -> Self {
270        Self::PreconditionFailed(message.into())
271    }
272
273    pub fn payload_too_large(message: impl Into<String>) -> Self {
274        Self::PayloadTooLarge(message.into())
275    }
276
277    pub fn unsupported_media_type(message: impl Into<String>) -> Self {
278        Self::UnsupportedMediaType(message.into())
279    }
280
281    pub fn not_acceptable(message: impl Into<String>) -> Self {
282        Self::NotAcceptable(message.into())
283    }
284
285    pub fn im_a_teapot(message: impl Into<String>) -> Self {
286        Self::ImATeapot(message.into())
287    }
288
289    pub fn unprocessable_entity(message: impl Into<String>) -> Self {
290        Self::UnprocessableEntity(message.into())
291    }
292
293    pub fn too_many_requests(message: impl Into<String>) -> Self {
294        Self::TooManyRequests(message.into())
295    }
296
297    pub fn internal_server_error(message: impl Into<String>) -> Self {
298        Self::InternalServerError(message.into())
299    }
300
301    pub fn not_implemented(message: impl Into<String>) -> Self {
302        Self::NotImplemented(message.into())
303    }
304
305    pub fn bad_gateway(message: impl Into<String>) -> Self {
306        Self::BadGateway(message.into())
307    }
308
309    pub fn service_unavailable(message: impl Into<String>) -> Self {
310        Self::ServiceUnavailable(message.into())
311    }
312
313    pub fn gateway_timeout(message: impl Into<String>) -> Self {
314        Self::GatewayTimeout(message.into())
315    }
316
317    pub fn internal(message: impl Into<String>) -> Self {
318        Self::Internal(message.into())
319    }
320
321    pub(crate) fn clone_for_filter(&self) -> Self {
322        match self {
323            Self::HttpException { status, message } => Self::HttpException {
324                status: *status,
325                message: message.clone(),
326            },
327            Self::NotFound(message)
328            | Self::MethodNotAllowed(message)
329            | Self::Forbidden(message)
330            | Self::Unauthorized(message)
331            | Self::BadRequest(message)
332            | Self::RequestTimeout(message)
333            | Self::Conflict(message)
334            | Self::Gone(message)
335            | Self::PreconditionFailed(message)
336            | Self::PayloadTooLarge(message)
337            | Self::UnsupportedMediaType(message)
338            | Self::NotAcceptable(message)
339            | Self::ImATeapot(message)
340            | Self::UnprocessableEntity(message)
341            | Self::TooManyRequests(message)
342            | Self::InternalServerError(message)
343            | Self::NotImplemented(message)
344            | Self::BadGateway(message)
345            | Self::ServiceUnavailable(message)
346            | Self::GatewayTimeout(message)
347            | Self::Adapter(message)
348            | Self::Internal(message)
349            | Self::InvalidRoutePath(message)
350            | Self::InvalidHostPattern(message)
351            | Self::DuplicateRoute(message)
352            | Self::DuplicateProvider(message)
353            | Self::MissingProvider(message)
354            | Self::ProviderTypeMismatch(message) => {
355                Self::from_kind_and_message(self.kind(), message)
356            }
357            Self::EmptyModuleName => Self::EmptyModuleName,
358            Self::Io(error) => Self::Io(std::io::Error::new(error.kind(), error.to_string())),
359        }
360    }
361
362    fn from_kind_and_message(kind: BootErrorKind, message: &str) -> Self {
363        match kind {
364            BootErrorKind::EmptyModuleName => Self::EmptyModuleName,
365            BootErrorKind::InvalidRoutePath => Self::InvalidRoutePath(message.to_string()),
366            BootErrorKind::InvalidHostPattern => Self::InvalidHostPattern(message.to_string()),
367            BootErrorKind::DuplicateRoute => Self::DuplicateRoute(message.to_string()),
368            BootErrorKind::NotFound => Self::NotFound(message.to_string()),
369            BootErrorKind::MethodNotAllowed => Self::MethodNotAllowed(message.to_string()),
370            BootErrorKind::DuplicateProvider => Self::DuplicateProvider(message.to_string()),
371            BootErrorKind::MissingProvider => Self::MissingProvider(message.to_string()),
372            BootErrorKind::ProviderTypeMismatch => Self::ProviderTypeMismatch(message.to_string()),
373            BootErrorKind::HttpException => Self::HttpException {
374                status: 500,
375                message: message.to_string(),
376            },
377            BootErrorKind::Forbidden => Self::Forbidden(message.to_string()),
378            BootErrorKind::Unauthorized => Self::Unauthorized(message.to_string()),
379            BootErrorKind::BadRequest => Self::BadRequest(message.to_string()),
380            BootErrorKind::RequestTimeout => Self::RequestTimeout(message.to_string()),
381            BootErrorKind::Conflict => Self::Conflict(message.to_string()),
382            BootErrorKind::Gone => Self::Gone(message.to_string()),
383            BootErrorKind::PreconditionFailed => Self::PreconditionFailed(message.to_string()),
384            BootErrorKind::PayloadTooLarge => Self::PayloadTooLarge(message.to_string()),
385            BootErrorKind::UnsupportedMediaType => Self::UnsupportedMediaType(message.to_string()),
386            BootErrorKind::NotAcceptable => Self::NotAcceptable(message.to_string()),
387            BootErrorKind::ImATeapot => Self::ImATeapot(message.to_string()),
388            BootErrorKind::UnprocessableEntity => Self::UnprocessableEntity(message.to_string()),
389            BootErrorKind::TooManyRequests => Self::TooManyRequests(message.to_string()),
390            BootErrorKind::InternalServerError => Self::InternalServerError(message.to_string()),
391            BootErrorKind::NotImplemented => Self::NotImplemented(message.to_string()),
392            BootErrorKind::BadGateway => Self::BadGateway(message.to_string()),
393            BootErrorKind::ServiceUnavailable => Self::ServiceUnavailable(message.to_string()),
394            BootErrorKind::GatewayTimeout => Self::GatewayTimeout(message.to_string()),
395            BootErrorKind::Adapter => Self::Adapter(message.to_string()),
396            BootErrorKind::Internal => Self::Internal(message.to_string()),
397            BootErrorKind::Io => Self::Io(std::io::Error::other(message.to_string())),
398        }
399    }
400}