pyo3/conversions/
either.rs1#![cfg(feature = "either")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"either\"] }")]
20#[cfg(feature = "experimental-inspect")]
48use crate::inspect::types::TypeInfo;
49use crate::{
50 exceptions::PyTypeError, Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny,
51 PyErr, Python,
52};
53use either::Either;
54
55#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
56impl<'py, L, R> IntoPyObject<'py> for Either<L, R>
57where
58 L: IntoPyObject<'py>,
59 R: IntoPyObject<'py>,
60{
61 type Target = PyAny;
62 type Output = Bound<'py, Self::Target>;
63 type Error = PyErr;
64
65 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
66 match self {
67 Either::Left(l) => l.into_bound_py_any(py),
68 Either::Right(r) => r.into_bound_py_any(py),
69 }
70 }
71}
72
73#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
74impl<'a, 'py, L, R> IntoPyObject<'py> for &'a Either<L, R>
75where
76 &'a L: IntoPyObject<'py>,
77 &'a R: IntoPyObject<'py>,
78{
79 type Target = PyAny;
80 type Output = Bound<'py, Self::Target>;
81 type Error = PyErr;
82
83 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
84 match self {
85 Either::Left(l) => l.into_bound_py_any(py),
86 Either::Right(r) => r.into_bound_py_any(py),
87 }
88 }
89}
90
91#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
92impl<'a, 'py, L, R> FromPyObject<'a, 'py> for Either<L, R>
93where
94 L: FromPyObject<'a, 'py>,
95 R: FromPyObject<'a, 'py>,
96{
97 type Error = PyErr;
98
99 #[inline]
100 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
101 if let Ok(l) = obj.extract::<L>() {
102 Ok(Either::Left(l))
103 } else if let Ok(r) = obj.extract::<R>() {
104 Ok(Either::Right(r))
105 } else {
106 let err_msg = format!(
109 "failed to convert the value to 'Union[{}, {}]'",
110 std::any::type_name::<L>(),
111 std::any::type_name::<R>()
112 );
113 Err(PyTypeError::new_err(err_msg))
114 }
115 }
116
117 #[cfg(feature = "experimental-inspect")]
118 fn type_input() -> TypeInfo {
119 TypeInfo::union_of(&[L::type_input(), R::type_input()])
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use std::borrow::Cow;
126
127 use crate::exceptions::PyTypeError;
128 use crate::{IntoPyObject, Python};
129
130 use crate::types::PyAnyMethods;
131 use either::Either;
132
133 #[test]
134 fn test_either_conversion() {
135 type E = Either<i32, String>;
136 type E1 = Either<i32, f32>;
137 type E2 = Either<f32, i32>;
138
139 Python::attach(|py| {
140 let l = E::Left(42);
141 let obj_l = (&l).into_pyobject(py).unwrap();
142 assert_eq!(obj_l.extract::<i32>().unwrap(), 42);
143 assert_eq!(obj_l.extract::<E>().unwrap(), l);
144
145 let r = E::Right("foo".to_owned());
146 let obj_r = (&r).into_pyobject(py).unwrap();
147 assert_eq!(obj_r.extract::<Cow<'_, str>>().unwrap(), "foo");
148 assert_eq!(obj_r.extract::<E>().unwrap(), r);
149
150 let obj_s = "foo".into_pyobject(py).unwrap();
151 let err = obj_s.extract::<E1>().unwrap_err();
152 assert!(err.is_instance_of::<PyTypeError>(py));
153 assert_eq!(
154 err.to_string(),
155 "TypeError: failed to convert the value to 'Union[i32, f32]'"
156 );
157
158 let obj_i = 42i32.into_pyobject(py).unwrap();
159 assert_eq!(obj_i.extract::<E1>().unwrap(), E1::Left(42));
160 assert_eq!(obj_i.extract::<E2>().unwrap(), E2::Left(42.0));
161
162 let obj_f = 42.0f64.into_pyobject(py).unwrap();
163 assert_eq!(obj_f.extract::<E1>().unwrap(), E1::Right(42.0));
164 assert_eq!(obj_f.extract::<E2>().unwrap(), E2::Left(42.0));
165 });
166 }
167}