fidius_python/
value_bridge.rs1use pyo3::prelude::*;
29use pyo3::types::{PyBool, PyBytes, PyDict, PyFloat, PyInt, PyList, PyString, PyTuple};
30use serde_json::{Map, Value};
31
32pub fn value_to_pyobject<'py>(py: Python<'py>, value: &Value) -> PyResult<Bound<'py, PyAny>> {
34 match value {
35 Value::Null => Ok(py.None().into_bound(py)),
36 Value::Bool(b) => Ok(PyBool::new(py, *b).to_owned().into_any()),
37 Value::Number(n) => {
38 if let Some(i) = n.as_i64() {
39 Ok(i.into_pyobject(py)?.into_any())
40 } else if let Some(u) = n.as_u64() {
41 Ok(u.into_pyobject(py)?.into_any())
42 } else {
43 let f = n.as_f64().ok_or_else(|| {
44 pyo3::exceptions::PyValueError::new_err("non-finite number in JSON value")
45 })?;
46 Ok(f.into_pyobject(py)?.into_any())
47 }
48 }
49 Value::String(s) => Ok(PyString::new(py, s).into_any()),
50 Value::Array(items) => {
51 let list = PyList::empty(py);
52 for item in items {
53 list.append(value_to_pyobject(py, item)?)?;
54 }
55 Ok(list.into_any())
56 }
57 Value::Object(map) => {
58 let dict = PyDict::new(py);
59 for (k, v) in map {
60 dict.set_item(k, value_to_pyobject(py, v)?)?;
61 }
62 Ok(dict.into_any())
63 }
64 }
65}
66
67pub fn pyobject_to_value(obj: &Bound<'_, PyAny>) -> PyResult<Value> {
73 if obj.is_none() {
74 return Ok(Value::Null);
75 }
76 if let Ok(b) = obj.downcast::<PyBool>() {
77 return Ok(Value::Bool(b.is_true()));
78 }
79 if let Ok(s) = obj.downcast::<PyString>() {
80 return Ok(Value::String(s.extract()?));
81 }
82 if let Ok(b) = obj.downcast::<PyBytes>() {
86 let bytes: &[u8] = b.as_bytes();
87 let arr: Vec<Value> = bytes
88 .iter()
89 .map(|x| Value::Number((*x as i64).into()))
90 .collect();
91 return Ok(Value::Array(arr));
92 }
93 if let Ok(_f) = obj.downcast::<PyFloat>() {
94 let f: f64 = obj.extract()?;
95 let n = serde_json::Number::from_f64(f).ok_or_else(|| {
96 pyo3::exceptions::PyValueError::new_err("non-finite float cannot be encoded to JSON")
97 })?;
98 return Ok(Value::Number(n));
99 }
100 if let Ok(_i) = obj.downcast::<PyInt>() {
101 if let Ok(i) = obj.extract::<i64>() {
103 return Ok(Value::Number(i.into()));
104 }
105 if let Ok(u) = obj.extract::<u64>() {
106 return Ok(Value::Number(u.into()));
107 }
108 let f: f64 = obj.extract()?;
109 let n = serde_json::Number::from_f64(f)
110 .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("integer too large for JSON"))?;
111 return Ok(Value::Number(n));
112 }
113 if let Ok(list) = obj.downcast::<PyList>() {
114 let mut out = Vec::with_capacity(list.len());
115 for item in list.iter() {
116 out.push(pyobject_to_value(&item)?);
117 }
118 return Ok(Value::Array(out));
119 }
120 if let Ok(tuple) = obj.downcast::<PyTuple>() {
121 let mut out = Vec::with_capacity(tuple.len());
122 for item in tuple.iter() {
123 out.push(pyobject_to_value(&item)?);
124 }
125 return Ok(Value::Array(out));
126 }
127 if let Ok(dict) = obj.downcast::<PyDict>() {
128 let mut map = Map::new();
129 for (k, v) in dict.iter() {
130 let key: String = k
131 .extract()
132 .or_else(|_| -> PyResult<String> { k.str()?.extract() })?;
133 map.insert(key, pyobject_to_value(&v)?);
134 }
135 return Ok(Value::Object(map));
136 }
137
138 let s: String = obj.str()?.extract()?;
140 Ok(Value::String(s))
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use serde_json::json;
147
148 #[test]
149 fn roundtrip_primitives() {
150 crate::ensure_initialized();
151 Python::with_gil(|py| {
152 for v in [
153 json!(null),
154 json!(true),
155 json!(42i64),
156 json!(3.5f64),
157 json!("hello"),
158 json!([1, "two", false]),
159 json!({"a": 1, "b": [2, 3]}),
160 ] {
161 let py_obj = value_to_pyobject(py, &v).unwrap();
162 let back = pyobject_to_value(&py_obj).unwrap();
163 assert_eq!(back, v, "round-trip failed for {v:?}");
164 }
165 });
166 }
167}