use std::any::{Any, TypeId, type_name};
use std::collections::HashMap;
use std::fmt::Debug;
pub trait Context: Debug {}
impl<'c, C> Context for &'c C where C: Context {}
pub trait ContextProvides<V> {
fn provide(&self) -> &V;
}
impl<'c, C, V> ContextProvides<V> for &'c C
where
C: ContextProvides<V>,
{
fn provide(&self) -> &V {
(**self).provide()
}
}
#[derive(Debug, Default)]
pub struct DynContext {
backing: HashMap<TypeId, Box<dyn Any>>,
}
impl DynContext {
pub fn provide_if_set<T: 'static>(&self) -> Option<&T> {
let found = self.backing.get(&TypeId::of::<T>());
found.map(|any| any.downcast_ref().unwrap())
}
}
impl Context for DynContext {}
impl<V> ContextProvides<V> for DynContext
where
V: 'static,
{
fn provide(&self) -> &V {
self.provide_if_set()
.unwrap_or_else(|| panic!("Context does not provide {:?}", type_name::<V>()))
}
}
#[derive(Debug, Default)]
pub struct DynContextBuilder(HashMap<TypeId, Box<dyn Any>>);
impl DynContextBuilder {
pub fn add_key<T: 'static>(&mut self, value: T) -> &mut Self {
self.0.insert(TypeId::of::<T>(), Box::new(value));
self
}
pub fn build(self) -> DynContext {
DynContext { backing: self.0 }
}
}
#[derive(Copy, Clone, Debug)]
pub struct EmptyContext;
impl Context for EmptyContext {}
#[derive(Clone, Debug)]
pub struct SingularContext<V>(pub V);
impl<V: Debug> Context for SingularContext<V> {}
impl<V> ContextProvides<V> for SingularContext<V> {
fn provide(&self) -> &V {
&self.0
}
}
#[macro_export]
macro_rules! static_context_provides {
($struct_name:ty, $field_type:ident, $field_name:ident) => {
impl $crate::context::ContextProvides<$field_type> for $struct_name {
fn provide(&self) -> &$field_type {
&self.$field_name
}
}
};
($struct_name:ty, $field_type:ident, $field_name:ident, $($further:ident),+) => {
impl $crate::context::ContextProvides<$field_type> for $struct_name {
fn provide(&self) -> &$field_type {
&self.$field_name
}
}
static_context_provides!($struct_name, $($further),+);
}
}
#[cfg(test)]
mod tests {
use super::ContextProvides;
pub struct StaticContextExample {
pub edition: u16,
pub name: String,
}
static_context_provides!(StaticContextExample, u16, edition, String, name);
#[test]
fn provide_same_values() {
let example = StaticContextExample {
edition: 32,
name: "test".to_string(),
};
let edition: &u16 = example.provide();
let name: &String = example.provide();
assert_eq!(*edition, 32);
assert_eq!(name, "test");
}
}