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(®ISTRATIONS, 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}