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}