diff rust/hg-core/src/errors.rs @ 46506:39e9407820ac

rust: Introduce an `HgError` enum for common error cases Differential Revision: https://phab.mercurial-scm.org/D9892
author Simon Sapin <simon.sapin@octobus.net>
date Wed, 27 Jan 2021 13:19:49 +0100
parents
children 43d63979a75e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-core/src/errors.rs	Wed Jan 27 13:19:49 2021 +0100
@@ -0,0 +1,111 @@
+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
+    ///
+    /// 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),
+}
+
+/// Details about where an I/O error happened
+#[derive(Debug, derive_more::From)]
+pub enum IoErrorContext {
+    /// A filesystem operation returned `std::io::Error`
+    #[from]
+    File(std::path::PathBuf),
+    /// `std::env::current_dir` returned `std::io::Error`
+    CurrentDir,
+}
+
+impl HgError {
+    pub fn corrupted(explanation: impl Into<String>) -> Self {
+        HgError::CorruptedRepository(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)
+            }
+        }
+    }
+}
+
+// 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"),
+        }
+    }
+}
+
+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),
+        }
+    }
+}