use std::ops::{Deref, DerefMut};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Params(IndexMap<String, Value>);
impl Params {
#[must_use]
pub fn new() -> Self {
Self(IndexMap::new())
}
#[must_use]
pub fn from_index_map(map: IndexMap<String, Value>) -> Self {
Self(map)
}
#[must_use]
pub fn get_u64(&self, key: &str) -> Option<u64> {
self.get(key).and_then(|v| v.as_u64())
}
#[must_use]
pub fn get_i64(&self, key: &str) -> Option<i64> {
self.get(key).and_then(|v| v.as_i64())
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "usize is 64-bit on all supported targets"
)]
pub fn get_usize(&self, key: &str) -> Option<usize> {
self.get(key).and_then(|v| v.as_u64()).map(|n| n as usize)
}
#[must_use]
pub fn get_str(&self, key: &str) -> Option<&str> {
self.get(key).and_then(|v| v.as_str())
}
#[must_use]
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key).and_then(|v| v.as_bool())
}
#[must_use]
pub fn get_f64(&self, key: &str) -> Option<f64> {
self.get(key).and_then(|v| v.as_f64())
}
#[cfg(feature = "python")]
pub fn to_pydict(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
crate::python::params::params_to_pydict(py, self)
}
}
impl Deref for Params {
type Target = IndexMap<String, Value>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Params {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> IntoIterator for &'a Params {
type Item = (&'a String, &'a Value);
type IntoIter = indexmap::map::Iter<'a, String, Value>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[cfg(feature = "python")]
pub fn from_pydict(
py: pyo3::Python<'_>,
dict: pyo3::Py<pyo3::types::PyDict>,
) -> pyo3::PyResult<Option<Params>> {
crate::python::params::pydict_to_params(py, dict)
}
#[cfg(test)]
mod tests {
use rstest::*;
use serde_json::json;
use super::Params;
fn create_test_params() -> Params {
let mut params = Params::new();
params.insert("u64_val".to_string(), json!(42u64));
params.insert("i64_val".to_string(), json!(-100i64));
params.insert("usize_val".to_string(), json!(5u64));
params.insert("str_val".to_string(), json!("hello"));
params.insert("bool_val".to_string(), json!(true));
params.insert("f64_val".to_string(), json!(2.5));
params
}
#[rstest]
fn test_params_option_get_u64() {
let params = Some(create_test_params());
assert_eq!(params.as_ref().and_then(|p| p.get_u64("u64_val")), Some(42));
assert_eq!(params.as_ref().and_then(|p| p.get_u64("missing")), None);
assert_eq!(params.as_ref().and_then(|p| p.get_u64("str_val")), None);
}
#[rstest]
fn test_params_option_get_i64() {
let params = Some(create_test_params());
assert_eq!(
params.as_ref().and_then(|p| p.get_i64("i64_val")),
Some(-100)
);
assert_eq!(params.as_ref().and_then(|p| p.get_i64("missing")), None);
}
#[rstest]
fn test_params_option_get_usize() {
let params = Some(create_test_params());
assert_eq!(
params.as_ref().and_then(|p| p.get_usize("usize_val")),
Some(5)
);
assert_eq!(params.as_ref().and_then(|p| p.get_usize("missing")), None);
}
#[rstest]
fn test_params_option_get_str() {
let params = Some(create_test_params());
assert_eq!(
params.as_ref().and_then(|p| p.get_str("str_val")),
Some("hello")
);
assert_eq!(params.as_ref().and_then(|p| p.get_str("missing")), None);
assert_eq!(params.as_ref().and_then(|p| p.get_str("u64_val")), None);
}
#[rstest]
fn test_params_option_get_bool() {
let params = Some(create_test_params());
assert_eq!(
params.as_ref().and_then(|p| p.get_bool("bool_val")),
Some(true)
);
assert_eq!(params.as_ref().and_then(|p| p.get_bool("missing")), None);
}
#[rstest]
fn test_params_option_get_f64() {
let params = Some(create_test_params());
assert_eq!(
params.as_ref().and_then(|p| p.get_f64("f64_val")),
Some(2.5)
);
assert_eq!(params.as_ref().and_then(|p| p.get_f64("missing")), None);
}
#[rstest]
fn test_params_option_none() {
let params: Option<Params> = None;
assert_eq!(params.as_ref().and_then(|p| p.get_u64("any")), None);
assert_eq!(params.as_ref().and_then(|p| p.get_str("any")), None);
}
#[rstest]
fn test_params_ref_get_u64() {
let params = create_test_params();
assert_eq!(params.get_u64("u64_val"), Some(42));
assert_eq!(params.get_u64("missing"), None);
}
#[rstest]
fn test_params_ref_get_usize() {
let params = create_test_params();
assert_eq!(params.get_usize("usize_val"), Some(5));
assert_eq!(params.get_usize("missing"), None);
}
#[rstest]
fn test_params_ref_get_str() {
let params = create_test_params();
assert_eq!(params.get_str("str_val"), Some("hello"));
assert_eq!(params.get_str("missing"), None);
}
#[rstest]
fn test_submit_tries_pattern() {
let mut params = Params::new();
params.insert("submit_tries".to_string(), json!(3u64));
let cmd_params = Some(params);
let submit_tries = cmd_params
.as_ref()
.and_then(|p| p.get_usize("submit_tries"))
.filter(|&n| n > 0);
assert_eq!(submit_tries, Some(3));
}
#[rstest]
fn test_submit_tries_pattern_zero_filtered() {
let mut params = Params::new();
params.insert("submit_tries".to_string(), json!(0u64));
let cmd_params = Some(params);
let submit_tries = cmd_params
.as_ref()
.and_then(|p| p.get_usize("submit_tries"))
.filter(|&n| n > 0);
assert_eq!(submit_tries, None);
}
#[rstest]
fn test_submit_tries_pattern_missing() {
let cmd_params: Option<Params> = None;
let submit_tries = cmd_params
.as_ref()
.and_then(|p| p.get_usize("submit_tries"))
.filter(|&n| n > 0);
assert_eq!(submit_tries, None);
}
}