error_envelope/
codes.rs

1use serde::{Deserialize, Serialize};
2
3/// Machine-readable error codes that remain stable across releases.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
6pub enum Code {
7    /// Internal server error (500).
8    Internal,
9    /// Bad request error (400).
10    BadRequest,
11    /// Resource not found (404).
12    NotFound,
13    /// HTTP method not allowed (405).
14    MethodNotAllowed,
15    /// Resource permanently deleted (410).
16    Gone,
17    /// Resource conflict, often due to concurrent modification (409).
18    Conflict,
19    /// Request payload exceeds size limit (413).
20    PayloadTooLarge,
21    /// Request timed out before completion (408).
22    RequestTimeout,
23    /// Too many requests, client should retry later (429).
24    RateLimited,
25    /// Service temporarily unavailable (503).
26    Unavailable,
27
28    /// Input validation failed (400).
29    ValidationFailed,
30    /// Authentication required or credentials invalid (401).
31    Unauthorized,
32    /// Authenticated but not permitted to access resource (403).
33    Forbidden,
34    /// Request is well-formed but semantically invalid (422).
35    UnprocessableEntity,
36
37    /// Gateway or processing timeout (504).
38    Timeout,
39    /// Request was canceled by client (499).
40    Canceled,
41
42    /// Downstream service returned an error (502).
43    DownstreamError,
44    /// Downstream service timed out (504).
45    DownstreamTimeout,
46}
47
48impl Code {
49    /// Returns the default HTTP status code for this error code.
50    pub fn default_status(&self) -> u16 {
51        match self {
52            Code::Internal => 500,
53            Code::BadRequest => 400,
54            Code::NotFound => 404,
55            Code::MethodNotAllowed => 405,
56            Code::Gone => 410,
57            Code::Conflict => 409,
58            Code::PayloadTooLarge => 413,
59            Code::RequestTimeout => 408,
60            Code::RateLimited => 429,
61            Code::Unavailable => 503,
62            Code::ValidationFailed => 400,
63            Code::Unauthorized => 401,
64            Code::Forbidden => 403,
65            Code::UnprocessableEntity => 422,
66            Code::Timeout => 504,
67            Code::Canceled => 499,
68            Code::DownstreamError => 502,
69            Code::DownstreamTimeout => 504,
70        }
71    }
72
73    /// Returns whether this error is retryable by default.
74    pub fn is_retryable_default(&self) -> bool {
75        matches!(
76            self,
77            Code::Timeout
78                | Code::DownstreamTimeout
79                | Code::Unavailable
80                | Code::RateLimited
81                | Code::RequestTimeout
82        )
83    }
84
85    /// Returns a default human-readable message for this code.
86    pub fn default_message(&self) -> &'static str {
87        match self {
88            Code::Internal => "Internal error",
89            Code::BadRequest => "Bad request",
90            Code::ValidationFailed => "Invalid input",
91            Code::Unauthorized => "Unauthorized",
92            Code::Forbidden => "Forbidden",
93            Code::NotFound => "Not found",
94            Code::Gone => "Resource no longer exists",
95            Code::Conflict => "Conflict",
96            Code::PayloadTooLarge => "Payload too large",
97            Code::UnprocessableEntity => "Unprocessable entity",
98            Code::RateLimited => "Rate limited",
99            Code::RequestTimeout | Code::Timeout | Code::DownstreamTimeout => "Request timed out",
100            Code::Unavailable => "Service unavailable",
101            Code::Canceled => "Request canceled",
102            Code::DownstreamError => "Downstream service error",
103            Code::MethodNotAllowed => "Method not allowed",
104        }
105    }
106}