use std::marker::PhantomData;
pub struct Key<T> {
path: &'static str,
marker: PhantomData<fn() -> T>,
}
impl<T> Key<T> {
pub const fn new(path: &'static str) -> Self {
Self { path, marker: PhantomData }
}
pub const fn path(&self) -> &'static str {
self.path
}
pub const fn as_type<U>(&self) -> Key<U> {
Key::new(self.path)
}
}
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 {
write!(f, "Key({})", self.path)
}
}
impl<T> std::fmt::Display for Key<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.path)
}
}
impl<T> PartialEq for Key<T> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<T> Eq for Key<T> {}
pub trait ConfigPath {
fn path(&self) -> &str;
}
impl ConfigPath for &str {
fn path(&self) -> &str {
self
}
}
impl ConfigPath for String {
fn path(&self) -> &str {
self
}
}
impl ConfigPath for &String {
fn path(&self) -> &str {
self
}
}
impl<T> ConfigPath for Key<T> {
fn path(&self) -> &str {
self.path
}
}
impl<T> ConfigPath for &Key<T> {
fn path(&self) -> &str {
self.path
}
}
pub trait ConfigKey<T>: ConfigPath {}
impl<T> ConfigKey<T> for &str {}
impl<T> ConfigKey<T> for String {}
impl<T> ConfigKey<T> for &String {}
impl<T> ConfigKey<T> for Key<T> {}
impl<T> ConfigKey<T> for &Key<T> {}
#[macro_export]
macro_rules! config_keys {
(
$(
$(#[$meta:meta])*
$vis:vis $name:ident: $type:ty = $path:literal;
)*
) => {
$(
$(#[$meta])*
///
#[doc = concat!("Reads and writes `", $path, "`.")]
$vis const $name: $crate::Key<$type> = $crate::Key::new($path);
)*
};
}
#[cfg(test)]
mod tests {
use super::*;
config_keys! {
pub CACHE_PREFIX: String = "cache.prefix";
pub POSTS_PER_PAGE: u64 = "posts.per_page";
}
#[test]
fn a_key_carries_its_path() {
assert_eq!(CACHE_PREFIX.path(), "cache.prefix");
assert_eq!(POSTS_PER_PAGE.to_string(), "posts.per_page");
assert_eq!(format!("{POSTS_PER_PAGE:?}"), "Key(posts.per_page)");
}
#[test]
fn a_key_is_copy_and_thread_safe_whatever_it_names() {
fn assert_send_sync_copy<T: Send + Sync + Copy>(_: T) {}
assert_send_sync_copy(CACHE_PREFIX);
assert_send_sync_copy(Key::<std::rc::Rc<()>>::new("a.b"));
}
#[test]
fn two_keys_at_the_same_path_are_equal() {
assert_eq!(CACHE_PREFIX, Key::<String>::new("cache.prefix"));
assert_ne!(CACHE_PREFIX, Key::<String>::new("cache.other"));
}
#[test]
fn as_type_reuses_the_path() {
let raw: Key<serde_json::Value> = POSTS_PER_PAGE.as_type();
assert_eq!(raw.path(), "posts.per_page");
}
#[test]
fn every_spelling_of_a_path_is_accepted() {
let owned = String::from("a.b");
assert_eq!(ConfigPath::path(&"a.b"), "a.b");
assert_eq!(ConfigPath::path(&owned), "a.b");
assert_eq!(ConfigPath::path(&&owned), "a.b");
assert_eq!(ConfigPath::path(&CACHE_PREFIX), "cache.prefix");
assert_eq!(ConfigPath::path(&&CACHE_PREFIX), "cache.prefix");
}
}