loco_rs/
errors.rs

1//! # Application Error Handling
2
3use axum::{
4    extract::rejection::JsonRejection,
5    http::{
6        header::{InvalidHeaderName, InvalidHeaderValue},
7        method::InvalidMethod,
8        StatusCode,
9    },
10};
11use lettre::{address::AddressError, transport::smtp};
12
13use crate::{controller::ErrorDetail, depcheck, validation::ModelValidationErrors};
14
15/*
16backtrace principles:
17- use a plan warapper variant with no 'from' conversion
18- hand-code "From" conversion and force capture there with 'bt', which
19  will wrap and create backtrace only if RUST_BACKTRACE=1.
20costs:
21- when RUST_BACKTRACE is not set, we don't pay for the capture and we dont pay for printing.
22
23 */
24impl From<serde_json::Error> for Error {
25    fn from(val: serde_json::Error) -> Self {
26        Self::JSON(val).bt()
27    }
28}
29
30#[derive(thiserror::Error, Debug)]
31pub enum Error {
32    #[error("{inner}\n{backtrace}")]
33    WithBacktrace {
34        inner: Box<Self>,
35        backtrace: Box<std::backtrace::Backtrace>,
36    },
37
38    #[error("{0}")]
39    Message(String),
40
41    #[error(
42        "error while running worker: no queue provider populated in context. Did you configure \
43         BackgroundQueue and connection details in `queue` in your config file?"
44    )]
45    QueueProviderMissing,
46
47    #[error("task not found: '{0}'")]
48    TaskNotFound(String),
49
50    #[error(transparent)]
51    Scheduler(#[from] crate::scheduler::Error),
52
53    #[error(transparent)]
54    Axum(#[from] axum::http::Error),
55
56    #[error(transparent)]
57    Tera(#[from] tera::Error),
58
59    #[error(transparent)]
60    JSON(serde_json::Error),
61
62    #[error(transparent)]
63    JsonRejection(#[from] JsonRejection),
64
65    #[error("cannot parse `{1}`: {0}")]
66    YAMLFile(#[source] serde_yaml::Error, String),
67
68    #[error(transparent)]
69    YAML(#[from] serde_yaml::Error),
70
71    #[error(transparent)]
72    EnvVar(#[from] std::env::VarError),
73
74    #[error("Error sending email: '{0}'")]
75    EmailSender(#[from] lettre::error::Error),
76
77    #[error("Error sending email (smtp): '{0}'")]
78    Smtp(#[from] smtp::Error),
79
80    #[error("Worker error: {0}")]
81    Worker(String),
82
83    #[error(transparent)]
84    IO(#[from] std::io::Error),
85
86    #[cfg(feature = "with-db")]
87    #[error(transparent)]
88    DB(#[from] sea_orm::DbErr),
89
90    #[error(transparent)]
91    ParseAddress(#[from] AddressError),
92
93    #[error("{0}")]
94    Hash(String),
95
96    // API
97    #[error("{0}")]
98    Unauthorized(String),
99
100    // API
101    #[error("not found")]
102    NotFound,
103
104    #[error("{0}")]
105    BadRequest(String),
106
107    #[error("")]
108    CustomError(StatusCode, ErrorDetail),
109
110    #[error("internal server error")]
111    InternalServerError,
112
113    #[error(transparent)]
114    InvalidHeaderValue(#[from] InvalidHeaderValue),
115
116    #[error(transparent)]
117    InvalidHeaderName(#[from] InvalidHeaderName),
118
119    #[error(transparent)]
120    InvalidMethod(#[from] InvalidMethod),
121
122    #[error(transparent)]
123    TaskJoinError(#[from] tokio::task::JoinError),
124
125    #[cfg(feature = "with-db")]
126    // Model
127    #[error(transparent)]
128    Model(#[from] crate::model::ModelError),
129
130    #[cfg(feature = "bg_redis")]
131    #[error(transparent)]
132    Redis(#[from] redis::RedisError),
133
134    #[cfg(any(feature = "bg_pg", feature = "bg_sqlt"))]
135    #[error(transparent)]
136    Sqlx(#[from] sqlx::Error),
137
138    #[error(transparent)]
139    Storage(#[from] crate::storage::StorageError),
140
141    #[error(transparent)]
142    Cache(#[from] crate::cache::CacheError),
143
144    #[cfg(debug_assertions)]
145    #[error(transparent)]
146    Generators(#[from] loco_gen::Error),
147
148    #[error(transparent)]
149    VersionCheck(#[from] depcheck::VersionCheckError),
150
151    #[error(transparent)]
152    SemVer(#[from] semver::Error),
153
154    #[error(transparent)]
155    Any(#[from] Box<dyn std::error::Error + Send + Sync>),
156
157    #[error(transparent)]
158    Validation(#[from] ModelValidationErrors),
159
160    #[error(transparent)]
161    AxumFormRejection(#[from] axum::extract::rejection::FormRejection),
162}
163
164impl Error {
165    pub fn wrap(err: impl std::error::Error + Send + Sync + 'static) -> Self {
166        Self::Any(Box::new(err)) //.bt()
167    }
168
169    pub fn msg(err: impl std::error::Error + Send + Sync + 'static) -> Self {
170        Self::Message(err.to_string()) //.bt()
171    }
172    #[must_use]
173    pub fn string(s: &str) -> Self {
174        Self::Message(s.to_string())
175    }
176    #[must_use]
177    pub fn bt(self) -> Self {
178        let backtrace = std::backtrace::Backtrace::capture();
179        match backtrace.status() {
180            std::backtrace::BacktraceStatus::Disabled
181            | std::backtrace::BacktraceStatus::Unsupported => self,
182            _ => Self::WithBacktrace {
183                inner: Box::new(self),
184                backtrace: Box::new(backtrace),
185            },
186        }
187    }
188}