head_empty/
lib.rs

1//! **Define parts of your configuration schema throughout your codebase**
2//!
3//! ## Example
4//!
5//! ```
6//! // mysql.rs
7//!
8//! #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
9//! struct Mysql {
10//!     host: String,
11//!     database: String,
12//!     user: String,
13//!     password: String,
14//! }
15//!
16//! head_empty::register! {
17//!     mysql: Mysql,
18//! }
19//!
20//! // main.rs
21//!
22//! #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
23//! struct Debug(bool);
24//!
25//! #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
26//! struct ListenPort(u16);
27//!
28//! head_empty::register! {
29//!     debug: Debug,
30//!     listen_port: ListenPort,
31//! }
32//!
33//! let deserializer = serde_json::json!({
34//!     "mysql": {
35//!         "host": "localhost:5432",
36//!         "database": "test",
37//!         "user": "root",
38//!         "password": "toor",
39//!     },
40//!     "debug": true,
41//!     "listen_port": 8080,
42//! });
43//!
44//! head_empty::init(deserializer).expect("deserializing configuration failed");
45//!
46//! let mysql: &'static Mysql = Mysql::configured();
47//! assert_eq!(
48//!     mysql,
49//!     &Mysql {
50//!         host: "localhost:5432".into(),
51//!         database: "test".into(),
52//!         user: "root".into(),
53//!         password: "toor".into()
54//!     }
55//! );
56//!
57//! let debug: &'static Debug = Debug::configured();
58//! assert_eq!(debug, &Debug(true));
59//!
60//! let listen_port: &'static ListenPort = ListenPort::configured();
61//! assert_eq!(listen_port, &ListenPort(8080));
62//! ```
63
64#![deny(unsafe_code)]
65
66mod compile_tests;
67mod de;
68
69#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
70pub use erased_serde;
71#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
72pub use linkme;
73#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
74pub use paste;
75
76use erased_serde as erased;
77use once_cell::race::OnceBox;
78use std::any::{Any, TypeId};
79use std::collections::HashMap;
80
81type Store = HashMap<TypeId, Box<dyn Any + Send + Sync>>;
82
83static STORE: OnceBox<Store> = OnceBox::new();
84
85#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
86pub fn store_get<T>() -> &'static T {
87    STORE
88        .get()
89        .expect("`head_empty::init` was not called soon enough")
90        .get(&TypeId::of::<T>())
91        .unwrap()
92        .downcast_ref()
93        .unwrap()
94}
95
96#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
97pub struct Registration {
98    pub field: &'static str,
99    pub type_id: fn() -> TypeId,
100    pub deserialize: DeserializeFn,
101}
102
103type DeserializeFn =
104    fn(&mut dyn erased::Deserializer) -> erased::Result<Box<dyn Any + Send + Sync>>;
105
106#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
107#[linkme::distributed_slice]
108pub static REGISTRATIONS: [Registration] = [..];
109
110#[cfg_attr(feature = "internal-doc-hidden", doc(hidden))]
111pub trait CanOnlyRegisterOnce {}
112
113/// Initialize configuration by deserializing it from a [`serde::Deserializer`]
114///
115/// # Panics
116///
117/// This will panic if it has already succeeded prior
118///
119/// This will panic if the same field name has been registered multiple times
120pub fn init<'de, Der>(der: Der) -> Result<(), Der::Error>
121where
122    Der: serde::Deserializer<'de>,
123{
124    let store = de::deserialize(&REGISTRATIONS, der)?;
125
126    if STORE.set(Box::new(store)).is_err() {
127        panic!("`head_empty::init` was called once too many times");
128    }
129
130    Ok(())
131}
132
133/// Register a type to be deserialized during [`init`]
134///
135/// Types or field names may only be registered once
136///
137/// Only crate-local types may be used
138///
139/// The trailing comma is optional
140///
141/// ```
142/// # #[derive(serde::Deserialize)] struct Type;
143/// # #[derive(serde::Deserialize)] struct Type2;
144/// #
145/// head_empty::register! {
146///     name: Type,
147///     name2: Type2,
148/// }
149/// ```
150#[macro_export]
151macro_rules! register {
152    ( $( $field:ident: $type:ty ),* ) => {
153        $crate::register! { $( $field: $type, )* }
154    };
155
156    ( $( $field:ident: $type:ty , )+ ) => {
157        $(
158            impl $crate::CanOnlyRegisterOnce for $type {}
159
160            $crate::paste::paste! {
161                #[$crate::linkme::distributed_slice($crate::REGISTRATIONS)]
162                static [< REGISTRATION_FOR_ $field >]: $crate::Registration = $crate::Registration {
163                    field: ::std::stringify!($field),
164                    type_id: || ::std::any::TypeId::of::<$type>(),
165                    deserialize: |d| {
166                        ::std::result::Result::Ok(::std::boxed::Box::new(
167                            $crate::erased_serde::deserialize::<$type>(d)?,
168                        ))
169                    },
170                };
171            }
172
173            impl $type {
174                /// # Panics
175                ///
176                /// This will panic if [`head_empty::init`] has not successfully ran prior
177                fn configured() -> &'static Self {
178                    $crate::store_get()
179                }
180            }
181        )+
182    };
183}