use std::any::{Any, TypeId, type_name};
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::hash::{BuildHasherDefault, Hasher};
#[derive(Default)]
pub struct Depot {
named: HashMap<String, Box<dyn Any + Send + Sync>>,
typed: TypedMap,
}
type TypedMap = HashMap<TypeId, TypedEntry, BuildHasherDefault<TypeIdHasher>>;
#[derive(Default)]
struct TypeIdHasher(u64);
impl Hasher for TypeIdHasher {
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = self.0.rotate_left(8) ^ u64::from(b);
}
}
#[inline]
fn write_u64(&mut self, n: u64) {
self.0 = n;
}
#[inline]
fn write_u128(&mut self, n: u128) {
self.0 = n as u64;
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
struct TypedEntry {
type_name: &'static str,
value: Box<dyn Any + Send + Sync>,
}
impl TypedEntry {
#[inline]
fn new<T: Any + Send + Sync>(value: T) -> Self {
Self {
type_name: type_name::<T>(),
value: Box::new(value),
}
}
}
impl Depot {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
named: HashMap::new(),
typed: TypedMap::default(),
}
}
#[inline]
#[must_use]
pub fn inner(&self) -> &HashMap<String, Box<dyn Any + Send + Sync>> {
&self.named
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
named: HashMap::with_capacity(capacity),
typed: TypedMap::default(),
}
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
self.named.capacity()
}
#[inline]
pub fn insert_typed<V: Any + Send + Sync>(&mut self, value: V) -> &mut Self {
self.typed.insert(TypeId::of::<V>(), TypedEntry::new(value));
self
}
#[inline]
#[deprecated(since = "0.94.0", note = "use `Depot::insert_typed` instead")]
pub fn inject<V: Any + Send + Sync>(&mut self, value: V) -> &mut Self {
self.insert_typed(value)
}
#[inline]
pub fn get_typed<T: Any + Send + Sync>(
&self,
) -> Result<&T, Option<&Box<dyn Any + Send + Sync>>> {
if let Some(entry) = self.typed.get(&TypeId::of::<T>()) {
entry.value.downcast_ref::<T>().ok_or(Some(&entry.value))
} else {
Err(None)
}
}
#[inline]
#[deprecated(since = "0.94.0", note = "use `Depot::get_typed` instead")]
pub fn obtain<T: Any + Send + Sync>(&self) -> Result<&T, Option<&Box<dyn Any + Send + Sync>>> {
self.get_typed::<T>()
}
#[inline]
pub fn get_typed_mut<T: Any + Send + Sync>(
&mut self,
) -> Result<&mut T, Option<&mut Box<dyn Any + Send + Sync>>> {
if let Some(entry) = self.typed.get_mut(&TypeId::of::<T>()) {
if entry.value.is::<T>() {
Ok(entry
.value
.downcast_mut::<T>()
.expect("downcast_mut should not fail"))
} else {
Err(Some(&mut entry.value))
}
} else {
Err(None)
}
}
#[inline]
#[deprecated(since = "0.94.0", note = "use `Depot::get_typed_mut` instead")]
pub fn obtain_mut<T: Any + Send + Sync>(
&mut self,
) -> Result<&mut T, Option<&mut Box<dyn Any + Send + Sync>>> {
self.get_typed_mut::<T>()
}
#[inline]
pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: Any + Send + Sync,
{
self.named.insert(key.into(), Box::new(value));
self
}
#[inline]
#[must_use]
pub fn contains_key(&self, key: &str) -> bool {
self.named.contains_key(key)
}
#[inline]
#[must_use]
pub fn contains_typed<T: Any + Send + Sync>(&self) -> bool {
self.typed.contains_key(&TypeId::of::<T>())
}
#[inline]
#[must_use]
#[deprecated(since = "0.94.0", note = "use `Depot::contains_typed` instead")]
pub fn contains<T: Any + Send + Sync>(&self) -> bool {
self.contains_typed::<T>()
}
#[inline]
pub fn get<V: Any + Send + Sync>(
&self,
key: &str,
) -> Result<&V, Option<&Box<dyn Any + Send + Sync>>> {
if let Some(value) = self.named.get(key) {
value.downcast_ref::<V>().ok_or(Some(value))
} else {
Err(None)
}
}
#[inline]
pub(crate) fn get_any(&self, key: &str) -> Option<&(dyn Any + Send + Sync)> {
self.named
.get(key)
.map(|v| &**v as &(dyn Any + Send + Sync))
}
pub fn get_mut<V: Any + Send + Sync>(
&mut self,
key: &str,
) -> Result<&mut V, Option<&mut Box<dyn Any + Send + Sync>>> {
if let Some(value) = self.named.get_mut(key) {
if value.is::<V>() {
Ok(value
.downcast_mut::<V>()
.expect("type checked by is::<V>() above"))
} else {
Err(Some(value))
}
} else {
Err(None)
}
}
#[inline]
pub fn remove(&mut self, key: &str) -> Option<Box<dyn Any + Send + Sync>> {
self.named.remove(key)
}
#[inline]
#[deprecated(
since = "0.94.0",
note = "use `Depot::remove` and check the returned `Option`"
)]
pub fn delete(&mut self, key: &str) -> bool {
self.remove(key).is_some()
}
#[inline]
pub fn remove_typed<T: Any + Send + Sync>(
&mut self,
) -> Result<T, Option<Box<dyn Any + Send + Sync>>> {
if let Some(entry) = self.typed.remove(&TypeId::of::<T>()) {
entry.value.downcast::<T>().map(|b| *b).map_err(Some)
} else {
Err(None)
}
}
#[inline]
#[deprecated(since = "0.94.0", note = "use `Depot::remove_typed` instead")]
pub fn scrape<T: Any + Send + Sync>(
&mut self,
) -> Result<T, Option<Box<dyn Any + Send + Sync>>> {
self.remove_typed::<T>()
}
}
impl Debug for Depot {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let types = self
.typed
.values()
.map(|entry| entry.type_name)
.collect::<Vec<_>>();
f.debug_struct("Depot")
.field("keys", &self.named.keys())
.field("types", &types)
.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::prelude::*;
use crate::test::{ResponseExt, TestClient};
#[test]
fn test_depot() {
let mut depot = Depot::with_capacity(6);
assert!(depot.capacity() >= 6);
depot.insert("one", "ONE".to_owned());
assert!(depot.contains_key("one"));
assert_eq!(depot.get::<String>("one").unwrap(), &"ONE".to_owned());
assert_eq!(
depot.get_mut::<String>("one").unwrap(),
&mut "ONE".to_owned()
);
}
#[test]
fn test_depot_typed() {
let mut depot = Depot::new();
assert!(depot.get_typed::<String>().is_err());
depot.insert_typed("typed".to_owned());
assert!(depot.contains_typed::<String>());
assert_eq!(depot.get_typed::<String>().unwrap(), "typed");
assert_eq!(depot.get_typed_mut::<String>().unwrap(), "typed");
assert_eq!(depot.remove_typed::<String>().unwrap(), "typed");
assert!(!depot.contains_typed::<String>());
}
#[test]
fn test_depot_named_and_typed_are_separate() {
let mut depot = Depot::new();
depot.insert("value", "named".to_owned());
depot.insert_typed("typed".to_owned());
assert_eq!(depot.get::<String>("value").unwrap(), "named");
assert_eq!(depot.get_typed::<String>().unwrap(), "typed");
assert_eq!(depot.inner().len(), 1);
assert!(depot.contains_key("value"));
assert_eq!(
depot
.remove("value")
.and_then(|v| v.downcast::<String>().ok()),
Some(Box::new("named".to_owned()))
);
assert!(depot.remove("value").is_none());
assert!(!depot.contains_key("value"));
assert_eq!(depot.get_typed::<String>().unwrap(), "typed");
}
#[tokio::test]
async fn test_middleware_use_depot() {
#[handler]
async fn set_user(
req: &mut Request,
depot: &mut Depot,
res: &mut Response,
ctrl: &mut FlowCtrl,
) {
depot.insert("user", "client");
ctrl.call_next(req, depot, res).await;
}
#[handler]
async fn hello(depot: &mut Depot) -> String {
format!(
"Hello {}",
depot.get::<&str>("user").copied().unwrap_or_default()
)
}
let router = Router::new().hoop(set_user).goal(hello);
let service = Service::new(router);
let content = TestClient::get("http://127.0.0.1:8698")
.send(&service)
.await
.take_string()
.await
.unwrap();
assert_eq!(content, "Hello client");
}
}