1mod env;
2mod file;
3mod paths;
4mod secret;
5
6use std::io;
7
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub struct ReadOptions<'a> {
10 pub env_prefix: Option<&'a str>,
11}
12
13impl<'a> ReadOptions<'a> {
14 pub const fn with_env_prefix(env_prefix: &'a str) -> Self {
15 Self {
16 env_prefix: Some(env_prefix),
17 }
18 }
19}
20
21pub fn save<T>(app_name: &str, config: T) -> io::Result<()>
38where
39 T: serde::Serialize,
40{
41 let path = paths::default_config_path(app_name)?;
42 file::write_config(&path, &config, file::FileType::TOML)
43}
44
45pub fn read<T>(app_name: &str, options: Option<ReadOptions<'_>>) -> Result<T, io::Error>
63where
64 T: serde::de::DeserializeOwned + Default + serde::Serialize,
65{
66 let path = paths::default_config_path(app_name)?;
67 let mut config_value = if !path.is_file() {
68 let default_config = T::default();
69 file::write_config(&path, &default_config, file::FileType::TOML)?;
70 serde_json::to_value(default_config).map_err(|e| {
71 io::Error::new(
72 io::ErrorKind::InvalidData,
73 format!(
74 "failed to serialize default config before applying overrides for {}: {e}",
75 path.display()
76 ),
77 )
78 })?
79 } else {
80 file::read_config_value(&path)?
81 };
82
83 if let Some(prefix) = options.and_then(|options| options.env_prefix) {
84 config_value = env::apply_env_overrides(config_value, prefix)?;
85 }
86
87 secret::resolve_secret_refs(&mut config_value)?;
88
89 serde_json::from_value(config_value).map_err(|e| {
90 io::Error::new(
91 io::ErrorKind::InvalidData,
92 format!(
93 "failed to deserialize config {} into requested type: {e}",
94 path.display()
95 ),
96 )
97 })
98}
99
100#[cfg(test)]
101mod tests;