view rust/hg-core/src/errors.rs @ 46505:a25033eb43b5

rhg: add limited support for the `config` sub-command Only with one argument and no flag. This is mostly for testing. Differential Revision: https://phab.mercurial-scm.org/D9972
author Simon Sapin <simon.sapin@octobus.net>
date Mon, 08 Feb 2021 23:41:58 +0100
parents f031fe1c6ede
children bc08c2331f99
line wrap: on
line source

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,
    },

    /// 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),
}

/// 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::IoError { error, context } => {
                write!(f, "{}: {}", error, context)
            }
            HgError::CorruptedRepository(explanation) => {
                write!(f, "corrupted repository: {}", explanation)
            }
            HgError::UnsupportedFeature(explanation) => {
                write!(f, "unsupported feature: {}", explanation)
            }
            HgError::Abort(explanation) => explanation.fmt(f),
        }
    }
}

// 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),
        }
    }
}