use alloc::sync::Arc;
use alloc::vec::Vec;
use core::any::TypeId;
use core::fmt::{self, Debug};
use crate::extensions::{DebugAny, TypeKey};
type Entry = (TypeKey, Arc<dyn DebugAny>);
#[cfg_attr(feature = "io", doc = "[io-module]: crate::io")]
#[cfg_attr(
not(feature = "io"),
doc = "[io-module]: https://docs.rs/deser/latest/deser/io/"
)]
#[cfg_attr(feature = "open-enums", doc = "[open-enums]: crate::OpenEnums")]
#[cfg_attr(
not(feature = "open-enums"),
doc = "[open-enums]: https://docs.rs/deser/latest/deser/struct.OpenEnums.html"
)]
#[derive(Clone, Default)]
pub struct Context {
values: Option<Arc<Vec<Entry>>>,
}
impl Context {
pub const fn new() -> Context {
Context { values: None }
}
pub fn with<T: Debug + Send + Sync + 'static>(value: T) -> Context {
let mut context = Context::new();
context.set(value);
context
}
pub fn set<T: Debug + Send + Sync + 'static>(&mut self, value: T) {
let values = Arc::make_mut(self.values.get_or_insert_with(Default::default));
let value: Arc<dyn DebugAny> = Arc::new(value);
match values
.iter_mut()
.find(|(key, _)| key.0 == TypeId::of::<T>())
{
Some(entry) => entry.1 = value,
None => values.push((TypeKey::of::<T>(), value)),
}
}
#[inline]
pub fn get<T: Debug + Send + Sync + 'static>(&self) -> Option<&T> {
let values = self.values.as_deref()?;
self.lookup(values, TypeId::of::<T>()).map(|value| {
unsafe { value.downcast_ref_unchecked::<T>() }
})
}
#[inline]
fn lookup<'a>(&self, values: &'a [Entry], key: TypeId) -> Option<&'a Arc<dyn DebugAny>> {
values
.iter()
.find(|(k, _)| k.0 == key)
.map(|(_, value)| value)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.values.as_ref().is_none_or(|values| values.is_empty())
}
pub(crate) fn fill_from(&mut self, defaults: &Context) -> bool {
let Some(ref defaults) = defaults.values else {
return false;
};
let own = match self.values {
Some(ref mut own) if !own.is_empty() => own,
_ => {
self.values = Some(defaults.clone());
return !defaults.is_empty();
}
};
if Arc::ptr_eq(own, defaults) {
return false;
}
let missing: Vec<Entry> = defaults
.iter()
.filter(|(key, _)| !own.iter().any(|(own_key, _)| own_key.0 == key.0))
.cloned()
.collect();
if missing.is_empty() {
return false;
}
Arc::make_mut(own).extend(missing);
true
}
}
impl PartialEq for Context {
fn eq(&self, other: &Context) -> bool {
match (&self.values, &other.values) {
(Some(a), Some(b)) if Arc::ptr_eq(a, b) => true,
_ => self.is_empty() && other.is_empty(),
}
}
}
impl Eq for Context {}
impl core::panic::UnwindSafe for Context {}
impl core::panic::RefUnwindSafe for Context {}
impl Debug for Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut map = f.debug_map();
if let Some(ref values) = self.values {
map.entries(values.iter().map(|(key, value)| (key, value)));
}
map.finish()
}
}
#[test]
fn test_context() {
let empty = Context::new();
assert!(empty.is_empty());
assert_eq!(empty.get::<u32>(), None);
let mut context = Context::with(1u32);
context.set("x");
assert_eq!(context.get::<u32>(), Some(&1));
assert_eq!(context.get::<&str>(), Some(&"x"));
assert_eq!(context.get::<u64>(), None);
let mut other = context.clone();
other.set(2u32);
assert_eq!(context.get::<u32>(), Some(&1));
assert_eq!(other.get::<u32>(), Some(&2));
assert_eq!(other.get::<&str>(), Some(&"x"));
assert_eq!(format!("{:?}", other), r#"{u32: 2, &str: "x"}"#);
assert_eq!(context, context.clone());
assert_ne!(context, other);
assert_ne!(context, Context::with(1u32));
assert_eq!(empty, Context::default());
fn unwind_safe<T: core::panic::UnwindSafe + core::panic::RefUnwindSafe>() {}
unwind_safe::<Context>();
}
#[test]
fn test_fill_from() {
let defaults = {
let mut context = Context::with(1u32);
context.set("x");
context
};
let mut context = Context::new();
assert!(context.fill_from(&defaults));
assert_eq!(context, defaults);
assert!(!context.fill_from(&defaults));
assert!(!context.fill_from(&Context::new()));
let mut context = Context::with(2u32);
assert!(context.fill_from(&defaults));
assert_eq!(context.get::<u32>(), Some(&2));
assert_eq!(context.get::<&str>(), Some(&"x"));
assert!(!context.fill_from(&defaults));
assert_eq!(defaults.get::<u32>(), Some(&1));
}