use std::fmt::Display; use chrono::DateTime; use chrono_tz::Tz; use ijson::IString; use itertools::Itertools; #[cfg(feature = "pyo3")] use pyo3::{ exceptions::PyTypeError, prelude::*, types::{PyNone, PyTuple}, }; use snafu::{ResultExt, Snafu}; use super::arbitrary::{Arbitrary, MapKeyFromArbitraryError}; #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum MapKey { Null, Bool(bool), Integer(i64), String(String), Tuple(Vec), DateTime(DateTime), } impl Display for MapKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MapKey::Null => write!(f, "null"), MapKey::Bool(b) => write!(f, "{b}"), MapKey::Integer(i) => write!(f, "{i}"), MapKey::String(s) => write!(f, "{s}"), MapKey::Tuple(vec) => { let comma_separated = Itertools::intersperse(vec.iter().map(ToString::to_string), ", ".to_string()); write!(f, "({})", String::from_iter(comma_separated)) } MapKey::DateTime(date_time) => { write!(f, "{}", date_time.to_rfc3339()) } } } } #[cfg(feature = "pyo3")] #[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)) } else if let Ok(int) = ob.extract() { Ok(Self::Integer(int)) } else if let Ok(s) = ob.extract() { Ok(Self::String(s)) } else if let Ok(tuple) = ob.extract() { Ok(Self::Tuple(tuple)) } else { let type_name = ob .get_type() .fully_qualified_name() .context(GetTypeNameSnafu)? .extract() .context(ExtractTypeNameSnafu)?; Err(ExtractMapKeyError::UnsupportedType { type_name }) } } } #[cfg(feature = "pyo3")] impl<'py> IntoPyObject<'py> for MapKey { type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { match self { MapKey::Null => Ok(PyNone::get(py).to_owned().into_any()), MapKey::Bool(b) => Ok(b.into_pyobject(py)?.to_owned().into_any()), MapKey::Integer(i) => Ok(i.into_pyobject(py)?.into_any()), MapKey::String(s) => Ok(s.into_pyobject(py)?.into_any()), MapKey::Tuple(vec) => Ok(PyTuple::new(py, vec)?.into_any()), MapKey::DateTime(date_time) => Ok(date_time.into_pyobject(py)?.into_any()), } } } impl From for IString { fn from(map_key: MapKey) -> Self { Self::from(map_key.to_string()) } } impl TryFrom for MapKey { type Error = MapKeyFromArbitraryError; fn try_from(arbitrary: Arbitrary) -> Result { match arbitrary { Arbitrary::Null => Ok(MapKey::Null), Arbitrary::Bool(b) => Ok(MapKey::Bool(b)), Arbitrary::Integer(i) => Ok(MapKey::Integer(i)), Arbitrary::String(s) => Ok(MapKey::String(s)), Arbitrary::Array(vec) => { let tuple = Result::from_iter(vec.into_iter().map(TryInto::try_into))?; Ok(MapKey::Tuple(tuple)) } Arbitrary::DateTime(date_time) => Ok(MapKey::DateTime(date_time)), Arbitrary::Float(float) => { Err(MapKeyFromArbitraryError::FloatNotSupported { value: float }) } Arbitrary::Map(map) => Err(MapKeyFromArbitraryError::MapCannotBeAMapKey { value: map }), } } }