return a concrete error type from try_main

Also adds a utility for formatting an error message in addition to all
of its sources, i.e. errors that came before it that led to the current
error.

This helps us to provide better error messages to users by including
more information: both our own error message and all of the underlying
error messages, if any, instead of only one or the other.
This commit is contained in:
Charles Hall 2024-04-19 15:16:55 -07:00
parent 4407e15d78
commit 49660b9e39
No known key found for this signature in database
GPG key ID: 7B8E0645816E07CF
2 changed files with 89 additions and 9 deletions

70
src/error.rs Normal file
View file

@ -0,0 +1,70 @@
//! Error handling facilities
use std::{fmt, iter};
use thiserror::Error;
/// Formats an [`Error`][0] and its [`source`][1]s with a separator
///
/// [0]: std::error::Error
/// [1]: std::error::Error::source
pub(crate) struct DisplayWithSources<'a> {
/// The error (and its sources) to write
pub(crate) error: &'a dyn std::error::Error,
/// Separator to write between the original error and subsequent sources
pub(crate) infix: &'static str,
}
impl fmt::Display for DisplayWithSources<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)?;
let mut source = self.error.source();
source
.into_iter()
.chain(iter::from_fn(|| {
source = source.and_then(std::error::Error::source);
source
}))
.try_for_each(|source| write!(f, "{}{source}", self.infix))
}
}
/// Top-level errors
// Missing docs are allowed here since that kind of information should be
// encoded in the error messages themselves anyway.
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub(crate) enum Main {
#[error(
"the `{0}` environment variable must either be set to a configuration \
file path or set to an empty string to force configuration through \
environment variables"
)]
ConfigPathUnset(&'static str),
#[error("invalid configuration")]
ConfigInvalid(#[from] figment::Error),
// Upstream's documentation on what this error means is very sparse
#[error("opentelemetry error")]
Otel(#[from] opentelemetry::trace::TraceError),
#[error("invalid log filter syntax")]
EnvFilter(#[from] tracing_subscriber::filter::ParseError),
#[error("failed to install global default tracing subscriber")]
SetSubscriber(#[from] tracing::subscriber::SetGlobalDefaultError),
// Upstream's documentation on what this error means is very sparse
#[error("tracing_flame error")]
TracingFlame(#[from] tracing_flame::Error),
#[error("failed to load or create the database")]
DatabaseError(#[source] crate::utils::error::Error),
#[error("failed to serve requests")]
Serve(#[source] std::io::Error),
}