errors.rs
133 lines
| 4.3 KiB
| application/rls-services+xml
|
RustLexer
Simon Sapin
|
r47167 | use std::fmt; | ||
/// Common error cases that can happen in many different APIs | ||||
#[derive(Debug)] | ||||
pub enum HgError { | ||||
IoError { | ||||
error: std::io::Error, | ||||
context: IoErrorContext, | ||||
}, | ||||
Simon Sapin
|
r47214 | /// A file under `.hg/` normally only written by Mercurial is not in the | ||
/// expected format. This indicates a bug in Mercurial, filesystem | ||||
/// corruption, or hardware failure. | ||||
Simon Sapin
|
r47167 | /// | ||
/// The given string is a short explanation for users, not intended to be | ||||
/// machine-readable. | ||||
CorruptedRepository(String), | ||||
/// The respository or requested operation involves a feature not | ||||
/// supported by the Rust implementation. Falling back to the Python | ||||
/// implementation may or may not work. | ||||
/// | ||||
/// The given string is a short explanation for users, not intended to be | ||||
/// machine-readable. | ||||
UnsupportedFeature(String), | ||||
Simon Sapin
|
r47214 | |||
/// Operation cannot proceed for some other reason. | ||||
/// | ||||
/// The given string is a short explanation for users, not intended to be | ||||
/// machine-readable. | ||||
Abort(String), | ||||
Simon Sapin
|
r47167 | } | ||
/// Details about where an I/O error happened | ||||
#[derive(Debug, derive_more::From)] | ||||
pub enum IoErrorContext { | ||||
Simon Sapin
|
r47212 | /// A filesystem operation for the given file | ||
Simon Sapin
|
r47167 | #[from] | ||
File(std::path::PathBuf), | ||||
Simon Sapin
|
r47212 | /// `std::env::current_dir` | ||
Simon Sapin
|
r47167 | CurrentDir, | ||
Simon Sapin
|
r47212 | /// `std::env::current_exe` | ||
CurrentExe, | ||||
Simon Sapin
|
r47167 | } | ||
impl HgError { | ||||
pub fn corrupted(explanation: impl Into<String>) -> Self { | ||||
Simon Sapin
|
r47172 | // TODO: capture a backtrace here and keep it in the error value | ||
// to aid debugging? | ||||
// https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html | ||||
Simon Sapin
|
r47167 | HgError::CorruptedRepository(explanation.into()) | ||
} | ||||
Simon Sapin
|
r47190 | |||
pub fn unsupported(explanation: impl Into<String>) -> Self { | ||||
HgError::UnsupportedFeature(explanation.into()) | ||||
} | ||||
Simon Sapin
|
r47214 | pub fn abort(explanation: impl Into<String>) -> Self { | ||
HgError::Abort(explanation.into()) | ||||
} | ||||
Simon Sapin
|
r47167 | } | ||
// TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? | ||||
impl fmt::Display for HgError { | ||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
match self { | ||||
HgError::IoError { error, context } => { | ||||
write!(f, "{}: {}", error, context) | ||||
} | ||||
HgError::CorruptedRepository(explanation) => { | ||||
write!(f, "corrupted repository: {}", explanation) | ||||
} | ||||
HgError::UnsupportedFeature(explanation) => { | ||||
write!(f, "unsupported feature: {}", explanation) | ||||
} | ||||
Simon Sapin
|
r47214 | HgError::Abort(explanation) => explanation.fmt(f), | ||
Simon Sapin
|
r47167 | } | ||
} | ||||
} | ||||
// TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? | ||||
impl fmt::Display for IoErrorContext { | ||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
match self { | ||||
IoErrorContext::File(path) => path.display().fmt(f), | ||||
IoErrorContext::CurrentDir => f.write_str("current directory"), | ||||
Simon Sapin
|
r47212 | IoErrorContext::CurrentExe => f.write_str("current executable"), | ||
Simon Sapin
|
r47167 | } | ||
} | ||||
} | ||||
pub trait IoResultExt<T> { | ||||
/// Annotate a possible I/O error as related to a file at the given path. | ||||
/// | ||||
/// This allows printing something like “File not found: example.txt” | ||||
/// instead of just “File not found”. | ||||
/// | ||||
/// Converts a `Result` with `std::io::Error` into one with `HgError`. | ||||
fn for_file(self, path: &std::path::Path) -> Result<T, HgError>; | ||||
} | ||||
impl<T> IoResultExt<T> for std::io::Result<T> { | ||||
fn for_file(self, path: &std::path::Path) -> Result<T, HgError> { | ||||
self.map_err(|error| HgError::IoError { | ||||
error, | ||||
context: IoErrorContext::File(path.to_owned()), | ||||
}) | ||||
} | ||||
} | ||||
pub trait HgResultExt<T> { | ||||
/// Handle missing files separately from other I/O error cases. | ||||
/// | ||||
/// Wraps the `Ok` type in an `Option`: | ||||
/// | ||||
/// * `Ok(x)` becomes `Ok(Some(x))` | ||||
/// * An I/O "not found" error becomes `Ok(None)` | ||||
/// * Other errors are unchanged | ||||
fn io_not_found_as_none(self) -> Result<Option<T>, HgError>; | ||||
} | ||||
impl<T> HgResultExt<T> for Result<T, HgError> { | ||||
fn io_not_found_as_none(self) -> Result<Option<T>, HgError> { | ||||
match self { | ||||
Ok(x) => Ok(Some(x)), | ||||
Err(HgError::IoError { error, .. }) | ||||
if error.kind() == std::io::ErrorKind::NotFound => | ||||
{ | ||||
Ok(None) | ||||
} | ||||
Err(other_error) => Err(other_error), | ||||
} | ||||
} | ||||
} | ||||