1use std::{
22 fmt::Debug,
23 hash::Hash,
24 ops::Deref,
25 sync::{Arc, OnceLock},
26};
27
28use indexmap::{Equivalent, IndexMap};
29use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap};
30use serde_json::Value;
31
32#[derive(Clone)]
83pub struct Params(Storage);
84
85impl Params {
86 #[must_use]
88 pub fn empty() -> Self {
89 Self::new(IndexMap::new())
90 }
91
92 #[must_use]
94 pub fn new(map: IndexMap<String, Value>) -> Self {
95 Self(Storage::Owned(map))
96 }
97
98 #[must_use]
100 pub fn from_shared(map: IndexMap<String, Arc<Value>>) -> Self {
101 Self(Storage::Shared(Arc::new(SharedStorage {
102 values: map,
103 owned: OnceLock::new(),
104 })))
105 }
106
107 #[must_use]
111 pub fn to_shared_map(&self) -> IndexMap<String, Arc<Value>> {
112 match &self.0 {
113 Storage::Owned(values) => values
114 .iter()
115 .map(|(key, value)| (key.clone(), Arc::new(value.clone())))
116 .collect(),
117 Storage::Shared(shared) => shared.values.clone(),
118 }
119 }
120
121 pub fn borrowed_entries(&self) -> impl DoubleEndedIterator<Item = (&String, &Value)> {
123 let (owned, shared) = match &self.0 {
124 Storage::Owned(values) => (Some(values), None),
125 Storage::Shared(shared) => (None, Some(&shared.values)),
126 };
127
128 owned.into_iter().flat_map(IndexMap::iter).chain(
129 shared
130 .into_iter()
131 .flat_map(IndexMap::iter)
132 .map(|(key, value)| (key, value.as_ref())),
133 )
134 }
135
136 #[must_use]
138 pub fn len(&self) -> usize {
139 match &self.0 {
140 Storage::Owned(values) => values.len(),
141 Storage::Shared(shared) => shared.values.len(),
142 }
143 }
144
145 #[must_use]
147 pub fn is_empty(&self) -> bool {
148 self.len() == 0
149 }
150
151 #[must_use]
153 pub fn capacity(&self) -> usize {
154 match &self.0 {
155 Storage::Owned(values) => values.capacity(),
156 Storage::Shared(shared) => shared.values.capacity(),
157 }
158 }
159
160 #[must_use]
162 pub fn get<Q>(&self, key: &Q) -> Option<&Value>
163 where
164 Q: ?Sized + Hash + Equivalent<String>,
165 {
166 match &self.0 {
167 Storage::Owned(values) => values.get(key),
168 Storage::Shared(shared) => shared.values.get(key).map(AsRef::as_ref),
169 }
170 }
171
172 #[must_use]
174 pub fn contains_key<Q>(&self, key: &Q) -> bool
175 where
176 Q: ?Sized + Hash + Equivalent<String>,
177 {
178 self.get(key).is_some()
179 }
180
181 #[must_use]
186 pub fn estimated_heap_size(&self) -> usize {
187 match &self.0 {
188 Storage::Owned(values) => map_heap_size(values),
189 Storage::Shared(shared) => shared.values.iter().fold(
190 shared.owned.get().map_or(0, map_heap_size).saturating_add(
191 shared
192 .values
193 .capacity()
194 .saturating_mul(size_of::<(usize, String, Arc<Value>)>()),
195 ),
196 |bytes, (key, value)| {
197 bytes
198 .saturating_add(key.capacity())
199 .saturating_add(size_of::<(usize, usize, Value)>())
200 .saturating_add(json_heap_size(value))
201 },
202 ),
203 }
204 }
205
206 #[must_use]
210 pub fn get_u64(&self, key: &str) -> Option<u64> {
211 self.get(key).and_then(Value::as_u64)
212 }
213
214 #[must_use]
218 pub fn get_i64(&self, key: &str) -> Option<i64> {
219 self.get(key).and_then(Value::as_i64)
220 }
221
222 #[must_use]
226 pub fn get_usize(&self, key: &str) -> Option<usize> {
227 self.get(key)
228 .and_then(Value::as_u64)
229 .and_then(|n| usize::try_from(n).ok())
230 }
231
232 #[must_use]
236 pub fn get_str(&self, key: &str) -> Option<&str> {
237 self.get(key).and_then(|v| v.as_str())
238 }
239
240 #[must_use]
244 pub fn get_bool(&self, key: &str) -> Option<bool> {
245 self.get(key).and_then(Value::as_bool)
246 }
247
248 #[must_use]
252 pub fn get_f64(&self, key: &str) -> Option<f64> {
253 self.get(key).and_then(Value::as_f64)
254 }
255
256 #[cfg(feature = "python")]
257 pub fn to_pydict(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
263 crate::python::params::params_to_pydict(py, self)
264 }
265}
266
267impl Deref for Params {
268 type Target = IndexMap<String, Value>;
269
270 fn deref(&self) -> &Self::Target {
271 match &self.0 {
272 Storage::Owned(values) => values,
273 Storage::Shared(shared) => shared.owned.get_or_init(|| {
274 shared
275 .values
276 .iter()
277 .map(|(key, value)| (key.clone(), value.as_ref().clone()))
278 .collect()
279 }),
280 }
281 }
282}
283
284impl<'a> IntoIterator for &'a Params {
285 type Item = (&'a String, &'a Value);
286 type IntoIter = indexmap::map::Iter<'a, String, Value>;
287
288 fn into_iter(self) -> Self::IntoIter {
289 self.deref().iter()
290 }
291}
292
293impl Default for Params {
294 fn default() -> Self {
295 Self::empty()
296 }
297}
298
299impl Debug for Params {
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 match &self.0 {
302 Storage::Owned(values) => f.debug_tuple("Params").field(values).finish(),
303 Storage::Shared(shared) => f.debug_tuple("Params").field(&shared.values).finish(),
304 }
305 }
306}
307
308impl PartialEq for Params {
309 fn eq(&self, other: &Self) -> bool {
310 self.len() == other.len()
311 && self
312 .borrowed_entries()
313 .all(|(key, value)| other.get(key) == Some(value))
314 }
315}
316
317impl Eq for Params {}
318
319impl Serialize for Params {
320 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
321 let mut map = serializer.serialize_map(Some(self.len()))?;
322 for (key, value) in self.borrowed_entries() {
323 map.serialize_entry(key, value)?;
324 }
325
326 map.end()
327 }
328}
329
330impl<'de> Deserialize<'de> for Params {
331 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
332 IndexMap::deserialize(deserializer).map(Self::new)
333 }
334}
335
336#[derive(Clone)]
337enum Storage {
338 Owned(IndexMap<String, Value>),
339 Shared(Arc<SharedStorage>),
340}
341
342struct SharedStorage {
343 values: IndexMap<String, Arc<Value>>,
344 owned: OnceLock<IndexMap<String, Value>>,
345}
346
347fn map_heap_size(values: &IndexMap<String, Value>) -> usize {
348 values.iter().fold(
349 values
350 .capacity()
351 .saturating_mul(size_of::<(usize, String, Value)>()),
352 |bytes, (key, value)| {
353 bytes
354 .saturating_add(key.capacity())
355 .saturating_add(json_heap_size(value))
356 },
357 )
358}
359
360fn json_heap_size(value: &Value) -> usize {
361 match value {
362 Value::String(value) => value.capacity(),
363 Value::Array(values) => values.iter().fold(
364 values.capacity().saturating_mul(size_of::<Value>()),
365 |bytes, value| bytes.saturating_add(json_heap_size(value)),
366 ),
367 Value::Object(values) => values.iter().fold(0usize, |bytes, (key, value)| {
368 bytes
369 .saturating_add(size_of::<(String, Value)>())
370 .saturating_add(key.capacity())
371 .saturating_add(json_heap_size(value))
372 }),
373 _ => 0,
374 }
375}
376
377#[cfg(feature = "python")]
378pub fn from_pydict(
389 py: pyo3::Python<'_>,
390 dict: &pyo3::Py<pyo3::types::PyDict>,
391) -> pyo3::PyResult<Option<Params>> {
392 crate::python::params::pydict_to_params(py, dict)
393}
394
395#[cfg(test)]
396mod tests {
397 use rstest::*;
398 use serde_json::json;
399
400 use super::*;
401
402 fn create_test_params() -> Params {
403 Params::new(
404 [
405 ("u64_val".to_string(), json!(42u64)),
406 ("i64_val".to_string(), json!(-100i64)),
407 ("usize_val".to_string(), json!(5u64)),
408 ("str_val".to_string(), json!("hello")),
409 ("bool_val".to_string(), json!(true)),
410 ("f64_val".to_string(), json!(2.5)),
411 ]
412 .into(),
413 )
414 }
415
416 #[rstest]
417 fn test_params_getters() {
418 let params = create_test_params();
419
420 assert_eq!(params.get_u64("u64_val"), Some(42));
421 assert_eq!(params.get_i64("i64_val"), Some(-100));
422 assert_eq!(params.get_usize("usize_val"), Some(5));
423 assert_eq!(params.get_str("str_val"), Some("hello"));
424 assert_eq!(params.get_bool("bool_val"), Some(true));
425 assert_eq!(params.get_f64("f64_val"), Some(2.5));
426 assert_eq!(params.get_u64("missing"), None);
427 assert_eq!(params.get_i64("missing"), None);
428 assert_eq!(params.get_usize("missing"), None);
429 assert_eq!(params.get_str("missing"), None);
430 assert_eq!(params.get_bool("missing"), None);
431 assert_eq!(params.get_f64("missing"), None);
432 assert_eq!(params.get_u64("str_val"), None);
433 assert_eq!(params.get_str("u64_val"), None);
434 }
435
436 #[rstest]
437 #[case(false)]
438 #[case(true)]
439 fn test_params_shared_reads_and_serialization(#[case] materialize: bool) {
440 let original = create_test_params();
441 let shared = Params::from_shared(original.to_shared_map());
442 let cloned = shared.clone();
443 let retained = shared.estimated_heap_size();
444 let json = serde_json::to_string(&original).unwrap();
445 let msgpack = rmp_serde::to_vec_named(&original).unwrap();
446
447 assert!(std::ptr::eq(
448 shared.get("str_val").unwrap(),
449 cloned.get("str_val").unwrap()
450 ));
451 assert_eq!(shared, original);
452 assert_eq!(format!("{shared:?}"), format!("{original:?}"));
453 assert_eq!(shared.len(), 6);
454 assert!(!shared.is_empty());
455 assert!(shared.contains_key("str_val"));
456 assert!(!shared.contains_key("missing"));
457 assert_eq!(shared.get_str("str_val"), Some("hello"));
458 assert_eq!(shared.get_u64("u64_val"), Some(42));
459 assert_eq!(shared.get_i64("i64_val"), Some(-100));
460 assert_eq!(shared.get_usize("usize_val"), Some(5));
461 assert_eq!(shared.get_bool("bool_val"), Some(true));
462 assert_eq!(shared.get_f64("f64_val"), Some(2.5));
463 assert_eq!(serde_json::to_string(&shared).unwrap(), json);
464 assert_eq!(rmp_serde::to_vec_named(&shared).unwrap(), msgpack);
465 assert_eq!(shared.estimated_heap_size(), retained);
466
467 if materialize {
468 let map: &IndexMap<String, Value> = &shared;
469 let cloned_map: &IndexMap<String, Value> = &cloned;
470 assert!(std::ptr::eq(map, cloned_map));
471 assert_eq!(map, &*original);
472 assert_eq!(shared["str_val"], json!("hello"));
473 assert_eq!(
474 (&shared).into_iter().collect::<Vec<_>>(),
475 original.iter().collect::<Vec<_>>()
476 );
477 }
478
479 assert_eq!(
480 shared.borrowed_entries().collect::<Vec<_>>(),
481 original.iter().collect::<Vec<_>>()
482 );
483 assert_eq!(
484 shared.borrowed_entries().rev().collect::<Vec<_>>(),
485 original.iter().rev().collect::<Vec<_>>()
486 );
487 assert_eq!(serde_json::from_str::<Params>(&json).unwrap(), shared);
488 assert_eq!(rmp_serde::from_slice::<Params>(&msgpack).unwrap(), shared);
489 }
490
491 #[rstest]
492 fn test_params_derivation_preserves_original_and_shared_values() {
493 let event = Arc::new(json!({"markets": ["first", "second"], "unknown": {"count": 7}}));
494 let original = Params::from_shared(
495 [
496 ("event".to_string(), Arc::clone(&event)),
497 ("closed".to_string(), Arc::new(json!(false))),
498 ]
499 .into(),
500 );
501 let mut map = original.to_shared_map();
502 map.insert("closed".to_string(), Arc::new(json!(true)));
503 let derived = Params::from_shared(map);
504 let mut owned = IndexMap::clone(&original);
505 owned["event"]["markets"][0] = json!("changed");
506 let modified = Params::new(owned);
507
508 assert_eq!(original.get_bool("closed"), Some(false));
509 assert_eq!(derived.get_bool("closed"), Some(true));
510 assert_eq!(original.get("event"), Some(event.as_ref()));
511 assert_eq!(derived.get("event"), Some(event.as_ref()));
512 assert!(std::ptr::eq(derived.get("event").unwrap(), event.as_ref()));
513 assert_eq!(modified["event"]["markets"][0], json!("changed"));
514 assert_eq!(original["event"]["markets"][0], json!("first"));
515 assert_eq!(derived["event"]["markets"][0], json!("first"));
516 }
517
518 #[rstest]
519 fn test_params_shared_storage_accounts_for_materialization() {
520 let mut key = String::with_capacity(31);
521 key.push_str("event");
522 let mut text = String::with_capacity(137);
523 text.push_str("snapshot");
524 let contents = key.capacity() + text.capacity();
525 let params = Params::from_shared([(key, Arc::new(Value::String(text)))].into());
526 let shared_bytes = params.capacity() * size_of::<(usize, String, Arc<Value>)>()
527 + size_of::<(usize, usize, Value)>()
528 + contents;
529 assert_eq!(params.estimated_heap_size(), shared_bytes);
530
531 let map: &IndexMap<String, Value> = ¶ms;
532
533 let (key, Value::String(value)) = map.get_index(0).unwrap() else {
534 unreachable!()
535 };
536
537 let owned_bytes = map.capacity() * size_of::<(usize, String, Value)>()
538 + key.capacity()
539 + value.capacity();
540 assert_eq!(params.estimated_heap_size(), shared_bytes + owned_bytes);
541 }
542
543 #[rstest]
544 fn test_params_empty_and_order_independent_equality() {
545 let first = Params::from_shared(
546 [
547 ("z".to_string(), Arc::new(json!(null))),
548 ("a".to_string(), Arc::new(json!([1, {"nested": true}]))),
549 ]
550 .into(),
551 );
552
553 let reverse = Params::new(
554 first
555 .borrowed_entries()
556 .rev()
557 .map(|(key, value)| (key.clone(), value.clone()))
558 .collect(),
559 );
560 let empty = Params::from_shared(IndexMap::new());
561
562 assert_eq!(first, reverse);
563 assert_eq!(
564 serde_json::to_string(&first).unwrap(),
565 r#"{"z":null,"a":[1,{"nested":true}]}"#
566 );
567 assert_eq!(empty, Params::empty());
568 assert!(empty.is_empty());
569 assert_eq!(empty.len(), 0);
570 assert_eq!(empty.get("missing"), None);
571 assert_eq!(empty.borrowed_entries().count(), 0);
572 assert_eq!(serde_json::to_string(&empty).unwrap(), "{}");
573 }
574
575 #[rstest]
576 fn test_params_ref_get_usize_respects_target_width() {
577 let params = Params::new(
578 [
579 ("u32_max".to_string(), json!(u32::MAX)),
580 ("u32_overflow".to_string(), json!(4_294_967_296_u64)),
581 ]
582 .into(),
583 );
584
585 assert_eq!(params.get_usize("u32_max"), Some(4_294_967_295_usize));
586 #[cfg(target_pointer_width = "32")]
587 assert_eq!(params.get_usize("u32_overflow"), None);
588 #[cfg(target_pointer_width = "64")]
589 assert_eq!(params.get_usize("u32_overflow"), Some(4_294_967_296));
590 }
591}