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(Value::as_u64)
57 }
58
59 #[must_use]
63 pub fn get_i64(&self, key: &str) -> Option<i64> {
64 self.get(key).and_then(Value::as_i64)
65 }
66
67 #[must_use]
71 pub fn get_usize(&self, key: &str) -> Option<usize> {
72 self.get(key)
73 .and_then(Value::as_u64)
74 .and_then(|n| usize::try_from(n).ok())
75 }
76
77 #[must_use]
81 pub fn get_str(&self, key: &str) -> Option<&str> {
82 self.get(key).and_then(|v| v.as_str())
83 }
84
85 #[must_use]
89 pub fn get_bool(&self, key: &str) -> Option<bool> {
90 self.get(key).and_then(Value::as_bool)
91 }
92
93 #[must_use]
97 pub fn get_f64(&self, key: &str) -> Option<f64> {
98 self.get(key).and_then(Value::as_f64)
99 }
100
101 #[cfg(feature = "python")]
102 pub fn to_pydict(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
108 crate::python::params::params_to_pydict(py, self)
109 }
110}
111
112impl Deref for Params {
113 type Target = IndexMap<String, Value>;
114
115 fn deref(&self) -> &Self::Target {
116 &self.0
117 }
118}
119
120impl DerefMut for Params {
121 fn deref_mut(&mut self) -> &mut Self::Target {
122 &mut self.0
123 }
124}
125
126impl<'a> IntoIterator for &'a Params {
127 type Item = (&'a String, &'a Value);
128 type IntoIter = indexmap::map::Iter<'a, String, Value>;
129
130 fn into_iter(self) -> Self::IntoIter {
131 self.0.iter()
132 }
133}
134
135#[cfg(feature = "python")]
136pub fn from_pydict(
146 py: pyo3::Python<'_>,
147 dict: &pyo3::Py<pyo3::types::PyDict>,
148) -> pyo3::PyResult<Option<Params>> {
149 crate::python::params::pydict_to_params(py, dict)
150}
151
152#[cfg(test)]
153mod tests {
154 use rstest::*;
155 use serde_json::json;
156
157 use super::Params;
158
159 fn create_test_params() -> Params {
160 let mut params = Params::new();
161 params.insert("u64_val".to_string(), json!(42u64));
162 params.insert("i64_val".to_string(), json!(-100i64));
163 params.insert("usize_val".to_string(), json!(5u64));
164 params.insert("str_val".to_string(), json!("hello"));
165 params.insert("bool_val".to_string(), json!(true));
166 params.insert("f64_val".to_string(), json!(2.5));
167 params
168 }
169
170 #[rstest]
171 fn test_params_option_get_u64() {
172 let params = Some(create_test_params());
173 assert_eq!(params.as_ref().and_then(|p| p.get_u64("u64_val")), Some(42));
174 assert_eq!(params.as_ref().and_then(|p| p.get_u64("missing")), None);
175 assert_eq!(params.as_ref().and_then(|p| p.get_u64("str_val")), None);
176 }
177
178 #[rstest]
179 fn test_params_option_get_i64() {
180 let params = Some(create_test_params());
181 assert_eq!(
182 params.as_ref().and_then(|p| p.get_i64("i64_val")),
183 Some(-100)
184 );
185 assert_eq!(params.as_ref().and_then(|p| p.get_i64("missing")), None);
186 }
187
188 #[rstest]
189 fn test_params_option_get_usize() {
190 let params = Some(create_test_params());
191 assert_eq!(
192 params.as_ref().and_then(|p| p.get_usize("usize_val")),
193 Some(5)
194 );
195 assert_eq!(params.as_ref().and_then(|p| p.get_usize("missing")), None);
196 }
197
198 #[rstest]
199 fn test_params_option_get_str() {
200 let params = Some(create_test_params());
201 assert_eq!(
202 params.as_ref().and_then(|p| p.get_str("str_val")),
203 Some("hello")
204 );
205 assert_eq!(params.as_ref().and_then(|p| p.get_str("missing")), None);
206 assert_eq!(params.as_ref().and_then(|p| p.get_str("u64_val")), None);
207 }
208
209 #[rstest]
210 fn test_params_option_get_bool() {
211 let params = Some(create_test_params());
212 assert_eq!(
213 params.as_ref().and_then(|p| p.get_bool("bool_val")),
214 Some(true)
215 );
216 assert_eq!(params.as_ref().and_then(|p| p.get_bool("missing")), None);
217 }
218
219 #[rstest]
220 fn test_params_option_get_f64() {
221 let params = Some(create_test_params());
222 assert_eq!(
223 params.as_ref().and_then(|p| p.get_f64("f64_val")),
224 Some(2.5)
225 );
226 assert_eq!(params.as_ref().and_then(|p| p.get_f64("missing")), None);
227 }
228
229 #[rstest]
230 fn test_params_option_none() {
231 let params: Option<Params> = None;
232 assert_eq!(params.as_ref().and_then(|p| p.get_u64("any")), None);
233 assert_eq!(params.as_ref().and_then(|p| p.get_str("any")), None);
234 }
235
236 #[rstest]
237 fn test_params_ref_get_u64() {
238 let params = create_test_params();
239 assert_eq!(params.get_u64("u64_val"), Some(42));
240 assert_eq!(params.get_u64("missing"), None);
241 }
242
243 #[rstest]
244 fn test_params_ref_get_usize() {
245 let params = create_test_params();
246 assert_eq!(params.get_usize("usize_val"), Some(5));
247 assert_eq!(params.get_usize("missing"), None);
248 }
249
250 #[rstest]
251 fn test_params_ref_get_usize_respects_target_width() {
252 let mut params = Params::new();
253 params.insert("u32_max".to_string(), json!(u32::MAX));
254 params.insert("u32_overflow".to_string(), json!(4_294_967_296_u64));
255
256 assert_eq!(params.get_usize("u32_max"), Some(4_294_967_295_usize));
257 #[cfg(target_pointer_width = "32")]
258 assert_eq!(params.get_usize("u32_overflow"), None);
259 #[cfg(target_pointer_width = "64")]
260 assert_eq!(params.get_usize("u32_overflow"), Some(4_294_967_296));
261 }
262
263 #[rstest]
264 fn test_params_ref_get_str() {
265 let params = create_test_params();
266 assert_eq!(params.get_str("str_val"), Some("hello"));
267 assert_eq!(params.get_str("missing"), None);
268 }
269
270 #[rstest]
271 fn test_submit_tries_pattern() {
272 let mut params = Params::new();
273 params.insert("submit_tries".to_string(), json!(3u64));
274 let cmd_params = Some(params);
275
276 let submit_tries = cmd_params
277 .as_ref()
278 .and_then(|p| p.get_usize("submit_tries"))
279 .filter(|&n| n > 0);
280
281 assert_eq!(submit_tries, Some(3));
282 }
283
284 #[rstest]
285 fn test_submit_tries_pattern_zero_filtered() {
286 let mut params = Params::new();
287 params.insert("submit_tries".to_string(), json!(0u64));
288 let cmd_params = Some(params);
289
290 let submit_tries = cmd_params
291 .as_ref()
292 .and_then(|p| p.get_usize("submit_tries"))
293 .filter(|&n| n > 0);
294
295 assert_eq!(submit_tries, None);
296 }
297
298 #[rstest]
299 fn test_submit_tries_pattern_missing() {
300 let cmd_params: Option<Params> = None;
301
302 let submit_tries = cmd_params
303 .as_ref()
304 .and_then(|p| p.get_usize("submit_tries"))
305 .filter(|&n| n > 0);
306
307 assert_eq!(submit_tries, None);
308 }
309}