errors.rs
211 lines
| 6.8 KiB
| application/rls-services+xml
|
RustLexer
|
r47340 | use crate::config::ConfigValueParseError; | ||
|
r48199 | use crate::exit_codes; | ||
|
r47167 | use std::fmt; | ||
/// Common error cases that can happen in many different APIs | ||||
|
r47340 | #[derive(Debug, derive_more::From)] | ||
|
r47167 | pub enum HgError { | ||
IoError { | ||||
error: std::io::Error, | ||||
context: IoErrorContext, | ||||
}, | ||||
|
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. | ||||
|
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), | ||||
|
r47214 | |||
/// Operation cannot proceed for some other reason. | ||||
/// | ||||
|
r48199 | /// The message is a short explanation for users, not intended to be | ||
|
r47214 | /// machine-readable. | ||
|
r48199 | Abort { | ||
message: String, | ||||
detailed_exit_code: exit_codes::ExitCode, | ||||
}, | ||||
|
r47340 | |||
/// A configuration value is not in the expected syntax. | ||||
/// | ||||
/// These errors can happen in many places in the code because values are | ||||
/// parsed lazily as the file-level parser does not know the expected type | ||||
/// and syntax of each value. | ||||
#[from] | ||||
ConfigValueParseError(ConfigValueParseError), | ||||
|
r50069 | |||
/// Censored revision data. | ||||
CensoredNodeError, | ||||
|
r47167 | } | ||
/// Details about where an I/O error happened | ||||
|
r47341 | #[derive(Debug)] | ||
|
r47167 | pub enum IoErrorContext { | ||
|
r48584 | /// `std::fs::metadata` | ||
ReadingMetadata(std::path::PathBuf), | ||||
|
r47341 | ReadingFile(std::path::PathBuf), | ||
WritingFile(std::path::PathBuf), | ||||
RemovingFile(std::path::PathBuf), | ||||
RenamingFile { | ||||
from: std::path::PathBuf, | ||||
to: std::path::PathBuf, | ||||
}, | ||||
|
r47474 | /// `std::fs::canonicalize` | ||
CanonicalizingPath(std::path::PathBuf), | ||||
|
r47212 | /// `std::env::current_dir` | ||
|
r47167 | CurrentDir, | ||
|
r47212 | /// `std::env::current_exe` | ||
CurrentExe, | ||||
|
r47167 | } | ||
impl HgError { | ||||
pub fn corrupted(explanation: impl Into<String>) -> Self { | ||||
|
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 | ||||
|
r47167 | HgError::CorruptedRepository(explanation.into()) | ||
} | ||||
|
r47190 | |||
pub fn unsupported(explanation: impl Into<String>) -> Self { | ||||
HgError::UnsupportedFeature(explanation.into()) | ||||
} | ||||
|
r48199 | |||
pub fn abort( | ||||
explanation: impl Into<String>, | ||||
exit_code: exit_codes::ExitCode, | ||||
) -> Self { | ||||
HgError::Abort { | ||||
message: explanation.into(), | ||||
detailed_exit_code: exit_code, | ||||
} | ||||
|
r47214 | } | ||
|
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 { | ||||
|
r48199 | HgError::Abort { message, .. } => write!(f, "{}", message), | ||
|
r47167 | HgError::IoError { error, context } => { | ||
|
r47465 | write!(f, "abort: {}: {}", context, error) | ||
|
r47167 | } | ||
HgError::CorruptedRepository(explanation) => { | ||||
|
r47469 | write!(f, "abort: {}", explanation) | ||
|
r47167 | } | ||
HgError::UnsupportedFeature(explanation) => { | ||||
write!(f, "unsupported feature: {}", explanation) | ||||
} | ||||
|
r50069 | HgError::CensoredNodeError => { | ||
write!(f, "encountered a censored node") | ||||
} | ||||
|
r47555 | HgError::ConfigValueParseError(error) => error.fmt(f), | ||
|
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 { | ||||
|
r48584 | IoErrorContext::ReadingMetadata(path) => { | ||
write!(f, "when reading metadata of {}", path.display()) | ||||
} | ||||
|
r47341 | IoErrorContext::ReadingFile(path) => { | ||
write!(f, "when reading {}", path.display()) | ||||
} | ||||
IoErrorContext::WritingFile(path) => { | ||||
write!(f, "when writing {}", path.display()) | ||||
} | ||||
IoErrorContext::RemovingFile(path) => { | ||||
write!(f, "when removing {}", path.display()) | ||||
} | ||||
IoErrorContext::RenamingFile { from, to } => write!( | ||||
f, | ||||
"when renaming {} to {}", | ||||
from.display(), | ||||
to.display() | ||||
), | ||||
|
r47474 | IoErrorContext::CanonicalizingPath(path) => { | ||
write!(f, "when canonicalizing {}", path.display()) | ||||
} | ||||
|
r47465 | IoErrorContext::CurrentDir => { | ||
write!(f, "error getting current working directory") | ||||
} | ||||
IoErrorContext::CurrentExe => { | ||||
write!(f, "error getting current executable") | ||||
} | ||||
|
r47167 | } | ||
} | ||||
} | ||||
pub trait IoResultExt<T> { | ||||
|
r47341 | /// Annotate a possible I/O error as related to a reading a file at the | ||
/// given path. | ||||
|
r47167 | /// | ||
|
r47341 | /// This allows printing something like “File not found when reading | ||
/// example.txt” instead of just “File not found”. | ||||
|
r47167 | /// | ||
/// Converts a `Result` with `std::io::Error` into one with `HgError`. | ||||
|
r47341 | fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>; | ||
|
r49246 | fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError>; | ||
|
r47341 | fn with_context( | ||
self, | ||||
context: impl FnOnce() -> IoErrorContext, | ||||
) -> Result<T, HgError>; | ||||
|
r47167 | } | ||
impl<T> IoResultExt<T> for std::io::Result<T> { | ||||
|
r47341 | fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> { | ||
self.with_context(|| IoErrorContext::ReadingFile(path.to_owned())) | ||||
} | ||||
|
r49246 | fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError> { | ||
self.with_context(|| IoErrorContext::WritingFile(path.to_owned())) | ||||
} | ||||
|
r47341 | fn with_context( | ||
self, | ||||
context: impl FnOnce() -> IoErrorContext, | ||||
) -> Result<T, HgError> { | ||||
|
r47167 | self.map_err(|error| HgError::IoError { | ||
error, | ||||
|
r47341 | context: context(), | ||
|
r47167 | }) | ||
} | ||||
} | ||||
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), | ||||
} | ||||
} | ||||
} | ||||