Patchwork D9892: rust: Introduce an `HgError` enum for common error cases

login
register
mail settings
Submitter phabricator
Date Jan. 27, 2021, 9:13 p.m.
Message ID <differential-rev-PHID-DREV-zizq3nql6mknidnp4cty-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/48203/
State Superseded
Headers show

Comments

phabricator - Jan. 27, 2021, 9:13 p.m.
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D9892

AFFECTED FILES
  rust/hg-core/src/errors.rs
  rust/hg-core/src/lib.rs

CHANGE DETAILS




To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs
--- a/rust/hg-core/src/lib.rs
+++ b/rust/hg-core/src/lib.rs
@@ -5,6 +5,7 @@ 
 // GNU General Public License version 2 or any later version.
 mod ancestors;
 pub mod dagops;
+pub mod errors;
 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
 mod dirstate;
 pub mod discovery;
diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/errors.rs
@@ -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),
+        }
+    }
+}