use std::{
any::{Any, TypeId},
collections::{HashMap, hash_map::Entry},
fmt,
hash::{BuildHasherDefault, Hasher},
iter,
sync::Arc,
};
use crate::context::BindingId;
#[derive(Default, Debug, Clone)]
pub struct ContextMap {
entries: HashMap<TypeId, Binding, BuildHasherDefault<TypeIdHasher>>,
}
impl ContextMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert<T>(&mut self, value: T)
where
T: Any + Send + Sync,
{
self.push(TypeId::of::<T>(), Arc::new(value));
}
pub fn remove<T>(&mut self)
where
T: Any + Send + Sync,
{
self.entries.remove(&TypeId::of::<T>());
}
pub fn overwrite(&mut self, values: ContextMap) {
self.entries.extend(values.entries);
}
fn push(&mut self, type_id: TypeId, value: Arc<dyn Any + Send + Sync>) {
let previous = self.entries.remove(&type_id).map(Arc::new);
let binding = Binding {
id: BindingId::new(),
value,
previous,
};
self.entries.insert(type_id, binding);
}
#[must_use]
pub fn contains<T>(&self) -> bool
where
T: Any + Send + Sync,
{
self.entries.contains_key(&TypeId::of::<T>())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn get<T>(&self) -> Option<&T>
where
T: Any + Send + Sync,
{
Some(self.lookup::<T>()?.1)
}
pub(crate) fn lookup<T>(&self) -> Option<(BindingId, &T)>
where
T: Any + Send + Sync,
{
let binding = self.entries.get(&TypeId::of::<T>())?;
Some((binding.id, binding.value.downcast_ref()?))
}
pub fn iter<T>(&self) -> impl Iterator<Item = &T>
where
T: Any + Send + Sync,
{
iter::successors(self.entries.get(&TypeId::of::<T>()), |binding| {
binding.previous.as_deref()
})
.filter_map(|binding| binding.value.downcast_ref())
}
pub(crate) fn binding_id(&self, type_id: TypeId) -> Option<BindingId> {
Some(self.entries.get(&type_id)?.id)
}
}
pub trait ContextValues {
fn install(self, context: &mut ContextMap);
}
impl ContextValues for ContextMap {
fn install(self, context: &mut ContextMap) {
for (type_id, binding) in self.entries {
if let Entry::Vacant(entry) = context.entries.entry(type_id) {
entry.insert(binding);
continue;
}
let values: Vec<_> =
iter::successors(Some(&binding), |binding| binding.previous.as_deref())
.map(|binding| Arc::clone(&binding.value))
.collect();
for value in values.into_iter().rev() {
context.push(type_id, value);
}
}
}
}
macro_rules! impl_context_values {
($($value:ident: $type:ident),+) => {
impl<$($type),+> ContextValues for ($($type,)+)
where
$($type: Any + Send + Sync),+
{
fn install(self, context: &mut ContextMap) {
let ($($value,)+) = self;
$(context.insert($value);)+
}
}
};
}
impl_context_values!(a: A);
impl_context_values!(a: A, b: B);
impl_context_values!(a: A, b: B, c: C);
impl_context_values!(a: A, b: B, c: C, d: D);
impl_context_values!(a: A, b: B, c: C, d: D, e: E);
impl_context_values!(a: A, b: B, c: C, d: D, e: E, f: F);
impl_context_values!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
impl_context_values!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
#[derive(Clone)]
struct Binding {
id: BindingId,
value: Arc<dyn Any + Send + Sync>,
previous: Option<Arc<Binding>>,
}
impl fmt::Debug for Binding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Binding")
.field("id", &self.id)
.field("previous", &self.previous)
.finish_non_exhaustive()
}
}
#[derive(Default)]
struct TypeIdHasher(u64);
impl Hasher for TypeIdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.0 = self.0.rotate_left(8) ^ u64::from(byte);
}
}
fn write_u64(&mut self, n: u64) {
self.0 = n;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Database(&'static str);
#[derive(Debug, PartialEq)]
struct Config(u32);
fn binding_id<T: Any>(context: &ContextMap) -> BindingId {
context
.binding_id(TypeId::of::<T>())
.expect("binding is registered")
}
#[test]
fn register_and_get_returns_value() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
assert_eq!(context.get::<Database>(), Some(&Database("primary")));
}
#[test]
fn get_returns_none_for_unregistered_type() {
let context = ContextMap::new();
assert_eq!(context.get::<Database>(), None);
}
#[test]
fn multiple_types_coexist() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
context.insert(Config(42));
assert_eq!(context.get::<Database>(), Some(&Database("primary")));
assert_eq!(context.get::<Config>(), Some(&Config(42)));
}
#[test]
fn insert_shadows_the_previous_value() {
let mut context = ContextMap::new();
context.insert(Config(1));
context.insert(Config(2));
context.insert(Config(3));
assert_eq!(context.get::<Config>(), Some(&Config(3)));
assert_eq!(
context.iter::<Config>().collect::<Vec<_>>(),
[&Config(3), &Config(2), &Config(1)]
);
}
#[test]
fn insert_issues_a_fresh_id() {
let mut context = ContextMap::new();
context.insert(Config(1));
let first = binding_id::<Config>(&context);
context.insert(Config(2));
assert_ne!(binding_id::<Config>(&context), first);
}
#[test]
fn iter_is_empty_for_unregistered_type() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
assert_eq!(context.iter::<Config>().count(), 0);
}
#[test]
fn remove_drops_every_value_of_the_type() {
let mut context = ContextMap::new();
context.insert(Config(1));
context.insert(Config(2));
context.insert(Database("primary"));
context.remove::<Config>();
assert_eq!(context.iter::<Config>().count(), 0);
assert_eq!(context.get::<Database>(), Some(&Database("primary")));
}
#[test]
fn overwrite_replaces_values_of_the_same_types() {
let mut context = ContextMap::new();
context.insert(Config(1));
context.insert(Config(2));
context.insert(Database("primary"));
let mut values = ContextMap::new();
values.insert(Config(3));
context.overwrite(values);
assert_eq!(context.iter::<Config>().collect::<Vec<_>>(), [&Config(3)]);
assert_eq!(context.get::<Database>(), Some(&Database("primary")));
}
#[test]
fn contains_reports_registered_types() {
let mut context = ContextMap::new();
assert!(!context.contains::<Database>());
context.insert(Database("primary"));
assert!(context.contains::<Database>());
assert!(!context.contains::<Config>());
}
#[test]
fn clone_shares_values_and_ids() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
let clone = context.clone();
assert_eq!(clone.get::<Database>(), Some(&Database("primary")));
assert_eq!(
binding_id::<Database>(&clone),
binding_id::<Database>(&context)
);
}
#[test]
fn inserting_into_a_clone_leaves_the_original_untouched() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
let mut clone = context.clone();
clone.insert(Database("replica"));
assert_eq!(
context.iter::<Database>().collect::<Vec<_>>(),
[&Database("primary")]
);
assert_eq!(
clone.iter::<Database>().collect::<Vec<_>>(),
[&Database("replica"), &Database("primary")]
);
}
#[test]
fn context_values_installs_every_tuple_element() {
let mut context = ContextMap::new();
(Database("primary"), Config(42)).install(&mut context);
assert_eq!(context.get::<Database>(), Some(&Database("primary")));
assert_eq!(context.get::<Config>(), Some(&Config(42)));
}
#[test]
fn installing_a_tuple_shadows_in_order() {
let mut context = ContextMap::new();
context.insert(Config(1));
(Config(2), Config(3)).install(&mut context);
assert_eq!(
context.iter::<Config>().collect::<Vec<_>>(),
[&Config(3), &Config(2), &Config(1)]
);
}
#[test]
fn installing_a_map_shadows_in_order() {
let mut context = ContextMap::new();
context.insert(Config(0));
let mut values = ContextMap::new();
values.insert(Config(1));
values.insert(Config(2));
let installed = binding_id::<Config>(&values);
values.install(&mut context);
assert_eq!(
context.iter::<Config>().collect::<Vec<_>>(),
[&Config(2), &Config(1), &Config(0)]
);
assert_ne!(binding_id::<Config>(&context), installed);
}
#[test]
fn installing_a_map_without_overlap_keeps_its_ids() {
let mut context = ContextMap::new();
context.insert(Database("primary"));
let mut values = ContextMap::new();
values.insert(Config(1));
values.insert(Config(2));
let installed = binding_id::<Config>(&values);
values.install(&mut context);
assert_eq!(
context.iter::<Config>().collect::<Vec<_>>(),
[&Config(2), &Config(1)]
);
assert_eq!(binding_id::<Config>(&context), installed);
}
}