mod accessors;
mod diagnostics;
mod remote;
mod schema;
mod watch;
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{Error, ItemStruct, Result};
fn crate_path() -> TokenStream {
match crate_name("dynamic-config") {
Ok(FoundCrate::Itself) if compiling_the_facade() => quote!(crate),
Ok(FoundCrate::Itself) => quote!(::dynamic_config),
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name, Span::call_site());
quote!(::#ident)
}
Err(_) => quote!(::dynamic_config),
}
}
fn compiling_the_facade() -> bool {
std::env::var_os("UNSTABLE_RUSTDOC_TEST_PATH").is_none()
&& std::env::var("CARGO_CRATE_NAME").is_ok_and(|name| name == "dynamic_config")
}
pub(crate) fn expand(mut input: ItemStruct) -> Result<TokenStream> {
let secrets = schema::take_field_options(&mut input)?;
let redacted_debug = diagnostics::expand_redacted_debug(&input, &secrets)?;
let name = &input.ident;
if let Some(lifetime) = input.generics.lifetimes().next() {
return Err(Error::new_spanned(
lifetime,
"`#[dynamic_config]` does not support lifetime parameters, because the \
configuration snapshot outlives every borrow that could name one",
));
}
let is_generic = !input.generics.params.is_empty();
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
let where_clause = if is_generic {
let mut clause = where_clause
.cloned()
.unwrap_or_else(|| syn::parse_quote!(where));
clause
.predicates
.push(syn::parse_quote!(Self: 'static + ::core::marker::Send + ::core::marker::Sync));
quote!(#clause)
} else {
quote!(#where_clause)
};
let known_fields = schema::field_names(&input).unwrap_or_default();
let secret_names: Vec<String> = secrets.iter().map(|(_, name)| name.clone()).collect();
let path = &crate_path();
let cell_slot = accessors::cell_slot(path, is_generic, name, &type_generics);
let configured_slot = accessors::configured_slot(path, is_generic, name, &type_generics);
let defaults_slot = accessors::defaults_slot(path, is_generic);
let overrides_slot = accessors::overrides_slot(path, is_generic);
let remote_slot = accessors::remote_slot(path, is_generic);
let aliases_slot = accessors::aliases_slot(path, is_generic);
let bindings_slot = accessors::bindings_slot(path, is_generic);
let flags_slot = accessors::flags_slot(path, is_generic);
let layer_setters = accessors::layer_setters(path);
let introspection_methods = diagnostics::introspection_methods(path, &secret_names);
let hook_methods = watch::hook_methods(path);
let defaults_and_flag_setters = accessors::defaults_and_flag_setters(path);
let remote_methods = remote::remote_methods(path, name);
let binding_methods = accessors::binding_methods(path);
let clear_remote_method = remote::clear_remote_method();
let clear_flags_method = accessors::clear_flags_method();
let check_method = diagnostics::check_method(path);
let clear_layer_methods = accessors::clear_layer_methods();
Ok(quote! {
#input
#redacted_debug
impl #impl_generics #name #type_generics #where_clause {
#defaults_slot
#remote_slot
#aliases_slot
#bindings_slot
#flags_slot
#overrides_slot
const DYNAMIC_CONFIG_FIELDS: &'static [&'static str] = &[#(#known_fields),*];
#[must_use]
pub fn builder(key: &str) -> #path::Builder<Self> {
#path::Builder::new(key)
.with_installer(
Self::dynamic_config_install,
Self::dynamic_config_record_failure,
)
.with_secrets(&[#(#secret_names),*])
.with_fields(Self::DYNAMIC_CONFIG_FIELDS)
.with_type_statics(
Self::dynamic_config_defaults(),
Self::dynamic_config_overrides(),
Self::dynamic_config_flags(),
Self::dynamic_config_env_bindings(),
Self::dynamic_config_aliases(),
Self::dynamic_config_remote(),
Self::dynamic_config_remember,
)
}
fn dynamic_config_remember(builder: &#path::Builder<Self>) {
Self::dynamic_config_configured().set(::core::clone::Clone::clone(builder));
}
fn dynamic_config_builder(
) -> ::core::result::Result<#path::Builder<Self>, #path::Error>
{
Self::dynamic_config_configured().get(::core::stringify!(#name))
}
#layer_setters
#introspection_methods
#hook_methods
#defaults_and_flag_setters
#remote_methods
#binding_methods
#clear_remote_method
#clear_flags_method
#check_method
#clear_layer_methods
#cell_slot
#configured_slot
pub fn prepare() -> ::core::result::Result<
#path::Commit,
#path::Error,
> {
Self::dynamic_config_builder()?.prepare()
}
pub fn replace(config: Self) {
Self::dynamic_config_cell().store(config);
}
fn dynamic_config_install(
config: Self,
reason: #path::ReloadReason,
) -> ::std::sync::Arc<Self> {
Self::dynamic_config_cell().store_with(config, reason)
}
fn dynamic_config_record_failure(error: &#path::Error) {
Self::dynamic_config_cell().record_failure(error);
}
pub fn status() -> #path::ConfigStatus {
Self::dynamic_config_cell().status()
}
pub fn current() -> ::std::sync::Arc<Self> {
Self::dynamic_config_cell().get_or_panic(::core::stringify!(#name))
}
pub fn try_current() -> ::core::option::Option<::std::sync::Arc<Self>> {
Self::dynamic_config_cell().load()
}
pub fn generation() -> u64 {
Self::dynamic_config_cell().generation()
}
pub fn meta() -> ::core::option::Option<#path::SnapshotMeta> {
Self::dynamic_config_cell().meta()
}
#path::__clap_methods!();
#path::__async_methods!(#name);
#path::__async_remote_methods!();
}
impl #impl_generics #path::Reloadable for #name #type_generics
#where_clause
{
fn prepare() -> ::core::result::Result<
#path::Commit,
#path::Error,
> {
Self::prepare()
}
fn name() -> &'static str {
::core::stringify!(#name)
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_manifest_that_cannot_be_read_keeps_the_old_hardcoded_path() {
let empty = std::env::temp_dir().join("dynamic-config-macros-no-manifest");
std::fs::create_dir_all(&empty).expect("the scratch directory is writable");
let restore = std::env::var_os("CARGO_MANIFEST_DIR");
std::env::set_var("CARGO_MANIFEST_DIR", &empty);
let path = crate_path().to_string();
match restore {
Some(value) => std::env::set_var("CARGO_MANIFEST_DIR", value),
None => std::env::remove_var("CARGO_MANIFEST_DIR"),
}
assert_eq!(path, quote!(::dynamic_config).to_string());
}
}