dmv 0.3.2-a1

Provides identity values that are restricted to user-specified scopes
Documentation
#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
#![feature(once_cell)]
#![feature(trait_alias)]
#![feature(generic_associated_types)]
#![feature(associated_type_defaults)]
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)]
#![allow(clippy::needless_question_mark)]
#![allow(unused_unsafe)]

/// Useful trait aliases of [`DmvId`] with common types.
pub mod aliases;
mod details;
/// Definition of the [`Id`] traits.
pub mod id;
/// Definition of the [`Scope`] trait and [`scope!`] macro.
pub mod scope;
/// Data structure that maps a [`Scope`]'s [`TypeId`] to a value.
pub mod scope_map;

/// Convenient alias for the [`Id`]-implementing type of the object
/// returned from [`Dmv::register`].
pub type DmvId<S, T> = details::Id<S, T>;

/// Alias of the [`Id::Handle`] associated type of [`DmvId`].
pub type DmvIdHandle<S, T> = <DmvId<S, T> as Id<S, T>>::Handle;

pub use id::{Comparable, Id, RawRepr};
pub use scope::{GlobalScope, Scope};

use details::Id as privId;
use scope_map::ScopeMap;

use std::{
    any::{Any, TypeId},
    collections::HashMap,
    lazy::SyncLazy,
    sync::Mutex,
};

use num_traits::Num;

macro_rules! gen_global_scope_inits {
    ($map:ident<$($T:ty),+ $(,).*>) => {$(
        $map.entry(TypeId::of::<$T>())
            .or_insert_with(|| {
                let mut scope_map = ScopeMap::<$T>::new();
                // Safety:
                // TODO
                unsafe { scope_map.set(TypeId::of::<GlobalScope>(), 1) };
                Box::new(scope_map)
            });
    )*};

    ($($t:tt)*) => {
        compile_error!(concat!(
            "encountered unexpected tokens: ",
            stringify!($($t)*),
        ));
    };
}

static MAPS: SyncLazy<Mutex<HashMap<TypeId, Box<dyn Any + Send>>>> = SyncLazy::new(|| {
    let mut map: HashMap<_, Box<dyn Any + Send>> = HashMap::new();

    gen_global_scope_inits!(map<u8, u16, u32, u64, u128, usize>);

    Mutex::new(map)
});

/// The administrator of [`Id`]s.
///
/// TODO: Write section describing internal behavior of [`Dmv`] and the term "ID bucket" which is referenced throughout the docs
///
/// # Panics
///
/// All methods on [`Dmv`] may panic if the internal [`Mutex`] ensuring safe
/// global mutable state for [`Dmv`] becomes [poisoned], which occurs when a
/// thread panics while holding the mutex. If the mutex is poisoned, it is also
/// likely that the underlying data is invalid in some way and should not be
/// trusted, and panicking is the safest way to resolve that situation.
///
/// # Pun
///
/// The struct's name, `Dmv`, is a reference to a common state-level agency in
/// the US, the [Department of Motor Vehicles][DMV wiki], where local citizens
/// may acquire a Drivers License/Photo ID.
///
/// [DMV wiki]: https://en.wikipedia.org/wiki/Department_of_motor_vehicles
/// [poisoned]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning
#[derive(Default)]
pub struct Dmv;

impl Dmv {
    /// Registers a new ID.
    ///
    /// # `None`
    ///
    /// This function returns [`None`] if the ID bucket for scope `S` and base
    /// type `T` has not been initialized via [`Self::init`] or
    /// [`Self::init_default`]. You can check if the ID type has been
    /// initialized via the [`Self::is_init`] function.
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    #[allow(clippy::unused_self)]
    #[must_use]
    pub fn register<S: Scope, T: RawRepr + Num + Send>(&self) -> Option<DmvId<S, T>> {
        let mut lock = MAPS.lock().unwrap();
        let scope_map = lock
            .get_mut(&TypeId::of::<T>())?
            .downcast_mut::<ScopeMap<T>>()
            .unwrap();
        let val = scope_map.get(TypeId::of::<S>())?;

        Some(privId::new(val))
    }

    /// Creates a new [`Id`] with the given `id_val`.
    ///
    /// This function may be used under contexts where the caller would like the
    /// returned [`Id`] to contain a specific value, such as in the case of
    /// deserialization.
    ///
    /// # Safety
    ///
    /// It is invalid to pass this function an `id_val` that would allow the
    /// resulting [`Id`] to compare equally with another [`Id`] given out by the
    /// [`Dmv`]. By calling this function you promise that `id_val` will not
    /// break the property that all [`Id`] values (including those not yet
    /// created) are unique. [`Dmv::reset`] may be of use in ensuring that the
    /// necessary values are reset before calling [`Dmv::register`] again.
    ///
    /// # See Also
    ///
    /// - [`Dmv`] struct documentation
    /// - [`Dmv::reset`] associated function documentation
    #[allow(clippy::unused_self)]
    #[must_use]
    pub unsafe fn register_unchecked<S: Scope, T: RawRepr>(&self, id_val: T) -> DmvId<S, T> {
        privId::new(id_val)
    }

    /// Initializes the ID bucket for scope `S` and base type `T` with the
    /// default value of [`num_traits::One::one`].
    ///
    /// If an ID bucket exists for `S` and `T` already, this function will do
    /// nothing. To always set the ID bucket value to a given `base`, use
    /// [`Self::reset`].
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    pub fn init_default<S: Scope, T: RawRepr + Send + Num>(&self) {
        self.init::<S, T>(T::one());
    }

    /// Initializes the ID bucket for scope `S` and base type `T`.
    ///
    /// If an ID bucket exists for `S` and `T` already, this function will do
    /// nothing. To always set the ID bucket value to a given `base`, use
    /// [`Self::reset`].
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    #[allow(clippy::unused_self)]
    pub fn init<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
        let mut lock = MAPS.lock().unwrap();
        let map = lock
            .entry(TypeId::of::<T>())
            .or_insert_with(move || Box::new(ScopeMap::<T>::new()))
            .downcast_mut::<ScopeMap<T>>()
            .unwrap();
        let scope = TypeId::of::<S>();
        if !map.has(scope) {
            // Safety:
            // TODO
            unsafe { map.set(scope, base) }
        }
    }

    /// Checks if the ID bucket for scope `S` and base type `T` has been
    /// initialized yet.
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    #[allow(clippy::unused_self)]
    #[must_use]
    pub fn is_init<S: Scope, T: RawRepr + Num>(&self) -> bool {
        let maps = MAPS.lock().unwrap();
        maps.contains_key(&TypeId::of::<T>())
            && maps
                .get(&TypeId::of::<T>())
                .unwrap()
                .downcast_ref::<ScopeMap<T>>()
                .unwrap()
                .has(TypeId::of::<S>())
    }

    /// Resets the value of the ID bucket for scope `S` and base type `T` to
    /// `base`.
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    ///
    /// # Safety
    ///
    /// ## `reset` contract
    ///
    /// It is unsafe to pass this function a `base` that would compare less-than
    /// or equal-to (`<=`) any pre-existing [`DmvId`] value with scope `S` and
    /// base type `T`.
    ///
    /// ## `reset`-`peek` contract
    ///
    /// See [`Dmv::peek`] method documentation for the full details of this
    /// contract.
    ///
    /// This function overwrites the value that would've been returned by a
    /// prior call to[`Dmv::peek`], which means that any code that relies on the
    /// value returned by [`Dmv::peek`] to be the same value returned by the
    /// next call to [`Dmv::register`] will be in an invalid state.
    ///
    /// # See Also
    ///
    /// - [`Dmv`] struct documentation
    /// - [`Dmv::peek`] method documentation
    #[allow(clippy::unused_self)]
    pub unsafe fn reset<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
        let mut lock = MAPS.lock().unwrap();
        let scope = lock
            .entry(TypeId::of::<T>())
            .or_insert_with(move || Box::new(ScopeMap::<T>::new()))
            .downcast_mut::<ScopeMap<T>>()
            .unwrap();
        unsafe {
            scope.set(TypeId::of::<S>(), base);
        }
    }

    /// Returns a copy of the value that will be returned by the next call to
    /// [`Dmv::register`].
    ///
    /// # Panics
    ///
    /// See [`Dmv`] struct docs for panic info.
    ///
    /// # Safety
    ///
    /// ## `reset`-`peek` contract
    ///
    /// The value that this method returns is representative of the state of the
    /// internal machinery at the point in time that the internal mutex is
    /// locked during a call to this method. This means that a call to
    /// [`Dmv::reset`] would overwriting the value in the that this method
    /// would've previously returned a copy of, and thus the value that will be
    /// returned by the next call to [`Dmv::register`].
    ///
    /// Any calling code that wants to rely on this value also being the value
    /// held internally by the [`DmvId`] returned by the next call to
    /// [`Dmv::register`] must also guarantee that [`Dmv::reset`] is not called
    /// before [`Dmv::register`] is called or that the previously `peek`'d value
    /// should be re-`peek`'d after a call to [`Dmv::reset`].
    ///
    /// # See Also
    ///
    /// - [`Dmv`] struct documentation
    /// - [`Dmv::reset`] method documentation
    #[allow(clippy::unused_self)]
    #[must_use]
    pub unsafe fn peek<S: Scope, T: RawRepr + Send + Num>(&self) -> Option<T> {
        Some(
            MAPS.lock()
                .unwrap()
                .get(&TypeId::of::<T>())?
                .downcast_ref::<ScopeMap<T>>()
                .unwrap()
                .peek(TypeId::of::<S>())?,
        )
    }
}

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

    #[test]
    fn test() {
        let id1 = Dmv.register::<GlobalScope, usize>().unwrap();
        let id2 = Dmv.register::<GlobalScope, usize>().unwrap();

        let h1 = id1.handle();
        let h2 = id2.handle();

        assert_ne!(id1, id2);
        assert_ne!(*h1, *h2);
    }

    #[test]
    fn test_reset_peek_contract() {
        use crate::aliases::DmvIdSize;

        local_scope! { Scope1 };
        local_scope! { Scope2 };

        //
        // Follow the `reset`-`peek` contract
        //

        Dmv.init::<Scope1, usize>(0);
        assert_eq!(unsafe { Dmv.peek::<Scope1, usize>() }.unwrap(), 0);
        let _id = Dmv.register::<Scope1, usize>().unwrap();
        assert_eq!(unsafe { Dmv.peek::<Scope1, usize>() }.unwrap(), 1);

        //
        // Break the `reset`-`peek` contract
        //

        Dmv.init::<Scope2, usize>(0);
        let _id = Dmv.register::<Scope2, usize>().unwrap();
        // peek what the next ID value will be
        let peek = unsafe { Dmv.peek::<Scope2, usize>() }.unwrap();
        // change what the next ID value will be
        unsafe { Dmv.reset::<Scope2, usize>(3) };
        let id: DmvIdSize<Scope2> = Dmv.register::<Scope2, usize>().unwrap();
        let handle = <DmvIdSize<Scope2> as Id<Scope2, usize>>::handle(&id);
        assert_ne!(peek, *handle);
        // `reset` contract is still valid
        assert_eq!(*handle, 3);
    }
}