rustio-core 2.0.5

Runtime core for RustIO: HTTP server, router, middleware, ORM, admin, and migrations.
Documentation
//! Typed per-request storage keyed by `TypeId`.
//!
//! Middleware writes into the context (`req.ctx_mut().insert(User { .. })`);
//! handlers and later middleware read by type (`req.ctx().get::<User>()`).
//! Stored values only need to be `Send + Sync + 'static` — no `Clone` bound.

use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt;

pub struct Context {
    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}

impl Context {
    pub fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
        self.map
            .insert(TypeId::of::<T>(), Box::new(value))
            .and_then(|prev| prev.downcast::<T>().ok().map(|b| *b))
    }

    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
        self.map
            .get(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast_ref::<T>())
    }

    pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
        self.map
            .get_mut(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast_mut::<T>())
    }

    pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
        self.map
            .remove(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast::<T>().ok().map(|b| *b))
    }

    pub fn contains<T: 'static>(&self) -> bool {
        self.map.contains_key(&TypeId::of::<T>())
    }

    pub fn len(&self) -> usize {
        self.map.len()
    }

    pub fn is_empty(&self) -> bool {
        self.map.is_empty()
    }
}

impl Default for Context {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Debug for Context {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Context")
            .field("entries", &self.map.len())
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, PartialEq)]
    struct Tag(u32);

    struct NotClone(String);

    #[test]
    fn insert_and_get_by_type() {
        let mut ctx = Context::new();
        assert!(ctx.insert(Tag(42)).is_none());
        assert_eq!(ctx.get::<Tag>(), Some(&Tag(42)));
    }

    #[test]
    fn different_types_coexist() {
        let mut ctx = Context::new();
        ctx.insert(Tag(1));
        ctx.insert(String::from("hello"));
        assert_eq!(ctx.get::<Tag>(), Some(&Tag(1)));
        assert_eq!(ctx.get::<String>().map(String::as_str), Some("hello"));
    }

    #[test]
    fn insert_replaces_same_type_and_returns_prev() {
        let mut ctx = Context::new();
        ctx.insert(Tag(1));
        let prev = ctx.insert(Tag(2));
        assert_eq!(prev, Some(Tag(1)));
        assert_eq!(ctx.get::<Tag>(), Some(&Tag(2)));
    }

    #[test]
    fn get_mut_permits_mutation_in_place() {
        let mut ctx = Context::new();
        ctx.insert(Tag(0));
        ctx.get_mut::<Tag>().unwrap().0 = 7;
        assert_eq!(ctx.get::<Tag>(), Some(&Tag(7)));
    }

    #[test]
    fn remove_returns_owned_value_and_clears() {
        let mut ctx = Context::new();
        ctx.insert(Tag(9));
        assert_eq!(ctx.remove::<Tag>(), Some(Tag(9)));
        assert!(!ctx.contains::<Tag>());
        assert!(ctx.get::<Tag>().is_none());
    }

    #[test]
    fn absent_type_returns_none() {
        let ctx = Context::new();
        assert!(ctx.get::<Tag>().is_none());
        assert!(!ctx.contains::<Tag>());
    }

    #[test]
    fn non_clone_types_are_allowed() {
        let mut ctx = Context::new();
        ctx.insert(NotClone("present".into()));
        assert_eq!(ctx.get::<NotClone>().unwrap().0, "present");
    }
}