masterror/app_error/constructors.rs
1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2//
3// SPDX-License-Identifier: MIT
4
5//! Canonical constructors for [`AppError`].
6//!
7//! This module provides ergonomic constructors for all error kinds, organized
8//! by category:
9//!
10//! - **4xx-ish (Client errors)**: `not_found`, `validation`, `unauthorized`,
11//! `forbidden`, `conflict`, `bad_request`, `rate_limited`, `telegram_auth`
12//! - **5xx-ish (Server errors)**: `internal`, `service`, `database`, `config`,
13//! `turnkey`
14//! - **Infra/Network**: `timeout`, `network`, `dependency_unavailable`,
15//! `service_unavailable`
16//! - **Serialization/External**: `serialization`, `deserialization`,
17//! `external_api`, `queue`, `cache`
18//!
19//! All constructors accept any type that implements `Into<Cow<'static, str>>`,
20//! enabling flexible message construction from string literals, owned strings,
21//! or pre-built `Cow` instances.
22
23use alloc::borrow::Cow;
24
25use super::core::AppError;
26use crate::AppErrorKind;
27
28impl AppError {
29 // --- Canonical constructors (keep in sync with AppErrorKind) -------------
30
31 // 4xx-ish
32 /// Build a `NotFound` error.
33 ///
34 /// ```rust
35 /// use masterror::AppError;
36 ///
37 /// let err = AppError::not_found("user not found");
38 /// assert_eq!(err.message.as_deref(), Some("user not found"));
39 /// ```
40 pub fn not_found(msg: impl Into<Cow<'static, str>>) -> Self {
41 Self::with(AppErrorKind::NotFound, msg)
42 }
43
44 /// Build a `Validation` error.
45 ///
46 /// ```rust
47 /// use masterror::AppError;
48 ///
49 /// let err = AppError::validation("invalid email format");
50 /// assert_eq!(err.message.as_deref(), Some("invalid email format"));
51 /// ```
52 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
53 Self::with(AppErrorKind::Validation, msg)
54 }
55
56 /// Build an `Unauthorized` error.
57 ///
58 /// ```rust
59 /// use masterror::AppError;
60 ///
61 /// let err = AppError::unauthorized("missing authentication token");
62 /// assert_eq!(err.message.as_deref(), Some("missing authentication token"));
63 /// ```
64 pub fn unauthorized(msg: impl Into<Cow<'static, str>>) -> Self {
65 Self::with(AppErrorKind::Unauthorized, msg)
66 }
67
68 /// Build a `Forbidden` error.
69 ///
70 /// ```rust
71 /// use masterror::AppError;
72 ///
73 /// let err = AppError::forbidden("insufficient permissions");
74 /// assert_eq!(err.message.as_deref(), Some("insufficient permissions"));
75 /// ```
76 pub fn forbidden(msg: impl Into<Cow<'static, str>>) -> Self {
77 Self::with(AppErrorKind::Forbidden, msg)
78 }
79
80 /// Build a `Conflict` error.
81 ///
82 /// ```rust
83 /// use masterror::AppError;
84 ///
85 /// let err = AppError::conflict("resource already exists");
86 /// assert_eq!(err.message.as_deref(), Some("resource already exists"));
87 /// ```
88 pub fn conflict(msg: impl Into<Cow<'static, str>>) -> Self {
89 Self::with(AppErrorKind::Conflict, msg)
90 }
91
92 /// Build a `BadRequest` error.
93 ///
94 /// ```rust
95 /// use masterror::AppError;
96 ///
97 /// let err = AppError::bad_request("malformed JSON payload");
98 /// assert_eq!(err.message.as_deref(), Some("malformed JSON payload"));
99 /// ```
100 pub fn bad_request(msg: impl Into<Cow<'static, str>>) -> Self {
101 Self::with(AppErrorKind::BadRequest, msg)
102 }
103
104 /// Build a `RateLimited` error.
105 ///
106 /// ```rust
107 /// use masterror::AppError;
108 ///
109 /// let err = AppError::rate_limited("rate limit exceeded");
110 /// assert_eq!(err.message.as_deref(), Some("rate limit exceeded"));
111 /// ```
112 pub fn rate_limited(msg: impl Into<Cow<'static, str>>) -> Self {
113 Self::with(AppErrorKind::RateLimited, msg)
114 }
115
116 /// Build a `TelegramAuth` error.
117 ///
118 /// ```rust
119 /// use masterror::AppError;
120 ///
121 /// let err = AppError::telegram_auth("invalid telegram signature");
122 /// assert_eq!(err.message.as_deref(), Some("invalid telegram signature"));
123 /// ```
124 pub fn telegram_auth(msg: impl Into<Cow<'static, str>>) -> Self {
125 Self::with(AppErrorKind::TelegramAuth, msg)
126 }
127
128 // 5xx-ish
129 /// Build an `Internal` error.
130 ///
131 /// ```rust
132 /// use masterror::AppError;
133 ///
134 /// let err = AppError::internal("unexpected server error");
135 /// assert_eq!(err.message.as_deref(), Some("unexpected server error"));
136 /// ```
137 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
138 Self::with(AppErrorKind::Internal, msg)
139 }
140
141 /// Build a `Service` error (generic server-side service failure).
142 ///
143 /// ```rust
144 /// use masterror::AppError;
145 ///
146 /// let err = AppError::service("service processing failed");
147 /// assert_eq!(err.message.as_deref(), Some("service processing failed"));
148 /// ```
149 pub fn service(msg: impl Into<Cow<'static, str>>) -> Self {
150 Self::with(AppErrorKind::Service, msg)
151 }
152 /// Build a `Database` error with an optional message.
153 ///
154 /// This constructor accepts a pre-built [`Cow`] so callers that already
155 /// manage ownership can pass either borrowed or owned strings. When you
156 /// have plain string data, prefer [`AppError::database_with_message`].
157 ///
158 /// ```rust
159 /// use masterror::AppError;
160 ///
161 /// let err = AppError::database(None);
162 /// assert!(err.message.is_none());
163 /// ```
164 pub fn database(msg: Option<Cow<'static, str>>) -> Self {
165 let err = Self::new_raw(AppErrorKind::Database, msg);
166 err.emit_telemetry();
167 err
168 }
169
170 /// Build a `Database` error with a message.
171 ///
172 /// Convenience wrapper around [`AppError::database`] for the common case
173 /// where you start from a plain string-like value.
174 ///
175 /// ```rust
176 /// use masterror::AppError;
177 ///
178 /// let err = AppError::database_with_message("db down");
179 /// assert_eq!(err.message.as_deref(), Some("db down"));
180 /// ```
181 pub fn database_with_message(msg: impl Into<Cow<'static, str>>) -> Self {
182 Self::database(Some(msg.into()))
183 }
184 /// Build a `Config` error.
185 ///
186 /// ```rust
187 /// use masterror::AppError;
188 ///
189 /// let err = AppError::config("missing required configuration key");
190 /// assert_eq!(
191 /// err.message.as_deref(),
192 /// Some("missing required configuration key")
193 /// );
194 /// ```
195 pub fn config(msg: impl Into<Cow<'static, str>>) -> Self {
196 Self::with(AppErrorKind::Config, msg)
197 }
198
199 /// Build a `Turnkey` error.
200 ///
201 /// ```rust
202 /// use masterror::AppError;
203 ///
204 /// let err = AppError::turnkey("turnkey operation failed");
205 /// assert_eq!(err.message.as_deref(), Some("turnkey operation failed"));
206 /// ```
207 pub fn turnkey(msg: impl Into<Cow<'static, str>>) -> Self {
208 Self::with(AppErrorKind::Turnkey, msg)
209 }
210
211 // Infra / network
212 /// Build a `Timeout` error.
213 ///
214 /// ```rust
215 /// use masterror::AppError;
216 ///
217 /// let err = AppError::timeout("request timed out after 30s");
218 /// assert_eq!(err.message.as_deref(), Some("request timed out after 30s"));
219 /// ```
220 pub fn timeout(msg: impl Into<Cow<'static, str>>) -> Self {
221 Self::with(AppErrorKind::Timeout, msg)
222 }
223
224 /// Build a `Network` error.
225 ///
226 /// ```rust
227 /// use masterror::AppError;
228 ///
229 /// let err = AppError::network("connection refused");
230 /// assert_eq!(err.message.as_deref(), Some("connection refused"));
231 /// ```
232 pub fn network(msg: impl Into<Cow<'static, str>>) -> Self {
233 Self::with(AppErrorKind::Network, msg)
234 }
235
236 /// Build a `DependencyUnavailable` error.
237 ///
238 /// ```rust
239 /// use masterror::AppError;
240 ///
241 /// let err = AppError::dependency_unavailable("payment service unavailable");
242 /// assert_eq!(err.message.as_deref(), Some("payment service unavailable"));
243 /// ```
244 pub fn dependency_unavailable(msg: impl Into<Cow<'static, str>>) -> Self {
245 Self::with(AppErrorKind::DependencyUnavailable, msg)
246 }
247
248 /// Backward-compatible alias; routes to `DependencyUnavailable`.
249 ///
250 /// ```rust
251 /// use masterror::AppError;
252 ///
253 /// let err = AppError::service_unavailable("service temporarily unavailable");
254 /// assert_eq!(
255 /// err.message.as_deref(),
256 /// Some("service temporarily unavailable")
257 /// );
258 /// ```
259 pub fn service_unavailable(msg: impl Into<Cow<'static, str>>) -> Self {
260 Self::with(AppErrorKind::DependencyUnavailable, msg)
261 }
262
263 // Serialization / external API / subsystems
264 /// Build a `Serialization` error.
265 ///
266 /// ```rust
267 /// use masterror::AppError;
268 ///
269 /// let err = AppError::serialization("failed to serialize response");
270 /// assert_eq!(err.message.as_deref(), Some("failed to serialize response"));
271 /// ```
272 pub fn serialization(msg: impl Into<Cow<'static, str>>) -> Self {
273 Self::with(AppErrorKind::Serialization, msg)
274 }
275
276 /// Build a `Deserialization` error.
277 ///
278 /// ```rust
279 /// use masterror::AppError;
280 ///
281 /// let err = AppError::deserialization("failed to parse JSON");
282 /// assert_eq!(err.message.as_deref(), Some("failed to parse JSON"));
283 /// ```
284 pub fn deserialization(msg: impl Into<Cow<'static, str>>) -> Self {
285 Self::with(AppErrorKind::Deserialization, msg)
286 }
287
288 /// Build an `ExternalApi` error.
289 ///
290 /// ```rust
291 /// use masterror::AppError;
292 ///
293 /// let err = AppError::external_api("third-party API returned error");
294 /// assert_eq!(
295 /// err.message.as_deref(),
296 /// Some("third-party API returned error")
297 /// );
298 /// ```
299 pub fn external_api(msg: impl Into<Cow<'static, str>>) -> Self {
300 Self::with(AppErrorKind::ExternalApi, msg)
301 }
302
303 /// Build a `Queue` error.
304 ///
305 /// ```rust
306 /// use masterror::AppError;
307 ///
308 /// let err = AppError::queue("queue is full");
309 /// assert_eq!(err.message.as_deref(), Some("queue is full"));
310 /// ```
311 pub fn queue(msg: impl Into<Cow<'static, str>>) -> Self {
312 Self::with(AppErrorKind::Queue, msg)
313 }
314
315 /// Build a `Cache` error.
316 ///
317 /// ```rust
318 /// use masterror::AppError;
319 ///
320 /// let err = AppError::cache("cache lookup failed");
321 /// assert_eq!(err.message.as_deref(), Some("cache lookup failed"));
322 /// ```
323 pub fn cache(msg: impl Into<Cow<'static, str>>) -> Self {
324 Self::with(AppErrorKind::Cache, msg)
325 }
326}