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)]
38#[serde(transparent)]
39pub struct Params(IndexMap<String, Value>);
40
41impl Params {
42 #[must_use]
44 pub fn new() -> Self {
45 Self(IndexMap::new())
46 }
47
48 #[must_use]
50 pub fn from_index_map(map: IndexMap<String, Value>) -> Self {
51 Self(map)
52 }
53
54 #[must_use]
58 pub fn get_u64(&self, key: &str) -> Option<u64> {
59 self.get(key).and_then(Value::as_u64)
60 }
61
62 #[must_use]
66 pub fn get_i64(&self, key: &str) -> Option<i64> {
67 self.get(key).and_then(Value::as_i64)
68 }
69
70 #[must_use]
74 pub fn get_usize(&self, key: &str) -> Option<usize> {
75 self.get(key)
76 .and_then(Value::as_u64)
77 .and_then(|n| usize::try_from(n).ok())
78 }
79
80 #[must_use]
84 pub fn get_str(&self, key: &str) -> Option<&str> {
85 self.get(key).and_then(|v| v.as_str())
86 }
87
88 #[must_use]
92 pub fn get_bool(&self, key: &str) -> Option<bool> {
93 self.get(key).and_then(Value::as_bool)
94 }
95
96 #[must_use]
100 pub fn get_f64(&self, key: &str) -> Option<f64> {
101 self.get(key).and_then(Value::as_f64)
102 }
103
104 #[cfg(feature = "python")]
105 pub fn to_pydict(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
111 crate::python::params::params_to_pydict(py, self)
112 }
113}
114
115impl Deref for Params {
116 type Target = IndexMap<String, Value>;
117
118 fn deref(&self) -> &Self::Target {
119 &self.0
120 }
121}
122
123impl DerefMut for Params {
124 fn deref_mut(&mut self) -> &mut Self::Target {
125 &mut self.0
126 }
127}
128
129impl<'a> IntoIterator for &'a Params {
130 type Item = (&'a String, &'a Value);
131 type IntoIter = indexmap::map::Iter<'a, String, Value>;
132
133 fn into_iter(self) -> Self::IntoIter {
134 self.0.iter()
135 }
136}
137
138#[cfg(feature = "python")]
139pub fn from_pydict(
150 py: pyo3::Python<'_>,
151 dict: &pyo3::Py<pyo3::types::PyDict>,
152) -> pyo3::PyResult<Option<Params>> {
153 crate::python::params::pydict_to_params(py, dict)
154}
155
156#[cfg(test)]
157mod tests {
158 use rstest::*;
159 use serde_json::json;
160
161 use super::Params;
162
163 fn create_test_params() -> Params {
164 let mut params = Params::new();
165 params.insert("u64_val".to_string(), json!(42u64));
166 params.insert("i64_val".to_string(), json!(-100i64));
167 params.insert("usize_val".to_string(), json!(5u64));
168 params.insert("str_val".to_string(), json!("hello"));
169 params.insert("bool_val".to_string(), json!(true));
170 params.insert("f64_val".to_string(), json!(2.5));
171 params
172 }
173
174 #[rstest]
175 fn test_params_getters() {
176 let params = create_test_params();
177
178 assert_eq!(params.get_u64("u64_val"), Some(42));
179 assert_eq!(params.get_i64("i64_val"), Some(-100));
180 assert_eq!(params.get_usize("usize_val"), Some(5));
181 assert_eq!(params.get_str("str_val"), Some("hello"));
182 assert_eq!(params.get_bool("bool_val"), Some(true));
183 assert_eq!(params.get_f64("f64_val"), Some(2.5));
184 assert_eq!(params.get_u64("missing"), None);
185 assert_eq!(params.get_i64("missing"), None);
186 assert_eq!(params.get_usize("missing"), None);
187 assert_eq!(params.get_str("missing"), None);
188 assert_eq!(params.get_bool("missing"), None);
189 assert_eq!(params.get_f64("missing"), None);
190 assert_eq!(params.get_u64("str_val"), None);
191 assert_eq!(params.get_str("u64_val"), None);
192 }
193
194 #[rstest]
195 fn test_params_ref_get_usize_respects_target_width() {
196 let mut params = Params::new();
197 params.insert("u32_max".to_string(), json!(u32::MAX));
198 params.insert("u32_overflow".to_string(), json!(4_294_967_296_u64));
199
200 assert_eq!(params.get_usize("u32_max"), Some(4_294_967_295_usize));
201 #[cfg(target_pointer_width = "32")]
202 assert_eq!(params.get_usize("u32_overflow"), None);
203 #[cfg(target_pointer_width = "64")]
204 assert_eq!(params.get_usize("u32_overflow"), Some(4_294_967_296));
205 }
206}