use std::error::Error as StdError;
use std::fmt;
pub struct Error(Box<dyn StdError + Send + Sync + 'static>);
pub type Result<T, E = Error> = std::result::Result<T, E>;
impl Error {
#[must_use]
pub fn is<E: StdError + 'static>(&self) -> bool {
self.0.is::<E>()
}
#[must_use]
pub fn downcast_ref<E: StdError + 'static>(&self) -> Option<&E> {
self.0.downcast_ref()
}
pub fn downcast<E: StdError + 'static>(self) -> Result<E, Self> {
self.0.downcast().map(|error| *error).map_err(Self)
}
#[must_use]
pub fn get_ref(&self) -> &(dyn StdError + Send + Sync + 'static) {
&*self.0
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0.source()
}
}
macro_rules! from_domain_errors {
($($(#[$attr:meta])* $error:ty),+ $(,)?) => {
$(
$(#[$attr])*
impl From<$error> for Error {
fn from(error: $error) -> Self {
Self(Box::new(error))
}
}
)+
};
}
impl<E: StdError + Send + Sync + 'static> From<crate::commit::SignError<E>> for Error {
fn from(error: crate::commit::SignError<E>) -> Self {
Self(Box::new(error))
}
}
from_domain_errors! {
crate::attributes::Error,
crate::attributes::LoadError,
crate::blame::Error,
std::io::Error,
crate::checkout::Error,
crate::checkout::Failure,
crate::clone::CloneError,
crate::clone::CloneFailure,
crate::clone::ClonePlanError,
crate::clone::CloneTransferError,
crate::CommitError,
crate::config::ConfigError,
crate::config::EditError,
crate::config::InvalidValue,
crate::config::ResolveError,
crate::config::ResolveFailure,
crate::content_diff::ContentReadError,
crate::content_diff::DiffError,
crate::fetch::FetchError,
crate::fetch::FetchFinishError,
crate::fetch::FetchFinishFailure,
crate::fetch::FetchPlanError,
crate::fetch::FetchShallowError,
crate::fetch::FetchUpdateError,
crate::fetch::FetchWorkflowError,
crate::fetch::RetainedFetchError,
crate::fetch::RetainedFetchFinishError,
crate::HistoryError,
crate::ignore::Error,
crate::ignore::LoadError,
crate::index::Error,
crate::index::SparseError,
crate::index::StorageError,
crate::index::WriteTreeError,
crate::tree::TreeWriteError,
crate::tree_diff::Error,
crate::locks::CleanupFailure,
crate::loose::Error,
crate::patch::Error,
crate::patch::PatternError,
crate::ObjectFormatError,
crate::ParseObjectIdError,
crate::ParseObjectIdPrefixError,
crate::ObjectReadError,
crate::ReadBlobError,
crate::ReadCommitError,
crate::ReadTagError,
crate::ReadTreeError,
crate::ResolvePrefixError,
crate::pack::IndexRebuildError,
crate::PackWriteError,
crate::PeelError,
crate::PeelFailure,
crate::push::PushError,
crate::push::PushFailure,
crate::refs::InvalidRefName,
crate::refs::PackRefsError,
crate::refs::ReferenceError,
crate::refs::ReflogInterpretationError,
crate::refs::TransactionError,
crate::refs::reftable::Error,
crate::remote::ConfiguredRefspecError,
crate::remote::ConfiguredRemoteError,
crate::remote::ConfiguredUrlError,
crate::remote::CredentialError,
crate::remote::EndpointError,
crate::remote::InvalidRemoteName,
crate::remote::MappingError,
crate::remote::RefspecError,
crate::remote::RefspecsError,
crate::remote::RemoteEditError,
crate::remote::RemoteError,
crate::remote::RemoteNameError,
crate::remote::UrlError,
crate::remote::UserUrlError,
crate::remote::UserUrlParseError,
crate::remote::UserUrlPathError,
crate::ColocationError,
crate::CreateWorktreeError,
crate::InitError,
crate::OpenError,
crate::OperationCleanupError,
crate::OperationError,
crate::ReflogPolicyError,
crate::ShallowError,
crate::WorktreeAdminError,
crate::WorktreeError,
crate::retention::ExpireCause,
crate::retention::ExpireFailure,
crate::retention::MaintenanceCause,
crate::retention::MaintenanceFailure,
crate::retention::PruneCause,
crate::retention::PruneFailure,
crate::retention::ReflogExpiryError,
crate::retention::RepackError,
crate::retention::RetentionError,
crate::retention::RetireCause,
crate::retention::RetireFailure,
crate::revision::ParseRevisionError,
crate::revision::RevisionError,
crate::rewrites::Error,
crate::status::Error,
crate::TagError,
#[cfg(feature = "http")]
crate::transport::http::HttpError,
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
crate::transport::ssh::SshError,
crate::TreeError,
crate::TreeCompareError,
crate::workflow::TransferError,
crate::workflow::CloneError,
crate::repo::Error,
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
use crate::refs::RefName;
#[test]
fn preserves_display_source_chain_and_type() {
let directory = tempfile::tempdir().unwrap();
let open = crate::Repository::open(directory.path().join("missing")).unwrap_err();
let display = open.to_string();
let cause = open.source().map(ToString::to_string);
let error = Error::from(open);
assert_eq!(error.to_string(), display);
assert_eq!(error.source().map(ToString::to_string), cause);
assert!(error.is::<crate::OpenError>());
assert!(error.downcast_ref::<crate::TreeError>().is_none());
let error = error.downcast::<crate::TreeError>().unwrap_err();
assert!(error.downcast::<crate::OpenError>().is_ok());
}
#[test]
fn question_mark_crosses_modules() {
fn parse(name: &str) -> Result<RefName> {
let _ = "HEAD".parse::<crate::revision::Revision>()?;
Ok(RefName::new(name)?)
}
let error = parse("refs/heads/a..b").unwrap_err();
assert!(error.is::<crate::refs::InvalidRefName>());
}
#[test]
fn lists_every_public_error_type() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let list = std::fs::read_to_string(root.join("error.rs")).unwrap();
let list = list.split("from_domain_errors! {").nth(1).unwrap();
let mut listed: Vec<String> = list
.split("#[cfg(test)]")
.next()
.unwrap()
.lines()
.map(|line| line.trim().trim_end_matches(',').to_owned())
.collect();
listed.push("crate::commit::SignError".to_owned());
let mut missing = Vec::new();
for (path, name) in public_error_types(&root) {
let module = path
.strip_prefix(&root)
.unwrap()
.components()
.next()
.unwrap();
let module = module.as_os_str().to_str().unwrap().trim_end_matches(".rs");
let position = listed.iter().position(|listed| match name.as_str() {
"Error" | "Failure" => {
listed.starts_with(&format!("crate::{module}::"))
&& listed.ends_with(&format!("::{name}"))
}
_ => listed.ends_with(&format!("::{name}")),
});
match position {
Some(position) => drop(listed.swap_remove(position)),
None => missing.push(format!("{}: {name}", path.display())),
}
}
assert_eq!(missing, Vec::<String>::new());
}
fn public_error_types(directory: &Path) -> Vec<(std::path::PathBuf, String)> {
let mut found = Vec::new();
for entry in std::fs::read_dir(directory).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
found.extend(public_error_types(&path));
continue;
}
let text = std::fs::read_to_string(&path).unwrap();
found.extend(scan(&text).into_iter().map(|name| (path.clone(), name)));
}
found
}
fn scan(text: &str) -> Vec<String> {
let mut names = Vec::new();
for derive in text.split("#[derive(").skip(1) {
let Some((list, rest)) = derive.split_once(")]") else {
continue;
};
let derives_error = list
.split(',')
.any(|name| matches!(name.trim(), "Error" | "thiserror::Error"));
let item = rest.lines().map(str::trim).find(|line| {
let line = line.strip_prefix("pub ").unwrap_or(line);
line.starts_with("enum ") || line.starts_with("struct ") || line.starts_with("pub(")
});
let public = item.and_then(|line| {
["pub enum ", "pub struct "]
.iter()
.find_map(|prefix| line.strip_prefix(prefix))
});
if let (true, Some(rest)) = (derives_error, public) {
let end = rest
.find(|c: char| !c.is_alphanumeric() && c != '_')
.unwrap_or(rest.len());
names.push(rest[..end].to_owned());
}
}
names
}
#[test]
fn scan_finds_multiline_and_single_line_derives() {
let text = "#[derive(\n Debug,\n thiserror::Error,\n)]\n#[non_exhaustive]\n\
pub enum Multi {}\n#[derive(Debug, Error)]\n/// Doc.\npub struct Single;\n\
#[derive(Debug, thiserror::Error)]\npub(crate) enum Private {}\n\
#[derive(Debug)]\npub struct Plain;\n";
assert_eq!(scan(text), ["Multi", "Single"]);
}
}