diff --git a/arbitrary-value/src/arbitrary.rs b/arbitrary-value/src/arbitrary.rs index 63bc962..e130b89 100644 --- a/arbitrary-value/src/arbitrary.rs +++ b/arbitrary-value/src/arbitrary.rs @@ -3,11 +3,13 @@ use chrono_tz::Tz; use ijson::{IArray, INumber, IObject, IString, IValue}; #[cfg(feature = "pyo3")] use pyo3::{ - exceptions::{PyTypeError, PyValueError}, + exceptions::{PyException, PyTypeError, PyValueError}, prelude::*, types::{PyList, PyNone}, }; -use snafu::Snafu; +use snafu::{ResultExt, Snafu}; + +use crate::finite_f64::NotFinite; use super::{finite_f64::FiniteF64, map::Map, map_key::MapKey}; @@ -73,22 +75,64 @@ impl From for IValue { } #[cfg(feature = "pyo3")] -impl<'py> FromPyObject<'py> for Arbitrary { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +#[derive(Debug, Snafu)] +pub enum ExtractArbitraryError { + /// error getting the qualified type name when trying to report + /// that an instance of this type cannot be extracted as an [`Arbitrary`] + GetTypeNameError { source: PyErr }, + + /// error extracting the (successfully retrieved) fully qualified type name as a [`String`] + ExtractTypeNameError { source: PyErr }, + + /// the float trying to be extracted is not finite, which isn't supported + FloatNotFinite { source: NotFinite }, + + /// can't extract an arbitrary from a {type_name} + UnsupportedType { type_name: String }, +} + +#[cfg(feature = "pyo3")] +impl From for PyErr { + fn from(error: ExtractArbitraryError) -> Self { + match &error { + ExtractArbitraryError::GetTypeNameError { .. } => { + PyException::new_err(error.to_string()) + } + ExtractArbitraryError::ExtractTypeNameError { .. } => { + PyException::new_err(error.to_string()) + } + ExtractArbitraryError::FloatNotFinite { .. } => { + PyValueError::new_err(error.to_string()) + } + ExtractArbitraryError::UnsupportedType { .. } => { + PyTypeError::new_err(error.to_string()) + } + } + } +} + +#[cfg(feature = "pyo3")] +impl<'a, 'py> FromPyObject<'a, 'py> for Arbitrary { + type Error = ExtractArbitraryError; + + fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(map_key) = ob.extract::() { Ok(map_key.into()) } else if let Ok(map) = ob.extract() { Ok(Self::Map(map)) } else if let Ok(f) = ob.extract::() { - let f = FiniteF64::try_from(f).map_err(|err| PyValueError::new_err(err.to_string()))?; + let f = FiniteF64::try_from(f).context(FloatNotFiniteSnafu)?; Ok(Self::Float(f)) } else if let Ok(vec) = ob.extract() { Ok(Self::Array(vec)) } else { - let type_name = ob.get_type().fully_qualified_name()?; - Err(PyTypeError::new_err(format!( - "can't extract an arbitrary from a {type_name}" - ))) + let type_name = ob + .get_type() + .fully_qualified_name() + .context(GetTypeNameSnafu)? + .extract() + .context(ExtractTypeNameSnafu)?; + Err(ExtractArbitraryError::UnsupportedType { type_name }) } } } diff --git a/arbitrary-value/src/map_key.rs b/arbitrary-value/src/map_key.rs index 4f593f2..de6c42f 100644 --- a/arbitrary-value/src/map_key.rs +++ b/arbitrary-value/src/map_key.rs @@ -10,6 +10,7 @@ use pyo3::{ prelude::*, types::{PyNone, PyTuple}, }; +use snafu::{ResultExt, Snafu}; use super::arbitrary::{Arbitrary, MapKeyFromArbitraryError}; @@ -43,9 +44,40 @@ impl Display for MapKey { } #[cfg(feature = "pyo3")] -impl<'py> FromPyObject<'py> for MapKey { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { - if let Ok(_none) = ob.downcast::() { +#[derive(Debug, Snafu)] +pub enum ExtractMapKeyError { + /// error getting the qualified type name when trying to report + /// that an instance of this type cannot be extracted as an [`Arbitrary`] + GetTypeNameError { source: PyErr }, + + /// error extracting the (successfully retrieved) fully qualified type name as a [`String`] + ExtractTypeNameError { source: PyErr }, + + /// can't extract a map key from a {type_name} + UnsupportedType { type_name: String }, +} + +#[cfg(feature = "pyo3")] +impl From for PyErr { + fn from(error: ExtractMapKeyError) -> Self { + use pyo3::exceptions::PyException; + + match &error { + ExtractMapKeyError::GetTypeNameError { .. } => PyException::new_err(error.to_string()), + ExtractMapKeyError::ExtractTypeNameError { .. } => { + PyException::new_err(error.to_string()) + } + ExtractMapKeyError::UnsupportedType { .. } => PyTypeError::new_err(error.to_string()), + } + } +} + +#[cfg(feature = "pyo3")] +impl<'a, 'py> FromPyObject<'a, 'py> for MapKey { + type Error = ExtractMapKeyError; + + fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { + if let Ok(_none) = ob.cast::() { Ok(Self::Null) } else if let Ok(b) = ob.extract() { Ok(Self::Bool(b)) @@ -56,10 +88,13 @@ impl<'py> FromPyObject<'py> for MapKey { } else if let Ok(tuple) = ob.extract() { Ok(Self::Tuple(tuple)) } else { - let type_name = ob.get_type().fully_qualified_name()?; - Err(PyTypeError::new_err(format!( - "can't extract a map key from a {type_name}" - ))) + let type_name = ob + .get_type() + .fully_qualified_name() + .context(GetTypeNameSnafu)? + .extract() + .context(ExtractTypeNameSnafu)?; + Err(ExtractMapKeyError::UnsupportedType { type_name }) } } }