use serde::de::DeserializeOwned;
use std::path::Path;
use crate::cache::{CacheMode, Recovery};
use crate::error::{Error, ErrorKind};
use super::Builder;
impl<T: DeserializeOwned> Builder<T> {
pub fn load(&self) -> Result<T, Error> {
let value: T = self.with_spec(crate::loader::load)?;
if let Some(check) = self.validate {
check(&value)?;
}
Ok(value)
}
pub fn init(&self) -> Result<(), Error> {
self.check_cache_mode()?;
let Some(install) = self.install.as_ref() else {
return Err(Error::new(
ErrorKind::Backend,
"this builder is tied to no config type, so there is nowhere \
to install; use `load()` here, or start from the generated \
`builder()` on a `#[dynamic_config]` type",
));
};
let outcome = match self.load() {
Ok(value) => {
install.install(value);
self.write_cache();
Ok(())
}
Err(failure) => {
let recovered = self.recover(failure)?;
if let Some(check) = self.validate {
check(&recovered)?;
}
install.install(recovered);
crate::log::warning!(
"{}: started from the last known good configuration",
self.key
);
Ok(())
}
};
if outcome.is_ok() {
if let Some(register) = self.register {
register(self);
}
}
outcome
}
pub fn prepare(&self) -> Result<crate::group::Commit, Error>
where
T: Send + Sync + 'static,
{
let Some(install) = self.install.as_ref() else {
return Err(Error::new(
ErrorKind::Backend,
"this builder is tied to no config type, so a prepared \
commit would have nowhere to install",
));
};
let value = self.load()?;
let install = install.clone();
Ok(Box::new(move || install.install(value)))
}
fn check_cache_mode(&self) -> Result<(), Error> {
if let Some((_, mode)) = &self.cache {
if !matches!(mode, CacheMode::Full) && self.secrets.is_none() {
return Err(Error::new(
ErrorKind::Backend,
"a redacted or fingerprint cache needs to know which \
fields are secret, and only the generated `builder()` on \
a `#[dynamic_config]` type knows; use that, or \
`CacheMode::Full`, spelled out",
));
}
}
Ok(())
}
pub(super) fn write_cache(&self) {
let Some((path, mode)) = &self.cache else {
return;
};
if !matches!(mode, CacheMode::Full) && self.secrets.is_none() {
crate::log::warning!(
"{}: not writing the cache at {path}: a redaction-dependent \
mode needs the generated builder's secret knowledge",
self.key
);
return;
}
let secrets = self.secrets.clone().unwrap_or_default();
let secret_refs: Vec<&str> = secrets.iter().map(String::as_str).collect();
let written = self.with_spec(|spec| {
let snapshot = crate::loader::snapshot(spec)?;
#[cfg(feature = "decrypt")]
if let Some(encryptor) = &self.cache_encryptor {
return crate::cache::write_encrypted(
&snapshot,
Path::new(path),
encryptor.as_ref(),
);
}
crate::cache::write(&snapshot, Path::new(path), *mode, &secret_refs)
});
if let Err(error) = written {
crate::log::warning!("could not write the configuration cache to {path}: {error}");
}
}
fn recover(&self, failure: Error) -> Result<T, Error> {
let Some((path, mode)) = &self.cache else {
return Err(failure);
};
let may_recover = mode.recovers();
let current = self.with_spec(crate::loader::snapshot).ok();
#[cfg(feature = "decrypt")]
let recovered = if let Some(_encryptor) = &self.cache_encryptor {
crate::cache::read_encrypted(Path::new(path), current.as_ref())
} else {
crate::cache::read(Path::new(path), current.as_ref())
};
#[cfg(not(feature = "decrypt"))]
let recovered = crate::cache::read(Path::new(path), current.as_ref());
match recovered {
Ok(Recovery::Usable(snapshot)) if may_recover => self
.with_spec(|spec| crate::loader::recover::<T>(spec, &snapshot))
.map(|(value, _snapshot)| value),
Ok(Recovery::Usable(_)) => {
crate::log::warning!(
"{}: the cache at {path} holds values, but this builder \
is configured `Fingerprint`, which diagnoses and never \
recovers; refusing to start from it",
self.key
);
Err(failure)
}
Ok(Recovery::Drift(moved)) => {
crate::log::warning!(
"{}: cannot start: {failure}. Since the last good configuration: {}",
self.key,
match moved {
Some(paths) if paths.is_empty() => "nothing detectably moved".to_owned(),
Some(paths) => paths.join(", "),
None => "could not compare — the sources do not resolve".to_owned(),
}
);
Err(failure)
}
Ok(Recovery::Absent) | Err(_) => Err(failure),
}
}
pub fn reload(&self) -> Result<(), Error> {
let Some(install) = self.install.as_ref() else {
return Err(Error::new(
ErrorKind::Backend,
"this builder is tied to no config type, so a reload would \
have nowhere to install",
));
};
install.install(self.load()?);
self.write_cache();
Ok(())
}
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
impl<T: DeserializeOwned + Send + Sync + 'static> Builder<T> {
pub async fn load_async(&self) -> Result<T, Error> {
let this = self.clone();
crate::asynchronous::off_thread(move || this.load()).await
}
pub async fn init_async(&self) -> Result<(), Error> {
let this = self.clone();
crate::asynchronous::off_thread(move || this.init()).await
}
}