changeset 52982:0c7ac026ed63

pyo3: add a generic error conversion extension trait This is going to be useful in more high-level modules like the upcoming `update` module.
author Rapha?l Gom?s <rgomes@octobus.net>
date Tue, 18 Feb 2025 11:39:17 +0100
parents d934d730c6c2
children 69d40a9778fe
files rust/hg-pyo3/src/utils.rs
diffstat 1 files changed, 44 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-pyo3/src/utils.rs	Tue Feb 18 11:33:20 2025 +0100
+++ b/rust/hg-pyo3/src/utils.rs	Tue Feb 18 11:39:17 2025 +0100
@@ -2,12 +2,15 @@
 use hg::revlog::index::Index as CoreIndex;
 use hg::revlog::inner_revlog::RevisionBuffer;
 use pyo3::buffer::{Element, PyBuffer};
-use pyo3::exceptions::PyValueError;
-use pyo3::prelude::*;
+use pyo3::exceptions::{
+    PyIOError, PyKeyboardInterrupt, PyRuntimeError, PyValueError,
+};
 use pyo3::types::{PyBytes, PyDict};
+use pyo3::{intern, prelude::*};
 use pyo3_sharedref::SharedByPyObject;
 use stable_deref_trait::StableDeref;
 
+use crate::exceptions::FallbackError;
 use crate::revlog::{InnerRevlog, PySharedIndex};
 
 /// Create the module, with `__package__` given from parent
@@ -301,3 +304,42 @@
         self.py_bytes
     }
 }
+
+/// Extension trait to help with generic error conversions from hg-core to
+/// Python.
+pub(crate) trait HgPyErrExt<T> {
+    fn into_pyerr(self, py: Python) -> PyResult<T>;
+}
+
+impl<T, E> HgPyErrExt<T> for Result<T, E>
+where
+    HgError: From<E>,
+{
+    fn into_pyerr(self, py: Python) -> PyResult<T> {
+        self.map_err(|e| match e.into() {
+            err @ HgError::IoError { .. } => {
+                PyIOError::new_err(err.to_string())
+            }
+            HgError::UnsupportedFeature(e) => {
+                FallbackError::new_err(e.to_string())
+            }
+            HgError::RaceDetected(_) => {
+                unreachable!("must not surface to the user")
+            }
+            HgError::Path(path_error) => {
+                let msg = PyBytes::new(py, path_error.to_string().as_bytes());
+                let cls = py
+                    .import(intern!(py, "mercurial.error"))
+                    .and_then(|m| m.getattr(intern!(py, "InputError")))
+                    .expect("failed to import InputError");
+                PyErr::from_value(
+                    cls.call1((msg,))
+                        .expect("initializing an InputError failed"),
+                )
+            }
+            HgError::InterruptReceived => PyKeyboardInterrupt::new_err(()),
+            e => PyRuntimeError::new_err(e.to_string()),
+        })
+    }
+}
+