rust: Introduce an `HgError` enum for common error cases
Differential Revision: https://phab.mercurial-scm.org/D9892
--- /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),
+ }
+ }
+}
--- a/rust/hg-core/src/lib.rs Tue Jan 26 20:42:36 2021 +0100
+++ b/rust/hg-core/src/lib.rs Wed Jan 27 13:19:49 2021 +0100
@@ -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;