Mercurial > public > mercurial-scm > hg-stable
view rust/hg-core/src/errors.rs @ 46637:bc08c2331f99
rust: Add a `ConfigValueParseError` variant to common errors
Configuration files are parsed into sections of key/value pairs when
they are read, but at that point values are still arbitrary bytes.
Only when a value is accessed by various parts of the code do we know
its expected type and syntax, so values are parsed at that point.
Let?s make a new error type for this latter kind of parsing error,
and add a variant to the common `HgError` so that most code can propagate it
without much boilerplate.
Differential Revision: https://phab.mercurial-scm.org/D10009
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Tue, 16 Feb 2021 15:22:20 +0100 |
parents | f031fe1c6ede |
children | 1f55cd5b292f |
line wrap: on
line source
use crate::config::ConfigValueParseError; use std::fmt; /// Common error cases that can happen in many different APIs #[derive(Debug, derive_more::From)] pub enum HgError { IoError { error: std::io::Error, context: IoErrorContext, }, /// 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. /// /// 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), /// Operation cannot proceed for some other reason. /// /// The given string is a short explanation for users, not intended to be /// machine-readable. Abort(String), /// 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), } /// Details about where an I/O error happened #[derive(Debug, derive_more::From)] pub enum IoErrorContext { /// A filesystem operation for the given file #[from] File(std::path::PathBuf), /// `std::env::current_dir` CurrentDir, /// `std::env::current_exe` CurrentExe, } impl HgError { pub fn corrupted(explanation: impl Into<String>) -> Self { // 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 HgError::CorruptedRepository(explanation.into()) } pub fn unsupported(explanation: impl Into<String>) -> Self { HgError::UnsupportedFeature(explanation.into()) } pub fn abort(explanation: impl Into<String>) -> Self { HgError::Abort(explanation.into()) } } // 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::Abort(explanation) => write!(f, "{}", explanation), HgError::IoError { error, context } => { write!(f, "{}: {}", error, context) } HgError::CorruptedRepository(explanation) => { write!(f, "corrupted repository: {}", explanation) } HgError::UnsupportedFeature(explanation) => { write!(f, "unsupported feature: {}", explanation) } HgError::ConfigValueParseError(ConfigValueParseError { origin: _, line: _, section, item, value, expected_type, }) => { // TODO: add origin and line number information, here and in // corresponding python code write!( f, "config error: {}.{} is not a {} ('{}')", String::from_utf8_lossy(section), String::from_utf8_lossy(item), expected_type, String::from_utf8_lossy(value) ) } } } } // 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"), IoErrorContext::CurrentExe => f.write_str("current executable"), } } } 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), } } }