solders_traits_core/
lib.rs

1use pyo3::{
2    exceptions::{PyTypeError, PyValueError},
3    prelude::*,
4    pyclass::CompareOp,
5    types::PyBytes,
6};
7use serde::{Deserialize, Serialize};
8use std::{
9    collections::hash_map::DefaultHasher,
10    fmt,
11    hash::{Hash, Hasher},
12};
13
14pub fn richcmp_type_error(op: &str) -> PyErr {
15    let msg = format!("{op} not supported.");
16    PyTypeError::new_err(msg)
17}
18
19fn calculate_hash<T>(t: &T) -> u64
20where
21    T: Hash + ?Sized,
22{
23    let mut s = DefaultHasher::new();
24    t.hash(&mut s);
25    s.finish()
26}
27
28pub trait RichcmpEqualityOnly: PartialEq {
29    fn richcmp(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
30        match op {
31            CompareOp::Eq => Ok(self == other),
32            CompareOp::Ne => Ok(self != other),
33            CompareOp::Lt => Err(richcmp_type_error("<")),
34            CompareOp::Gt => Err(richcmp_type_error(">")),
35            CompareOp::Le => Err(richcmp_type_error("<=")),
36            CompareOp::Ge => Err(richcmp_type_error(">=")),
37        }
38    }
39}
40pub trait RichcmpFull: PartialEq + PartialOrd {
41    fn richcmp(&self, other: &Self, op: CompareOp) -> bool {
42        match op {
43            CompareOp::Eq => self == other,
44            CompareOp::Ne => self != other,
45            CompareOp::Lt => self < other,
46            CompareOp::Gt => self > other,
47            CompareOp::Le => self <= other,
48            CompareOp::Ge => self >= other,
49        }
50    }
51}
52
53#[macro_export]
54macro_rules! impl_display {
55    ($ident:ident) => {
56        impl std::fmt::Display for $ident {
57            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58                write!(f, "{:?}", self.0)
59            }
60        }
61    };
62}
63
64pub trait PyHash: Hash {
65    fn pyhash(&self) -> u64 {
66        calculate_hash(self)
67    }
68}
69
70pub trait PyBytesSlice: AsRef<[u8]> {
71    fn pybytes_slice<'a>(&self, py: Python<'a>) -> &'a PyBytes {
72        PyBytes::new(py, AsRef::<[u8]>::as_ref(self))
73    }
74}
75
76#[macro_export]
77macro_rules! pybytes_general_for_pybytes_slice {
78    ($ident:ident) => {
79        impl $crate::PyBytesGeneral for $ident {
80            fn pybytes_general<'a>(
81                &self,
82                py: pyo3::prelude::Python<'a>,
83            ) -> &'a pyo3::types::PyBytes {
84                $crate::PyBytesSlice::pybytes_slice(self, py)
85            }
86        }
87    };
88}
89
90#[macro_export]
91macro_rules! pybytes_general_for_pybytes_bincode {
92    ($ident:ident) => {
93        impl $crate::PyBytesGeneral for $ident {
94            fn pybytes_general<'a>(
95                &self,
96                py: pyo3::prelude::Python<'a>,
97            ) -> &'a pyo3::types::PyBytes {
98                $crate::PyBytesBincode::pybytes_bincode(self, py)
99            }
100        }
101    };
102}
103
104#[macro_export]
105macro_rules! pybytes_general_for_pybytes_cbor {
106    ($ident:ident) => {
107        impl $crate::PyBytesGeneral for $ident {
108            fn pybytes_general<'a>(
109                &self,
110                py: pyo3::prelude::Python<'a>,
111            ) -> &'a pyo3::types::PyBytes {
112                $crate::PyBytesCbor::pybytes_cbor(self, py)
113            }
114        }
115    };
116}
117
118pub trait PyBytesBincode: Serialize {
119    fn pybytes_bincode<'a>(&self, py: Python<'a>) -> &'a PyBytes {
120        PyBytes::new(py, &bincode::serialize(self).unwrap())
121    }
122}
123
124pub trait PyBytesCbor: Serialize + std::marker::Sized {
125    fn pybytes_cbor<'a>(&self, py: Python<'a>) -> &'a PyBytes {
126        PyBytes::new(py, &serde_cbor::to_vec(self).unwrap())
127    }
128}
129
130pub trait PyBytesGeneral {
131    fn pybytes_general<'a>(&self, py: Python<'a>) -> &'a PyBytes;
132}
133
134#[macro_export]
135macro_rules! pybytes_general_via_slice {
136    ($ident:ident) => {
137        impl $crate::PyBytesSlice for $ident {}
138        $crate::pybytes_general_for_pybytes_slice!($ident);
139    };
140}
141
142#[macro_export]
143macro_rules! pybytes_general_via_bincode {
144    ($ident:ident) => {
145        impl $crate::PyBytesBincode for $ident {}
146        $crate::pybytes_general_for_pybytes_bincode!($ident);
147    };
148}
149
150#[macro_export]
151macro_rules! pybytes_general_via_cbor {
152    ($ident:ident) => {
153        impl $crate::PyBytesCbor for $ident {}
154        $crate::pybytes_general_for_pybytes_cbor!($ident);
155    };
156}
157
158#[macro_export]
159macro_rules! py_from_bytes_general_for_py_from_bytes_bincode {
160    ($ident:ident) => {
161        impl $crate::PyFromBytesGeneral for $ident {
162            fn py_from_bytes_general(raw: &[u8]) -> PyResult<Self> {
163                <Self as $crate::PyFromBytesBincode>::py_from_bytes_bincode(raw)
164            }
165        }
166    };
167}
168
169#[macro_export]
170macro_rules! py_from_bytes_general_for_py_from_bytes_cbor {
171    ($ident:ident) => {
172        impl $crate::PyFromBytesGeneral for $ident {
173            fn py_from_bytes_general(raw: &[u8]) -> PyResult<Self> {
174                <Self as $crate::PyFromBytesCbor>::py_from_bytes_cbor(raw)
175            }
176        }
177    };
178}
179
180#[macro_export]
181macro_rules! py_from_bytes_general_via_bincode {
182    ($ident:ident) => {
183        impl $crate::PyFromBytesBincode<'_> for $ident {}
184        $crate::py_from_bytes_general_for_py_from_bytes_bincode!($ident);
185    };
186}
187
188pub fn to_py_value_err(err: &impl ToString) -> PyErr {
189    PyValueError::new_err(err.to_string())
190}
191
192pub fn handle_py_value_err<T: Into<P>, E: ToString, P>(res: Result<T, E>) -> PyResult<P> {
193    res.map_or_else(|e| Err(to_py_value_err(&e)), |v| Ok(v.into()))
194}
195
196pub trait PyFromBytesBincode<'b>: Deserialize<'b> {
197    fn py_from_bytes_bincode(raw: &'b [u8]) -> PyResult<Self> {
198        let deser = bincode::deserialize::<Self>(raw);
199        handle_py_value_err(deser)
200    }
201}
202
203#[macro_export]
204macro_rules! py_from_bytes_general_via_cbor {
205    ($ident:ident) => {
206        impl $crate::PyFromBytesCbor<'_> for $ident {}
207        $crate::py_from_bytes_general_for_py_from_bytes_cbor!($ident);
208    };
209}
210
211pub trait PyFromBytesCbor<'b>: Deserialize<'b> {
212    fn py_from_bytes_cbor(raw: &'b [u8]) -> PyResult<Self> {
213        let deser = serde_cbor::from_slice::<Self>(raw);
214        handle_py_value_err(deser)
215    }
216}
217
218pub trait PyFromBytesGeneral: Sized {
219    fn py_from_bytes_general(raw: &[u8]) -> PyResult<Self>;
220}
221
222pub trait CommonMethodsCore:
223    fmt::Display + fmt::Debug + PyBytesGeneral + PyFromBytesGeneral + IntoPy<PyObject> + Clone
224{
225    fn pybytes<'b>(&self, py: Python<'b>) -> &'b PyBytes {
226        PyBytesGeneral::pybytes_general(self, py)
227    }
228
229    fn pystr(&self) -> String {
230        self.to_string()
231    }
232    fn pyrepr(&self) -> String {
233        format!("{self:#?}")
234    }
235
236    fn py_from_bytes(raw: &[u8]) -> PyResult<Self> {
237        <Self as PyFromBytesGeneral>::py_from_bytes_general(raw)
238    }
239
240    fn pyreduce(&self) -> PyResult<(PyObject, PyObject)> {
241        let cloned = self.clone();
242        Python::with_gil(|py| {
243            let constructor = cloned.into_py(py).getattr(py, "from_bytes")?;
244            Ok((
245                constructor,
246                (PyBytesGeneral::pybytes_general(self, py).to_object(py),).to_object(py),
247            ))
248        })
249    }
250}
251
252pub trait CommonMethods<'a>: CommonMethodsCore + Serialize + Deserialize<'a> {
253    fn py_to_json(&self) -> String {
254        serde_json::to_string(self).unwrap()
255    }
256
257    fn py_from_json(raw: &'a str) -> PyResult<Self> {
258        serde_json::from_str(raw).map_err(|e| to_py_value_err(&e))
259    }
260}
261
262#[macro_export]
263macro_rules! common_methods_default {
264    ($ty:ty) => {
265        impl $crate::CommonMethodsCore for $ty {}
266        impl $crate::CommonMethods<'_> for $ty {}
267    };
268}
269
270#[macro_export]
271macro_rules! transaction_status_boilerplate {
272    ($name:ident) => {
273        impl $crate::RichcmpEqualityOnly for $name {}
274        impl std::fmt::Display for $name {
275            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
276                write!(f, "{:?}", self)
277            }
278        }
279        $crate::pybytes_general_via_bincode!($name);
280        $crate::py_from_bytes_general_via_bincode!($name);
281        $crate::common_methods_default!($name);
282    };
283}