1use std::ops::{Deref, DerefMut};
22
23use indexmap::IndexMap;
24use serde::{Deserialize, Serialize};
25use serde_json::Value;
26
27#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
35#[serde(transparent)]
36pub struct Params(IndexMap<String, Value>);
37
38impl Params {
39 #[must_use]
41 pub fn new() -> Self {
42 Self(IndexMap::new())
43 }
44
45 #[must_use]
47 pub fn from_index_map(map: IndexMap<String, Value>) -> Self {
48 Self(map)
49 }
50
51 #[must_use]
55 pub fn get_u64(&self, key: &str) -> Option<u64> {
56 self.get(key).and_then(|v| v.as_u64())
57 }
58
59 #[must_use]
63 pub fn get_i64(&self, key: &str) -> Option<i64> {
64 self.get(key).and_then(|v| v.as_i64())
65 }
66
67 #[must_use]
71 #[expect(
72 clippy::cast_possible_truncation,
73 reason = "usize is 64-bit on all supported targets"
74 )]
75 pub fn get_usize(&self, key: &str) -> Option<usize> {
76 self.get(key).and_then(|v| v.as_u64()).map(|n| n as usize)
77 }
78
79 #[must_use]
83 pub fn get_str(&self, key: &str) -> Option<&str> {
84 self.get(key).and_then(|v| v.as_str())
85 }
86
87 #[must_use]
91 pub fn get_bool(&self, key: &str) -> Option<bool> {
92 self.get(key).and_then(|v| v.as_bool())
93 }
94
95 #[must_use]
99 pub fn get_f64(&self, key: &str) -> Option<f64> {
100 self.get(key).and_then(|v| v.as_f64())
101 }
102
103 #[cfg(feature = "python")]
104 pub fn to_pydict(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
110 crate::python::params::params_to_pydict(py, self)
111 }
112}
113
114impl Deref for Params {
115 type Target = IndexMap<String, Value>;
116
117 fn deref(&self) -> &Self::Target {
118 &self.0
119 }
120}
121
122impl DerefMut for Params {
123 fn deref_mut(&mut self) -> &mut Self::Target {
124 &mut self.0
125 }
126}
127
128impl<'a> IntoIterator for &'a Params {
129 type Item = (&'a String, &'a Value);
130 type IntoIter = indexmap::map::Iter<'a, String, Value>;
131
132 fn into_iter(self) -> Self::IntoIter {
133 self.0.iter()
134 }
135}
136
137#[cfg(feature = "python")]
138pub fn from_pydict(
148 py: pyo3::Python<'_>,
149 dict: pyo3::Py<pyo3::types::PyDict>,
150) -> pyo3::PyResult<Option<Params>> {
151 crate::python::params::pydict_to_params(py, dict)
152}
153
154#[cfg(test)]
155mod tests {
156 use rstest::*;
157 use serde_json::json;
158
159 use super::Params;
160
161 fn create_test_params() -> Params {
162 let mut params = Params::new();
163 params.insert("u64_val".to_string(), json!(42u64));
164 params.insert("i64_val".to_string(), json!(-100i64));
165 params.insert("usize_val".to_string(), json!(5u64));
166 params.insert("str_val".to_string(), json!("hello"));
167 params.insert("bool_val".to_string(), json!(true));
168 params.insert("f64_val".to_string(), json!(2.5));
169 params
170 }
171
172 #[rstest]
173 fn test_params_option_get_u64() {
174 let params = Some(create_test_params());
175 assert_eq!(params.as_ref().and_then(|p| p.get_u64("u64_val")), Some(42));
176 assert_eq!(params.as_ref().and_then(|p| p.get_u64("missing")), None);
177 assert_eq!(params.as_ref().and_then(|p| p.get_u64("str_val")), None);
178 }
179
180 #[rstest]
181 fn test_params_option_get_i64() {
182 let params = Some(create_test_params());
183 assert_eq!(
184 params.as_ref().and_then(|p| p.get_i64("i64_val")),
185 Some(-100)
186 );
187 assert_eq!(params.as_ref().and_then(|p| p.get_i64("missing")), None);
188 }
189
190 #[rstest]
191 fn test_params_option_get_usize() {
192 let params = Some(create_test_params());
193 assert_eq!(
194 params.as_ref().and_then(|p| p.get_usize("usize_val")),
195 Some(5)
196 );
197 assert_eq!(params.as_ref().and_then(|p| p.get_usize("missing")), None);
198 }
199
200 #[rstest]
201 fn test_params_option_get_str() {
202 let params = Some(create_test_params());
203 assert_eq!(
204 params.as_ref().and_then(|p| p.get_str("str_val")),
205 Some("hello")
206 );
207 assert_eq!(params.as_ref().and_then(|p| p.get_str("missing")), None);
208 assert_eq!(params.as_ref().and_then(|p| p.get_str("u64_val")), None);
209 }
210
211 #[rstest]
212 fn test_params_option_get_bool() {
213 let params = Some(create_test_params());
214 assert_eq!(
215 params.as_ref().and_then(|p| p.get_bool("bool_val")),
216 Some(true)
217 );
218 assert_eq!(params.as_ref().and_then(|p| p.get_bool("missing")), None);
219 }
220
221 #[rstest]
222 fn test_params_option_get_f64() {
223 let params = Some(create_test_params());
224 assert_eq!(
225 params.as_ref().and_then(|p| p.get_f64("f64_val")),
226 Some(2.5)
227 );
228 assert_eq!(params.as_ref().and_then(|p| p.get_f64("missing")), None);
229 }
230
231 #[rstest]
232 fn test_params_option_none() {
233 let params: Option<Params> = None;
234 assert_eq!(params.as_ref().and_then(|p| p.get_u64("any")), None);
235 assert_eq!(params.as_ref().and_then(|p| p.get_str("any")), None);
236 }
237
238 #[rstest]
239 fn test_params_ref_get_u64() {
240 let params = create_test_params();
241 assert_eq!(params.get_u64("u64_val"), Some(42));
242 assert_eq!(params.get_u64("missing"), None);
243 }
244
245 #[rstest]
246 fn test_params_ref_get_usize() {
247 let params = create_test_params();
248 assert_eq!(params.get_usize("usize_val"), Some(5));
249 assert_eq!(params.get_usize("missing"), None);
250 }
251
252 #[rstest]
253 fn test_params_ref_get_str() {
254 let params = create_test_params();
255 assert_eq!(params.get_str("str_val"), Some("hello"));
256 assert_eq!(params.get_str("missing"), None);
257 }
258
259 #[rstest]
260 fn test_submit_tries_pattern() {
261 let mut params = Params::new();
262 params.insert("submit_tries".to_string(), json!(3u64));
263 let cmd_params = Some(params);
264
265 let submit_tries = cmd_params
266 .as_ref()
267 .and_then(|p| p.get_usize("submit_tries"))
268 .filter(|&n| n > 0);
269
270 assert_eq!(submit_tries, Some(3));
271 }
272
273 #[rstest]
274 fn test_submit_tries_pattern_zero_filtered() {
275 let mut params = Params::new();
276 params.insert("submit_tries".to_string(), json!(0u64));
277 let cmd_params = Some(params);
278
279 let submit_tries = cmd_params
280 .as_ref()
281 .and_then(|p| p.get_usize("submit_tries"))
282 .filter(|&n| n > 0);
283
284 assert_eq!(submit_tries, None);
285 }
286
287 #[rstest]
288 fn test_submit_tries_pattern_missing() {
289 let cmd_params: Option<Params> = None;
290
291 let submit_tries = cmd_params
292 .as_ref()
293 .and_then(|p| p.get_usize("submit_tries"))
294 .filter(|&n| n > 0);
295
296 assert_eq!(submit_tries, None);
297 }
298}