#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![warn(clippy::must_use_candidate)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "age")]
#[cfg_attr(docsrs, doc(cfg(feature = "age")))]
pub mod age;
mod aliases;
#[cfg(feature = "async")]
mod asynchronous;
mod bindings;
mod cache;
mod cell;
mod check;
#[cfg(feature = "decrypt")]
mod decrypt;
mod discovery;
#[cfg(feature = "dotenv")]
mod dotenv;
mod error;
mod group;
mod layer;
mod loader;
mod log;
mod registry;
mod remote;
#[cfg(feature = "schema")]
#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
pub mod schema;
mod snapshot;
mod source;
mod units;
mod write;
#[cfg(feature = "watch")]
#[cfg_attr(docsrs, doc(cfg(feature = "watch")))]
pub mod watch;
#[cfg(feature = "async")]
pub use asynchronous::{set_blocking_executor, BlockingExecutor, Changes};
#[cfg(feature = "figment")]
#[cfg_attr(docsrs, doc(cfg(feature = "figment")))]
pub use figment;
pub use aliases::Aliases;
pub use bindings::EnvBindings;
pub use cache::{CacheMode, Recovery};
pub use cell::ConfigCell;
pub use check::{check, Report, Resolved, UnknownKey};
#[cfg(feature = "decrypt")]
#[cfg_attr(docsrs, doc(cfg(feature = "decrypt")))]
pub use decrypt::{has_decryptor, set_decryptor, Decryptor, Encryptor};
pub use discovery::Search;
pub use error::{Error, ErrorKind, Origin};
pub use group::{Commit, ReloadGroup, Reloadable};
pub use layer::Layer;
pub use registry::Registry;
#[cfg(feature = "async")]
pub use remote::AsyncRemoteSource;
pub use remote::{Fetched, Remote, RemoteSource, RemoteWatch, Watching};
pub use snapshot::{Change, ChangeKind, Snapshot};
pub use source::{Format, LoadSpec, Source, DEFAULT_NEST};
pub use units::{bytes, duration};
#[cfg(feature = "decrypt")]
#[cfg_attr(docsrs, doc(cfg(feature = "decrypt")))]
pub use write::save_encrypted;
pub use write::{save, save_new};
pub use dynamic_config_macros::dynamic_config;
use serde::de::DeserializeOwned;
pub fn load<T: DeserializeOwned>(spec: &LoadSpec<'_>) -> Result<T, Error> {
loader::load(spec)
}
pub fn snapshot(spec: &LoadSpec<'_>) -> Result<Snapshot, Error> {
loader::snapshot(spec)
}
pub fn source_of(spec: &LoadSpec<'_>, path: &str) -> Result<Option<Origin>, Error> {
loader::source_of(spec, path)
}
pub fn is_set(spec: &LoadSpec<'_>, path: &str) -> Result<bool, Error> {
loader::is_set(spec, path)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn load_async<T>(spec: LoadSpec<'static>) -> Result<T, Error>
where
T: DeserializeOwned + Send + 'static,
{
off_thread(move || load(&spec)).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn off_thread<T, F>(work: F) -> Result<T, Error>
where
F: FnOnce() -> Result<T, Error> + Send + 'static,
T: Send + 'static,
{
asynchronous::off_thread(work).await
}
#[doc(hidden)]
pub mod __private {
#[cfg(feature = "clap")]
pub use clap;
#[cfg(feature = "schema")]
pub use schemars;
pub use serde;
#[cfg(feature = "schema")]
pub use serde_json;
}
#[cfg(feature = "decrypt")]
#[macro_export]
#[doc(hidden)]
macro_rules! __save_encrypted_method {
($key:expr) => {
pub fn save_encrypted(
&self,
path: impl ::core::convert::AsRef<::std::path::Path>,
encryptor: &dyn $crate::Encryptor,
) -> ::core::result::Result<(), $crate::Error> {
let path = path.as_ref();
let format = $crate::Format::from_path(path)?;
$crate::save_encrypted(self, path, format, $key, encryptor)
}
};
}
#[cfg(not(feature = "decrypt"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __save_encrypted_method {
($key:expr) => {};
}
#[cfg(feature = "dotenv")]
#[macro_export]
#[doc(hidden)]
macro_rules! __require_dotenv {
() => {};
}
#[cfg(not(feature = "dotenv"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __require_dotenv {
() => {
::core::compile_error!(
"dynamic-config: `env_files` in #[dynamic_config(..)] requires the `dotenv` \
feature; add features = [\"dotenv\"] to your dynamic-config dependency"
);
};
}
#[cfg(feature = "decrypt")]
#[macro_export]
#[doc(hidden)]
macro_rules! __source_encrypted {
($path:expr, $format:expr) => {
$crate::Source::encrypted($path, $format)
};
}
#[cfg(not(feature = "decrypt"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __source_encrypted {
($path:expr, $format:expr) => {{
::core::compile_error!(
"dynamic-config: a `.age` config file needs decryption support; \
add features = [\"age\"] to your dynamic-config dependency"
);
$crate::Source::file($path, $format)
}};
}
#[cfg(feature = "json")]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_json {
() => {
$crate::Format::Json
};
}
#[cfg(not(feature = "json"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_json {
() => {
::core::compile_error!(
"dynamic-config: `.json` files require the `json` feature; \
add features = [\"json\"] to your dynamic-config dependency"
)
};
}
#[cfg(feature = "toml")]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_toml {
() => {
$crate::Format::Toml
};
}
#[cfg(not(feature = "toml"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_toml {
() => {
::core::compile_error!(
"dynamic-config: `.toml` files require the `toml` feature; \
add features = [\"toml\"] to your dynamic-config dependency"
)
};
}
#[cfg(feature = "yaml")]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_yaml {
() => {
$crate::Format::Yaml
};
}
#[cfg(not(feature = "yaml"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __format_yaml {
() => {
::core::compile_error!(
"dynamic-config: `.yaml` and `.yml` files require the `yaml` feature; \
add features = [\"yaml\"] to your dynamic-config dependency"
)
};
}
#[doc(hidden)]
pub fn __write_cache(
snapshot: &Snapshot,
cache: Option<(&'static str, &'static str, &'static [&'static str])>,
) {
let Some((path, mode, secrets)) = cache else {
return;
};
let mode = CacheMode::parse(mode).unwrap_or_default();
if let Err(error) = cache::write(snapshot, std::path::Path::new(path), mode, secrets) {
crate::log::warning!("could not write the configuration cache to {path}: {error}");
}
}
#[doc(hidden)]
pub fn recover<T: DeserializeOwned>(
name: &str,
spec: &LoadSpec<'_>,
cache: Option<(&'static str, &'static str, &'static [&'static str])>,
failure: &Error,
) -> Result<Option<T>, Error> {
let Some((path, mode, _)) = cache else {
return Ok(None);
};
let mode = CacheMode::parse(mode).unwrap_or_default();
let path = std::path::Path::new(path);
let current = loader::snapshot(spec).ok();
match cache::read(path, current.as_ref())? {
Recovery::Absent => Ok(None),
Recovery::Drift(moved) => {
report(
name,
&format!(
"cannot start: {failure}. Since the last good configuration: {}",
if moved.is_empty() {
"the same keys, with different values".to_owned()
} else {
moved.join(", ")
},
),
);
Ok(None)
}
Recovery::Usable(cached) if mode.recovers() => {
let config = loader::recover::<T>(spec, &cached).map_err(|error| {
Error::new(
ErrorKind::Backend,
format!("the cached configuration did not load either: {error}"),
)
})?;
report(
name,
&format!("starting from the last configuration that worked, because: {failure}"),
);
Ok(Some(config))
}
Recovery::Usable(_) => Ok(None),
}
}
fn report(name: &str, message: &str) {
crate::log::warning!("{name}: {message}");
}
#[doc(hidden)]
pub fn __log_remote_reload(name: &str, summary: Option<&str>) {
match summary {
Some(summary) => crate::log::info!("{name}: reloaded from the remote store, {summary}"),
None => crate::log::info!("{name}: reloaded from the remote store"),
}
}
#[doc(hidden)]
pub fn __log_remote_failure(name: &str, error: &Error) {
crate::log::warning!(
"{name}: the remote store's document did not apply, keeping the previous \
snapshot: {error}"
);
}
#[doc(hidden)]
#[must_use]
pub fn __summarize_changes(previous: &Snapshot, current: &Snapshot) -> String {
let changes = previous.diff(current);
if changes.is_empty() {
return "no keys changed".to_owned();
}
changes
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(feature = "watch")]
#[macro_export]
#[doc(hidden)]
macro_rules! __spawn_watch {
($($argument:tt)*) => {
$crate::watch::spawn_with($($argument)*)
};
}
#[cfg(not(feature = "watch"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __spawn_watch {
($($argument:tt)*) => {
::core::compile_error!(
"dynamic-config: `watch` in #[dynamic_config(..)] requires the `watch` feature; \
add features = [\"watch\"] to your dynamic-config dependency"
)
};
}
#[cfg(feature = "clap")]
#[macro_export]
#[doc(hidden)]
macro_rules! __clap_methods {
() => {
pub fn bind_clap(
matches: &$crate::__private::clap::ArgMatches,
bindings: &[(&str, &str)],
) -> ::core::result::Result<(), $crate::Error> {
Self::dynamic_config_flags().bind_clap(matches, bindings)
}
};
}
#[cfg(not(feature = "clap"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __clap_methods {
() => {};
}
#[cfg(feature = "schema")]
#[macro_export]
#[doc(hidden)]
macro_rules! __schema_methods {
($key:expr, $secrets:expr) => {
pub fn schema() -> ::dynamic_config::__private::serde_json::Value
where
Self: ::dynamic_config::__private::schemars::JsonSchema,
{
let generated = ::dynamic_config::__private::schemars::schema_for!(Self);
::dynamic_config::schema::section(
$key,
::core::convert::Into::into(generated),
$secrets,
)
}
};
}
#[cfg(not(feature = "schema"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __schema_methods {
($key:expr, $secrets:expr) => {
::core::compile_error!(
"dynamic-config: `schema` in #[dynamic_config(..)] requires the `schema` feature; \
add features = [\"schema\"] to your dynamic-config dependency"
);
};
}
#[cfg(feature = "async")]
#[macro_export]
#[doc(hidden)]
macro_rules! __async_methods {
($name:ident) => {
pub async fn load_async() -> ::core::result::Result<Self, $crate::Error> {
$crate::load_async(Self::dynamic_config_spec()).await
}
pub async fn init_async() -> ::core::result::Result<(), $crate::Error> {
$crate::off_thread(Self::dynamic_config_apply).await?;
::core::result::Result::Ok(())
}
pub fn changes() -> $crate::Changes<Self> {
Self::dynamic_config_cell().changes()
}
};
}
#[cfg(feature = "async")]
#[macro_export]
#[doc(hidden)]
macro_rules! __async_remote_methods {
() => {
pub fn set_remote_async(source: impl $crate::AsyncRemoteSource) {
Self::dynamic_config_remote().set_async(source);
}
pub async fn refresh_remote_async() -> ::core::result::Result<(), $crate::Error> {
Self::dynamic_config_remote().refresh_async().await
}
};
}
#[cfg(not(feature = "async"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __async_remote_methods {
() => {};
}
#[cfg(not(feature = "async"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __async_methods {
($name:ident) => {
::core::compile_error!(
"dynamic-config: `async` in #[dynamic_config(..)] requires the `async` feature \
(or `tokio`, which implies it); add features = [\"async\"] to your \
dynamic-config dependency"
);
};
}