1pub mod toon;
2
3#[cfg(feature = "python")]
4mod py_binding {
5 use crate::toon::{encode_with, Config};
8 use pyo3::exceptions::{PyTypeError, PyValueError};
9 use pyo3::prelude::*;
10 use pyo3::types::{PyBytes, PyDict};
11
12 const OPTION_NAMES: [&str; 7] = [
15 "delimiter",
16 "key_folding",
17 "flatten_depth",
18 "empty_array_bare",
19 "escape_controls",
20 "max_depth",
21 "max_input_bytes",
22 ];
23
24 macro_rules! opt_kw {
27 ($kwargs:expr, $name:literal, $dest:expr, $ty:ty) => {
28 if let Some(v) = $kwargs.get_item($name)? {
29 if !v.is_none() {
30 $dest = v.extract::<$ty>()?;
31 }
32 }
33 };
34 }
35
36 fn config_from_kwargs(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Config> {
40 let mut cfg = Config::default();
41 let Some(kwargs) = kwargs else {
42 return Ok(cfg);
43 };
44 for k in kwargs.keys().iter() {
45 let name: String = k.extract()?;
46 if !OPTION_NAMES.contains(&name.as_str()) {
47 return Err(PyTypeError::new_err(format!(
48 "unexpected keyword argument '{name}'"
49 )));
50 }
51 }
52 if let Some(v) = kwargs.get_item("delimiter")? {
53 if !v.is_none() {
54 let d: String = v.extract()?;
55 cfg.delimiter = match d.as_bytes() {
58 b"," => b',',
59 b"\t" => b'\t',
60 b"|" => b'|',
61 _ => {
62 return Err(PyValueError::new_err(
63 "delimiter must be ',', '\\t', or '|'",
64 ))
65 }
66 };
67 }
68 }
69 opt_kw!(kwargs, "key_folding", cfg.key_folding, bool);
70 opt_kw!(kwargs, "flatten_depth", cfg.flatten_depth, Option<usize>);
71 opt_kw!(kwargs, "empty_array_bare", cfg.empty_array_bare, bool);
72 opt_kw!(kwargs, "escape_controls", cfg.escape_controls, bool);
73 opt_kw!(kwargs, "max_depth", cfg.max_depth, usize);
74 opt_kw!(kwargs, "max_input_bytes", cfg.max_input_bytes, usize);
75 Ok(cfg)
76 }
77
78 #[pyfunction(signature = (json_bytes, **kwargs))]
79 fn dumps_bytes<'py>(
80 py: Python<'py>,
81 json_bytes: &Bound<'py, PyBytes>,
82 kwargs: Option<&Bound<'py, PyDict>>,
83 ) -> PyResult<String> {
84 let cfg = config_from_kwargs(kwargs)?;
85 let bytes = json_bytes.as_bytes();
86 py.detach(|| encode_with(bytes, &cfg))
87 .map_err(|e| PyValueError::new_err(e.to_string()))
88 }
89
90 #[pymodule]
91 fn _etoon(m: &Bound<'_, PyModule>) -> PyResult<()> {
92 m.add_function(wrap_pyfunction!(dumps_bytes, m)?)?;
93 Ok(())
94 }
95}