use std::marker::PhantomData;
use serde::Serialize;
use serde::de::DeserializeOwned;
use toml::{Table, Value};
use crate::error::ClapfigError;
use crate::ops::ConfigResult;
use crate::runtime_builder::RuntimeBuilder;
use crate::static_schema::Schema;
use crate::types::{ConfigAction, Layer, SearchMode, SearchPath};
pub struct SchemaConfigBuilder<C: Schema> {
inner: RuntimeBuilder,
_phantom: PhantomData<fn() -> C>,
}
impl<C: Schema> SchemaConfigBuilder<C> {
pub(crate) fn new() -> Self {
Self {
inner: RuntimeBuilder::from_arc(C::schema_arc()),
_phantom: PhantomData,
}
}
pub fn app_name(mut self, name: &str) -> Self {
self.inner = self.inner.app_name(name);
self
}
pub fn file_name(mut self, name: &str) -> Self {
self.inner = self.inner.file_name(name);
self
}
pub fn search_paths(mut self, paths: Vec<SearchPath>) -> Self {
self.inner = self.inner.search_paths(paths);
self
}
pub fn add_search_path(mut self, path: SearchPath) -> Self {
self.inner = self.inner.add_search_path(path);
self
}
pub fn search_mode(mut self, mode: SearchMode) -> Self {
self.inner = self.inner.search_mode(mode);
self
}
pub fn persist_scope(mut self, name: &str, path: SearchPath) -> Self {
self.inner = self.inner.persist_scope(name, path);
self
}
pub fn env_prefix(mut self, prefix: &str) -> Self {
self.inner = self.inner.env_prefix(prefix);
self
}
pub fn no_env(mut self) -> Self {
self.inner = self.inner.no_env();
self
}
pub fn strict(mut self, strict: bool) -> Self {
self.inner = self.inner.strict(strict);
self
}
pub fn strict_at(mut self, path: &str, strict: bool) -> Self {
self.inner = self.inner.strict_at(path, strict);
self
}
pub fn on_unknown_key<F>(mut self, callback: F) -> Self
where
F: Fn(&crate::UnknownKeyContext<'_>) -> crate::UnknownKeyDecision + Send + Sync + 'static,
{
self.inner = self.inner.on_unknown_key(callback);
self
}
pub fn accept_dotted_extension_keys_in(
mut self,
path: &str,
decision: crate::UnknownKeyDecision,
) -> Self {
self.inner = self.inner.accept_dotted_extension_keys_in(path, decision);
self
}
pub fn normalize_keys(mut self, normalize: bool) -> Self {
self.inner = self.inner.normalize_keys(normalize);
self
}
pub fn layer_order(mut self, order: Vec<Layer>) -> Self {
self.inner = self.inner.layer_order(order);
self
}
#[cfg(feature = "url")]
pub fn url_query(mut self, query: &str) -> Self {
self.inner = self.inner.url_query(query);
self
}
pub fn cli_override<V: Into<Value>>(mut self, key: &str, value: Option<V>) -> Self {
self.inner = self.inner.cli_override(key, value);
self
}
pub fn cli_overrides_from<S: Serialize>(mut self, source: &S) -> Self {
self.inner = self.inner.cli_overrides_from(source);
self
}
}
impl<C: Schema + DeserializeOwned> SchemaConfigBuilder<C> {
pub fn post_validate<F>(mut self, f: F) -> Self
where
F: Fn(&C) -> Result<(), String> + Send + Sync + 'static,
{
self.inner = self.inner.post_validate(move |t: &Table| {
let text = toml::to_string(&Value::Table(t.clone()))
.map_err(|e: toml::ser::Error| e.to_string())?;
let typed: C = toml::from_str(&text).map_err(|e: toml::de::Error| e.to_string())?;
f(&typed)
});
self
}
pub fn load(self) -> Result<C, ClapfigError> {
let table = self.inner.load()?;
deserialize_table::<C>(table)
}
pub fn load_with_unknowns(
self,
) -> Result<(C, Vec<crate::strict::CollectedUnknown>), ClapfigError> {
let (table, unknowns) = self.inner.load_with_unknowns()?;
let typed = deserialize_table::<C>(table)?;
Ok((typed, unknowns))
}
pub fn handle(self, action: &ConfigAction) -> Result<ConfigResult, ClapfigError> {
self.inner.handle(action)
}
pub fn handle_and_print(self, action: &ConfigAction) -> Result<(), ClapfigError> {
self.inner.handle_and_print(action)
}
pub fn handle_to_string(self, action: &ConfigAction) -> Result<String, ClapfigError> {
self.inner.handle_to_string(action)
}
}
fn deserialize_table<C: DeserializeOwned>(table: Table) -> Result<C, ClapfigError> {
let text = toml::to_string(&Value::Table(table)).map_err(|e: toml::ser::Error| {
ClapfigError::InvalidValue {
key: "<merged>".into(),
reason: e.to_string(),
}
})?;
toml::from_str(&text).map_err(|e: toml::de::Error| ClapfigError::InvalidValue {
key: "<merged>".into(),
reason: e.to_string(),
})
}