tag2upload_service_manager/
error.rs1
2use crate::prelude::*;
3use std::backtrace::Backtrace;
4
5#[derive(Error, Debug)]
6pub enum ProcessingError {
7 #[error("problem at forge: {0:#}")]
8 Forge(anyhow::Error),
9
10 #[error("local problem {0:#}")]
11 Local(anyhow::Error),
12
13 #[error("{0}")]
14 Mismatch(#[from] MismatchError),
15
16 #[error("{0}")]
17 Internal(#[from] InternalError),
18}
19
20pub struct TaskWorkComplete {}
26
27#[derive(Debug, From)]
29pub enum QuitTask {
30 Crashed(InternalError),
31 Shutdown(ShuttingDown),
32}
33
34pub type TaskResult = Result<TaskWorkComplete, QuitTask>;
35
36#[derive(Error, Debug)]
37pub enum StartupError {
38 #[error("failed to parse/deserialise t2u configuration: {0}")]
39 ParseConfig(figment::Error),
40
41 #[error("invalid configuration")]
42 InvalidConfig,
43
44 #[error("problem with temp directory: {0:#}")]
45 TempDir(AE),
46
47 #[error("failed to initialise http client: {0}")]
48 Reqwest(#[from] reqwest::Error),
49
50 #[error("failed to initialise DNS resolver: {0}")]
51 Resolver(#[from] hickory_resolver::ResolveError),
52
53 #[error("failed to initialise Rocket http server: {0}")]
54 Rocket(#[from] rocket::Error),
55
56 #[error("failed to initialise worker listener(s): {0:#}")]
57 WorkerListener(AE),
58
59 #[error("failed to open database: {0}")]
60 DbOpen(rusqlite::Error),
61
62 #[error("failed to idempotently initialise db schema: {0}")]
63 ExecuteSchema(rusqlite::Error),
64
65 #[error("failed to access database during startup: {0}")]
66 DbAccess(rusqlite::Error),
67
68 #[error("failed to initialise logging: {0:#}")]
69 Logging(AE),
70
71 #[error("failed to initialise templates from explcit dir: {0:#}")]
72 Templates(#[source] AE),
73
74 #[error("internal error during startup: {0}")]
75 Internal(#[from] InternalError),
76}
77
78#[derive(Error, Debug)]
79pub enum NotForUsReason {
80 #[error("unexpected/incomplete JSON payload: {0}")]
81 DeserFailed(String),
82
83 #[error("webhook event is tag being deleted")]
84 TagIsBeingDeleted,
85
86 #[error("tag name has unexpected syntax (not DEP-14)")]
87 TagNameUnexpectedSyntax,
88
89 #[error("tag name doesn't start with our DEP-14 distro name")]
90 TagNameNotOurDistro,
91
92 #[error("tag message has only summary/title, no body")]
93 TagWithoutMessageBody,
94
95 #[error("no [dgit please-upload] instruction")]
96 NoPleaseUpload,
97
98 #[error("missing [dgit source= ] information (old git-debpush?)")]
99 MissingSource,
100
101 #[error("missing [dgit version= ] information (old git-debpush?)")]
102 MissingVersion,
103
104 #[error("no [dgit distro=...] for the distro we support")]
105 MetaNotOurDistro,
106
107 #[error("bad metadata item: {item:?}: {error}")]
108 BadMetadataItem { item: String, error: MetadataItemError, },
109
110 #[error("source package not mentioned in passlist")]
111 PackageNotPasslisted,
112
113 #[error("tag is too old ({age} > {max})")]
114 TagTooOld {
115 age: humantime::Duration,
116 max: humantime::Duration,
117 },
118
119 #[error("tag is too new by {skew} (> {max})")]
120 TagTooNew {
121 skew: humantime::Duration,
122 max: humantime::Duration,
123 },
124}
125
126#[derive(Error, Debug)]
127pub enum WebError {
128 #[error("misconfigured (or malfunctioning) web hook: {0:#}")]
129 MisconfiguredWebhook(AE),
130 #[error("{0}")]
131 InternalError(#[from] InternalError),
132 #[error("tag is not for us: {0}")]
133 NotForUs(#[from] NotForUsReason),
134 #[error("page not found at this URL: {0:#}")]
135 PageNotFoundHere(AE),
136 #[error("{0}")]
137 DisallowedClient(#[from] dns::DisallowedClient),
138 #[error("service throttled: {0}")]
139 Throttled(String),
140}
141
142#[derive(Error, Debug)]
143pub enum OracleTaskError {
144 #[error("oracle disconnected")]
145 Disconnected,
146
147 #[error("I/O error on oracle connection")]
148 Io(Arc<io::Error>),
149
150 #[error("malformed message received: {0}")]
151 BadMessage(#[from] o2m_support::BadMessage),
152
153 #[error("peer reported protocol violation: {0}")]
154 PeerReportedProtocolViolation(#[from] o2m_messages::ProtocolViolation),
155
156 #[error("peer requested protocol version {}; not supported", .0.version)]
157 UnsupportedVersion(o2m_messages::VersionRequest),
158
159 #[error("maximum line length (`limits.o2m_line`) exceeded")]
160 MaxLineLengthExceeded,
161
162 #[error("{0}")]
163 InternalError(#[from] InternalError),
164
165 #[error("shutting down")]
166 Shutdown(ShuttingDown),
167
168 #[error("restarting old worker")]
169 RestartingWorker,
170}
171
172
173#[derive(Error, Debug)]
174#[error("mismatch (possible race): {what}: earlier={earlier:?} now={now:?}")]
175pub struct MismatchError {
176 what: String,
177 earlier: String,
178 now: String,
179}
180
181impl MismatchError {
184 pub fn check<'t, T: Display + Eq>(
185 what: impl Display,
186 earlier: &'t T,
187 now: &'t T,
188 ) -> Result<&'t T, MismatchError> {
189 if earlier == now {
190 Ok(earlier)
191 } else {
192 Err(MismatchError {
193 what: what.to_string(),
194 earlier: earlier.to_string(),
195 now: now.to_string(),
196 })
197 }
198 }
199}
200
201impl WebError {
202 fn http_status(&self) -> rocket::http::Status {
203 use rocket::http::Status as S;
204 match self {
205 WE::MisconfiguredWebhook(_) => S::BadRequest,
206 WE::InternalError(_) => S::InternalServerError,
207 WE::PageNotFoundHere(_) => S::NotFound,
208 WE::NotForUs { .. } => S::Ok,
209 WE::Throttled(..) => S::ServiceUnavailable,
210 WE::DisallowedClient(dc) => dc.http_status(),
211 }
212 }
213}
214
215impl<'r, 'o: 'r> rocket::response::Responder<'r, 'o> for WebError {
216 fn respond_to(
217 self,
218 _req: &'r rocket::request::Request<'_>,
219 ) -> rocket::response::Result<'o> {
220 let msg = format!("error: {self}");
221 Ok(rocket::response::Response::build()
222 .status(self.http_status())
223 .sized_body(None, Cursor::new(msg))
224 .header(rocket::http::ContentType::Text)
225 .finalize())
226 }
227}
228
229#[derive(Error, Debug, Clone)]
236#[error("internal error: {:#}", self.0.ae)]
238pub struct InternalError(Arc<InternalErrorPayload>);
239
240#[derive(derive_more::Display, Debug)]
241#[display("{ae:#}\n{backtrace}")]
242struct InternalErrorPayload {
243 ae: anyhow::Error,
244 backtrace: Backtrace,
245}
246
247macro_rules! internal { { $fmt:literal $($rest:tt)* } => {
248 IE::new(anyhow!($fmt $($rest)*))
249} }
250
251impl InternalError {
252 #[track_caller]
253 pub fn new(ae: AE) -> InternalError {
254 IE::new_inner(ae, Backtrace::force_capture())
255 }
256
257 pub fn new_without_backtrace(ae: AE) -> InternalError {
258 IE::new_inner(ae, Backtrace::disabled())
259 }
260
261 pub fn new_quiet(ae: AE) -> InternalError {
265 let backtrace = Backtrace::disabled();
266 error!("internal error - but carrying on! {ae:#}");
267 let pl = InternalErrorPayload { ae, backtrace };
268 InternalError(pl.into())
269 }
270
271 pub fn note_only(self) {
275 }
277
278 #[track_caller]
279 fn new_inner(ae: AE, backtrace: Backtrace) -> InternalError {
280 let pl = InternalErrorPayload { ae, backtrace };
281 let ie = InternalError(pl.into());
282
283 #[cfg(test)]
284 test::internal_error_hook(&ie);
285
286 globals().state.send_modify(|state| {
287 state.note_internal_error_inner(ie.clone())
288 });
289 ie
290 }
291
292 #[cfg(test)]
293 pub fn display_with_backtrace(&self) -> &impl Display { &self.0 }
294}
295
296impl global::State {
297 fn note_internal_error_inner(&mut self, ie: InternalError) {
298 let pl = &ie.0;
299 let store = match &mut self.shutdown_reason {
300 reason @ None => {
301 error!("internal error, will shut down! {pl}");
302 Some(reason)
303 }
304 reason @ Some(Ok(ShuttingDown)) => {
305 error!("internal error during shutdown! {pl}");
306 Some(reason)
307 }
308 _already @ Some(Err(_)) => {
309 info!("additional internal error! {pl}");
310 None
311 }
312 };
313 if let Some(store) = store {
314 *store = Some(Err(ie));
315 }
316 }
317}
318
319pub trait IntoInternal: Sized {
320 #[track_caller]
321 fn into_internal(self, what: impl Display)
323 -> InternalError;
324}
325impl<E> IntoInternal for E where anyhow::Error: From<E> {
326 #[track_caller]
327 fn into_internal(self, what: impl Display)
328 -> InternalError {
329 let what = what.to_string();
330 IE::new(anyhow::Error::from(self).context(what))
331 }
332}
333
334#[ext(IntoInternalResult)]
335pub impl<T, E: IntoInternal> Result<T, E> {
336 #[track_caller]
337 fn into_internal(self, what: impl Display)
338 -> Result<T, InternalError> {
339 self.map_err(move |e| e.into_internal(what))
340 }
341}
342#[ext(IntoInternalOption)]
343pub impl<T> Option<T> {
344 #[track_caller]
345 fn ok_or_internal(self, what: impl Display)
346 -> Result<T, InternalError> {
347 self.ok_or_else(|| IE::new(anyhow!(what.to_string())))
348 }
349}
350
351impl InternalError {
352 #[cfg(test)]
357 pub fn nyi(what: &str) -> InternalError {
358 let ae = anyhow!("not yet implemented")
363 .context(what.to_string());
364 IE::new_quiet(ae)
365 }
366}