use std::{path::Path, sync::Arc};
use clap::CommandFactory;
use figment::Error as FigmentError;
use ortho_config::subcommand::Prefix;
use ortho_config::{
OrthoConfig, OrthoError, OrthoResult, OrthoResultExt, ResultIntoFigment, SubcmdConfigMerge,
load_and_merge_subcommand,
};
use serde::de::DeserializeOwned;
fn with_jail<F, L, T>(setup: F, loader: L) -> OrthoResult<T>
where
F: FnOnce(&mut figment::Jail) -> figment::error::Result<()>,
L: FnOnce() -> OrthoResult<T>,
{
use std::cell::RefCell;
let result = RefCell::new(None);
figment::Jail::try_with(|j| {
setup(j)?;
let cfg = loader().to_figment()?;
result.replace(Some(cfg));
Ok(())
})
.into_ortho()?;
result.into_inner().ok_or_else(|| {
Arc::new(OrthoError::Validation {
key: "subcommand_loader".into(),
message: "loader did not run".into(),
})
})
}
#[expect(
clippy::result_large_err,
reason = "figment::Error must be returned directly to integrate with Jail closures"
)]
pub fn path_to_utf8_string(path: &Path, context: &str) -> Result<String, FigmentError> {
path.to_str().map(str::to_owned).ok_or_else(|| {
let display = path.display();
FigmentError::from(format!("{context} path not valid UTF-8: {display}"))
})
}
pub fn with_merged_subcommand_cli<F, T>(setup: F, cli: &T) -> OrthoResult<T>
where
F: FnOnce(&mut figment::Jail) -> figment::error::Result<()>,
T: serde::Serialize + DeserializeOwned + Default + CommandFactory,
{
with_jail(setup, || {
load_and_merge_subcommand(&Prefix::new("APP_"), cli)
})
}
pub fn with_merged_subcommand_cli_for<F, T>(setup: F, cli: &T) -> OrthoResult<T>
where
F: FnOnce(&mut figment::Jail) -> figment::error::Result<()>,
T: OrthoConfig + serde::Serialize + Default + CommandFactory,
{
with_jail(setup, || cli.load_and_merge())
}