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