dmv 0.3.1

Provides identity values that are restricted to user-specified scopes
#![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)]

mod details;
/// Definition of the [`Id`] traits.
pub mod id;
/// Public `use` statements of some items that may be commonly used, but less
/// common than the items in the crate root.
pub mod prelude;
/// Definition of the [`Scope`] trait and [`scope!`] macro.
pub mod scope;
mod scope_map;

pub type DmvId<S, T> = Pin<Box<details::Id<S, T>>>;

pub use id::{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,
    pin::Pin,
    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();
                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)
///
/// # Pun
///
/// The struct 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
#[derive(Default)]
pub struct Dmv;

impl Dmv {
    /// Registers a new ID
    ///
    /// # Panics
    ///
    /// This function panics 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 and recommended way to resolve that
    /// situation.
    #[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 unsafe 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.
    ///
    /// # See Also
    ///
    /// - [`Dmv`] struct 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
    ///
    /// This function panics 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 and recommended way to resolve that
    /// situation.
    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
    ///
    /// This function panics 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 and recommended way to resolve that
    /// situation.
    #[allow(clippy::unused_self)]
    pub fn init<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
        let mut lock = MAPS.lock().unwrap();
        let scope_map = lock
            .entry(TypeId::of::<T>())
            .or_insert_with(move || Box::new(ScopeMap::<T>::new()))
            .downcast_mut::<ScopeMap<T>>()
            .unwrap();
        if !scope_map.has(TypeId::of::<S>()) {
            scope_map.set(TypeId::of::<S>(), base);
        }
    }

    /// Checks if the ID bucket for scope `S` and base type `T` has been
    /// initialized yet.
    ///
    /// # Panics
    ///
    /// This function panics 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 and recommended way to resolve that
    /// situation.
    #[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
    ///
    /// This function panics 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 and recommended way to resolve that
    /// situation.
    ///
    /// # Safety
    ///
    /// 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`.
    ///
    /// # See Also
    ///
    /// - [`Dmv`] struct documentation
    #[allow(clippy::unused_self)]
    pub unsafe fn reset<S: Scope, T: RawRepr + Send + Num>(&self, base: T) {
        MAPS.lock()
            .unwrap()
            .entry(TypeId::of::<T>())
            .or_insert_with(move || Box::new(ScopeMap::<T>::new()))
            .downcast_mut::<ScopeMap<T>>()
            .unwrap()
            .set(TypeId::of::<S>(), base);
    }
}

#[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);
    }
}