error_envelope/
helpers.rs

1use crate::{Code, Error};
2
3/// Helper constructors for common error types.
4impl Error {
5    // Generic errors
6
7    /// Creates an internal server error (500).
8    pub fn internal(message: impl Into<String>) -> Self {
9        Self::new(Code::Internal, 500, message).with_retryable(false)
10    }
11
12    /// Creates a bad request error (400).
13    pub fn bad_request(message: impl Into<String>) -> Self {
14        Self::new(Code::BadRequest, 400, message).with_retryable(false)
15    }
16
17    /// Creates a validation error (400).
18    pub fn validation(message: impl Into<String>) -> Self {
19        Self::new(Code::ValidationFailed, 400, message).with_retryable(false)
20    }
21
22    /// Creates an unauthorized error (401).
23    pub fn unauthorized(message: impl Into<String>) -> Self {
24        Self::new(Code::Unauthorized, 401, message).with_retryable(false)
25    }
26
27    /// Creates a forbidden error (403).
28    pub fn forbidden(message: impl Into<String>) -> Self {
29        Self::new(Code::Forbidden, 403, message).with_retryable(false)
30    }
31
32    /// Creates a not found error (404).
33    pub fn not_found(message: impl Into<String>) -> Self {
34        Self::new(Code::NotFound, 404, message).with_retryable(false)
35    }
36
37    /// Creates a method not allowed error (405).
38    pub fn method_not_allowed(message: impl Into<String>) -> Self {
39        Self::new(Code::MethodNotAllowed, 405, message).with_retryable(false)
40    }
41
42    /// Creates a request timeout error (408).
43    pub fn request_timeout(message: impl Into<String>) -> Self {
44        Self::new(Code::RequestTimeout, 408, message).with_retryable(true)
45    }
46
47    /// Creates a conflict error (409).
48    pub fn conflict(message: impl Into<String>) -> Self {
49        Self::new(Code::Conflict, 409, message).with_retryable(false)
50    }
51
52    /// Creates a gone error (410).
53    pub fn gone(message: impl Into<String>) -> Self {
54        Self::new(Code::Gone, 410, message).with_retryable(false)
55    }
56
57    /// Creates a payload too large error (413).
58    pub fn payload_too_large(message: impl Into<String>) -> Self {
59        Self::new(Code::PayloadTooLarge, 413, message).with_retryable(false)
60    }
61
62    /// Creates an unprocessable entity error (422).
63    pub fn unprocessable_entity(message: impl Into<String>) -> Self {
64        Self::new(Code::UnprocessableEntity, 422, message).with_retryable(false)
65    }
66
67    /// Creates a rate limited error (429).
68    pub fn rate_limited(message: impl Into<String>) -> Self {
69        Self::new(Code::RateLimited, 429, message).with_retryable(true)
70    }
71
72    /// Creates a timeout error (504).
73    pub fn timeout(message: impl Into<String>) -> Self {
74        Self::new(Code::Timeout, 504, message).with_retryable(true)
75    }
76
77    /// Creates an unavailable error (503).
78    pub fn unavailable(message: impl Into<String>) -> Self {
79        Self::new(Code::Unavailable, 503, message).with_retryable(true)
80    }
81
82    /// Creates a downstream error (502).
83    pub fn downstream(service: impl Into<String>, cause: impl std::error::Error) -> Self {
84        let service = service.into();
85        let mut err = Self::wrap(Code::DownstreamError, 502, "", cause);
86        if !service.is_empty() {
87            err = err.with_details(serde_json::json!({"service": service}));
88        }
89        err.with_retryable(true)
90    }
91
92    /// Creates a downstream timeout error (504).
93    pub fn downstream_timeout(service: impl Into<String>, cause: impl std::error::Error) -> Self {
94        let service = service.into();
95        let mut err = Self::wrap(Code::DownstreamTimeout, 504, "", cause);
96        if !service.is_empty() {
97            err = err.with_details(serde_json::json!({"service": service}));
98        }
99        err.with_retryable(true)
100    }
101}
102
103// Formatted constructors (using format! macro)
104
105/// Creates an internal server error with formatted message.
106pub fn internalf(message: impl Into<String>) -> Error {
107    Error::internal(message)
108}
109
110/// Creates a bad request error with formatted message.
111pub fn bad_requestf(message: impl Into<String>) -> Error {
112    Error::bad_request(message)
113}
114
115/// Creates a not found error with formatted message.
116pub fn not_foundf(message: impl Into<String>) -> Error {
117    Error::not_found(message)
118}
119
120/// Creates an unauthorized error with formatted message.
121pub fn unauthorizedf(message: impl Into<String>) -> Error {
122    Error::unauthorized(message)
123}
124
125/// Creates a forbidden error with formatted message.
126pub fn forbiddenf(message: impl Into<String>) -> Error {
127    Error::forbidden(message)
128}
129
130/// Creates a conflict error with formatted message.
131pub fn conflictf(message: impl Into<String>) -> Error {
132    Error::conflict(message)
133}
134
135/// Creates a timeout error with formatted message.
136pub fn timeoutf(message: impl Into<String>) -> Error {
137    Error::timeout(message)
138}
139
140/// Creates an unavailable error with formatted message.
141pub fn unavailablef(message: impl Into<String>) -> Error {
142    Error::unavailable(message)
143}
144
145// Additional helpers
146
147use serde_json::json;
148use std::collections::HashMap;
149
150/// Field-level validation errors.
151pub type FieldErrors = HashMap<String, String>;
152
153/// Creates a validation error with field-level details.
154pub fn validation(fields: FieldErrors) -> Error {
155    Error::new(Code::ValidationFailed, 400, "")
156        .with_details(json!({"fields": fields}))
157        .with_retryable(false)
158}
159
160/// Maps arbitrary errors into an Error.
161///
162/// Handles common error types and wraps unknown errors as Internal.
163pub fn from(err: impl std::error::Error + 'static) -> Error {
164    let err_str = err.to_string().to_lowercase();
165
166    // Check for timeout errors
167    if err_str.contains("timeout") || err_str.contains("timed out") {
168        return Error::timeout("");
169    }
170
171    // Check for canceled errors
172    if err_str.contains("cancel") {
173        return Error::new(Code::Canceled, 499, "").with_retryable(false);
174    }
175
176    // Default to wrapping as internal error
177    Error::wrap(Code::Internal, 500, "", err).with_retryable(false)
178}
179
180/// Checks if an error has the given code.
181pub fn is(err: &Error, code: Code) -> bool {
182    err.code == code
183}