tag2upload_service_manager/
error.rs
use crate::prelude::*;
use std::backtrace::Backtrace;
#[derive(Error, Debug)]
pub enum ProcessingError {
#[error("problem at forge: {0:#}")]
Forge(anyhow::Error),
#[error("local problem {0:#}")]
Local(anyhow::Error),
#[error("{0}")]
Mismatch(#[from] MismatchError),
#[error("{0}")]
Internal(#[from] InternalError),
}
pub struct TaskWorkComplete {}
#[derive(Debug, From)]
pub enum QuitTask {
Crashed(InternalError),
Shutdown(ShuttingDown),
}
pub type TaskResult = Result<TaskWorkComplete, QuitTask>;
#[derive(Error, Debug)]
pub enum StartupError {
#[error("failed to parse/deserialise t2u configuration: {0}")]
ParseConfig(figment::Error),
#[error("invalid configuration")]
InvalidConfig,
#[error("problem with temp directory: {0:#}")]
TempDir(AE),
#[error("failed to initialise http client: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("failed to initialise DNS resolver: {0}")]
Resolver(#[from] hickory_resolver::error::ResolveError),
#[error("failed to initialise Rocket http server: {0}")]
Rocket(#[from] rocket::Error),
#[error("failed to initialise worker listener(s): {0:#}")]
WorkerListener(AE),
#[error("failed to open database: {0}")]
DbOpen(rusqlite::Error),
#[error("failed to idempotently initialise db schema: {0}")]
ExecuteSchema(rusqlite::Error),
#[error("failed to access database during startup: {0}")]
DbAccess(rusqlite::Error),
#[error("failed to initialise logging: {0:#}")]
Logging(AE),
#[error("failed to initialise templates from explcit dir: {0:#}")]
Templates(#[source] AE),
#[error("internal error during startup: {0}")]
Internal(#[from] InternalError),
}
#[derive(Error, Debug)]
pub enum NotForUsReason {
#[error("webhook event is tag being deleted")]
TagIsBeingDeleted,
#[error("tag name has unexpected syntax (not DEP-14)")]
TagNameUnexpectedSyntax,
#[error("tag name doesn't start with our DEP-14 distro name")]
TagNameNotOurDistro,
#[error("tag message has only summary/title, no body")]
TagWithoutMessageBody,
#[error("no [dgit please-upload] instruction")]
NoPleaseUpload,
#[error("missing [dgit source= ] information (old git-debpush?)")]
MissingSource,
#[error("missing [dgit version= ] information (old git-debpush?)")]
MissingVersion,
#[error("no [dgit distro=...] for the distro we support")]
MetaNotOurDistro,
#[error("bad metadata item: {item:?}: {error}")]
BadMetadataItem { item: String, error: MetadataItemError, },
#[error("tag is too old ({age} > {max})")]
TagTooOld {
age: humantime::Duration,
max: humantime::Duration,
},
#[error("tag is too new by {skew} (> {max})")]
TagTooNew {
skew: humantime::Duration,
max: humantime::Duration,
},
}
#[derive(Error, Debug)]
pub enum WebError {
#[error("misconfigured web hook: {0:#}")]
MisconfiguredWebhook(AE),
#[error("web hook source is malfunctioning: {0:#}")]
MalfunctioningWebhook(AE),
#[error("network error: {0}")]
NetworkError(AE),
#[error("{0}")]
InternalError(#[from] InternalError),
#[error("tag is not for us: {0}")]
NotForUs(#[from] NotForUsReason),
}
#[derive(Error, Debug)]
pub enum OracleTaskError {
#[error("oracle disconnected")]
Disconnected,
#[error("I/O error on oracle connection")]
Io(Arc<io::Error>),
#[error("malformed message received: {0}")]
BadMessage(#[from] o2m_support::BadMessage),
#[error("peer reported protocol violation: {0}")]
PeerReportedProtocolViolation(#[from] o2m_messages::ProtocolViolation),
#[error("peer requested protocol version {}; not supported", .0.version)]
UnsupportedVersion(o2m_messages::Version),
#[error("maximum line length (`limits.o2m_line`) exceeded")]
MaxLineLengthExceeded,
#[error("{0}")]
InternalError(#[from] InternalError),
#[error("shutting down")]
Shutdown(ShuttingDown),
}
#[derive(Error, Debug)]
#[error("mismatch (possible race): {what}: earlier={earlier:?} now={now:?}")]
pub struct MismatchError {
what: String,
earlier: String,
now: String,
}
#[derive(Error, Debug, Clone)]
#[error("internal error: {:#}", self.0.ae)]
pub struct InternalError(Arc<InternalErrorPayload>);
#[derive(derive_more::Display, Debug)]
#[display(fmt = "{ae:#}\n{backtrace}")]
struct InternalErrorPayload {
ae: anyhow::Error,
backtrace: Backtrace,
}
macro_rules! internal { { $fmt:literal $($rest:tt)* } => {
IE::new(anyhow!($fmt $($rest)*))
} }
impl InternalError {
#[track_caller]
pub fn new(ae: AE) -> InternalError {
IE::new_inner(ae, Backtrace::force_capture())
}
pub fn new_without_backtrace(ae: AE) -> InternalError {
IE::new_inner(ae, Backtrace::disabled())
}
pub fn new_quiet(ae: AE) -> InternalError {
let backtrace = Backtrace::disabled();
error!("internal error - NYI, carrying on! {ae:#}");
let pl = InternalErrorPayload { ae, backtrace };
InternalError(pl.into())
}
pub fn note_only(self) {
}
#[track_caller]
fn new_inner(ae: AE, backtrace: Backtrace) -> InternalError {
let pl = InternalErrorPayload { ae, backtrace };
let ie = InternalError(pl.into());
#[cfg(test)]
test::internal_error_hook(&ie);
globals().state.send_modify(|state| {
state.note_internal_error_inner(ie.clone())
});
ie
}
#[cfg(test)]
pub fn display_with_backtrace(&self) -> &impl Display { &self.0 }
}
impl global::State {
fn note_internal_error_inner(&mut self, ie: InternalError) {
let pl = &ie.0;
let store = match &mut self.shutdown_reason {
reason @ None => {
error!("internal error, will shut down! {pl}");
Some(reason)
}
reason @ Some(Ok(ShuttingDown)) => {
error!("internal error during shutdown! {pl}");
Some(reason)
}
_already @ Some(Err(_)) => {
info!("additional internal error! {pl}");
None
}
};
if let Some(store) = store {
*store = Some(Err(ie));
}
}
}
pub trait IntoInternal: Sized {
#[track_caller]
fn into_internal(self, what: impl Display)
-> InternalError;
}
impl<E> IntoInternal for E where anyhow::Error: From<E> {
#[track_caller]
fn into_internal(self, what: impl Display)
-> InternalError {
let what = what.to_string();
IE::new(anyhow::Error::from(self).context(what))
}
}
impl MismatchError {
pub fn check<'t, T: Display + Eq>(
what: impl Display,
earlier: &'t T,
now: &'t T,
) -> Result<&'t T, MismatchError> {
if earlier == now {
Ok(earlier)
} else {
Err(MismatchError {
what: what.to_string(),
earlier: earlier.to_string(),
now: now.to_string(),
})
}
}
}
#[ext(IntoInternalResult)]
pub impl<T, E: IntoInternal> Result<T, E> {
#[track_caller]
fn into_internal(self, what: impl Display)
-> Result<T, InternalError> {
self.map_err(move |e| e.into_internal(what))
}
}
#[ext(IntoInternalOption)]
pub impl<T> Option<T> {
#[track_caller]
fn ok_or_internal(self, what: impl Display)
-> Result<T, InternalError> {
self.ok_or_else(|| IE::new(anyhow!(what.to_string())))
}
}
impl InternalError {
#[cfg(test)]
pub fn nyi(what: &str) -> InternalError {
let ae = anyhow!("not yet implemented")
.context(what.to_string());
IE::new_quiet(ae)
}
}
impl WebError {
fn http_status(&self) -> rocket::http::Status {
use rocket::http::Status as S;
match self {
WE::MisconfiguredWebhook(_) |
WE::MalfunctioningWebhook(_) => S::BadRequest,
WE::InternalError(_) => S::InternalServerError,
WE::NetworkError(_) => S::ServiceUnavailable,
WE::NotForUs(_) => S::Ok,
}
}
}
impl<'r, 'o: 'r> rocket::response::Responder<'r, 'o> for WebError {
fn respond_to(
self,
_req: &'r rocket::request::Request<'_>,
) -> rocket::response::Result<'o> {
let msg = format!("error: {self}");
Ok(rocket::response::Response::build()
.status(self.http_status())
.sized_body(None, Cursor::new(msg))
.header(rocket::http::ContentType::Text)
.finalize())
}
}