prefs 0.1.2

Type-safe macOS preferences library
use std::ffi::CString;
use std::marker::PhantomData;

pub(crate) fn key_cstring(name: &'static str) -> CString {
    CString::new(name).expect("static key contains null byte")
}

/// A typed preference key. Zero-cost wrapper around a `&'static str`.
///
/// The type parameter guarantees at compile time that only values of that
/// type are read or written for this key.
///
/// ```rust
/// use prefs::Key;
///
/// const VOLUME: Key<f64> = Key::new("volume");
/// ```
pub struct Key<T> {
    name: &'static str,
    _marker: PhantomData<T>,
}

impl<T> Key<T> {
    /// Create a new key with the given name.
    pub const fn new(name: &'static str) -> Self {
        Self {
            name,
            _marker: PhantomData,
        }
    }

    pub(crate) fn name(&self) -> &'static str {
        self.name
    }
}

impl<T> Clone for Key<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for Key<T> {}

impl<T> std::fmt::Debug for Key<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Key").field("name", &self.name).finish()
    }
}

impl<T> std::fmt::Display for Key<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name)
    }
}

impl<T> PartialEq for Key<T> {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl<T> Eq for Key<T> {}

impl<T> std::hash::Hash for Key<T> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

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

    #[test]
    fn test_key_new() {
        const KEY: Key<i64> = Key::new("test_key");
        assert_eq!(KEY.name(), "test_key");
    }

    #[test]
    fn test_key_clone_and_copy() {
        const KEY: Key<i64> = Key::new("copy_key");
        let cloned = KEY;
        assert_eq!(KEY, cloned);

        let copied = KEY;
        assert_eq!(KEY, copied);
    }

    #[test]
    fn test_key_display() {
        const KEY: Key<i64> = Key::new("display_key");
        assert_eq!(format!("{KEY}"), "display_key");
    }

    #[test]
    fn test_key_debug() {
        const KEY: Key<i64> = Key::new("debug_key");
        let debug = format!("{KEY:?}");
        assert!(debug.contains("debug_key"));
    }

    #[test]
    fn test_key_equality() {
        const A: Key<i64> = Key::new("same");
        const B: Key<i64> = Key::new("same");
        const C: Key<i64> = Key::new("different");

        assert_eq!(A, B);
        assert_ne!(A, C);
    }

    #[test]
    fn test_key_hash() {
        use std::collections::HashSet;

        const A: Key<i64> = Key::new("hash_key");
        const B: Key<i64> = Key::new("hash_key");

        let mut set = HashSet::new();
        set.insert(A);
        assert!(set.contains(&B));
    }

    #[test]
    fn test_key_cstring() {
        let first = key_cstring("cstring_test");
        let second = key_cstring("cstring_test");
        assert_eq!(first, second);
    }
}