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_getters() {
172 let params = create_test_params();
173
174 assert_eq!(params.get_u64("u64_val"), Some(42));
175 assert_eq!(params.get_i64("i64_val"), Some(-100));
176 assert_eq!(params.get_usize("usize_val"), Some(5));
177 assert_eq!(params.get_str("str_val"), Some("hello"));
178 assert_eq!(params.get_bool("bool_val"), Some(true));
179 assert_eq!(params.get_f64("f64_val"), Some(2.5));
180 assert_eq!(params.get_u64("missing"), None);
181 assert_eq!(params.get_i64("missing"), None);
182 assert_eq!(params.get_usize("missing"), None);
183 assert_eq!(params.get_str("missing"), None);
184 assert_eq!(params.get_bool("missing"), None);
185 assert_eq!(params.get_f64("missing"), None);
186 assert_eq!(params.get_u64("str_val"), None);
187 assert_eq!(params.get_str("u64_val"), None);
188 }
189
190 #[rstest]
191 fn test_params_ref_get_usize_respects_target_width() {
192 let mut params = Params::new();
193 params.insert("u32_max".to_string(), json!(u32::MAX));
194 params.insert("u32_overflow".to_string(), json!(4_294_967_296_u64));
195
196 assert_eq!(params.get_usize("u32_max"), Some(4_294_967_295_usize));
197 #[cfg(target_pointer_width = "32")]
198 assert_eq!(params.get_usize("u32_overflow"), None);
199 #[cfg(target_pointer_width = "64")]
200 assert_eq!(params.get_usize("u32_overflow"), Some(4_294_967_296));
201 }
202}