1mod env;
2mod file;
3mod format;
4mod paths;
5mod secret;
6mod sql;
7
8use std::io;
9
10#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
11pub struct ReadOptions<'a> {
12 pub env_prefix: Option<&'a str>,
13}
14
15impl<'a> ReadOptions<'a> {
16 pub const fn with_env_prefix(env_prefix: &'a str) -> Self {
17 Self {
18 env_prefix: Some(env_prefix),
19 }
20 }
21}
22
23pub trait ConfigSource {
24 fn source_name(&self) -> String;
25 fn read_value(&mut self) -> io::Result<Option<serde_json::Value>>;
26 fn write_config<T>(&mut self, config: &T) -> io::Result<()>
27 where
28 T: serde::Serialize;
29}
30
31impl<T> ConfigSource for &mut T
32where
33 T: ConfigSource + ?Sized,
34{
35 fn source_name(&self) -> String {
36 (**self).source_name()
37 }
38
39 fn read_value(&mut self) -> io::Result<Option<serde_json::Value>> {
40 (**self).read_value()
41 }
42
43 fn write_config<S>(&mut self, config: &S) -> io::Result<()>
44 where
45 S: serde::Serialize,
46 {
47 (**self).write_config(config)
48 }
49}
50
51impl ConfigSource for &str {
52 fn source_name(&self) -> String {
53 match paths::default_config_path(self) {
54 Ok(path) => path.display().to_string(),
55 Err(_) => (*self).to_string(),
56 }
57 }
58
59 fn read_value(&mut self) -> io::Result<Option<serde_json::Value>> {
60 let path = paths::default_config_path(self)?;
61 if path.is_file() {
62 file::read_config_value(&path).map(Some)
63 } else {
64 Ok(None)
65 }
66 }
67
68 fn write_config<T>(&mut self, config: &T) -> io::Result<()>
69 where
70 T: serde::Serialize,
71 {
72 let path = paths::default_config_path(self)?;
73 file::write_config(&path, config, file::FileType::TOML)
74 }
75}
76
77pub fn save<T>(mut source: impl ConfigSource, config: T) -> io::Result<()>
78where
79 T: serde::Serialize,
80{
81 source.write_config(&config)
82}
83
84pub fn read<T>(
85 mut source: impl ConfigSource,
86 options: Option<ReadOptions<'_>>,
87) -> Result<T, io::Error>
88where
89 T: serde::de::DeserializeOwned + Default + serde::Serialize,
90{
91 let source_name = source.source_name();
92 let config_value = match source.read_value()? {
93 Some(value) => value,
94 None => {
95 let default_config = T::default();
96 let default_value = serde_json::to_value(&default_config).map_err(|e| {
97 io::Error::new(
98 io::ErrorKind::InvalidData,
99 format!(
100 "failed to serialize default config before applying overrides for {source_name}: {e}"
101 ),
102 )
103 })?;
104 source.write_config(&default_config)?;
105 default_value
106 }
107 };
108
109 process_config_value(config_value, options.and_then(|opt| opt.env_prefix), &source_name)
110}
111
112fn process_config_value<T>(
113 mut config_value: serde_json::Value,
114 env_prefix: Option<&str>,
115 source: &str,
116) -> Result<T, io::Error>
117where
118 T: serde::de::DeserializeOwned,
119{
120 if let Some(prefix) = env_prefix {
121 config_value = env::apply_env_overrides(config_value, prefix)?;
122 }
123
124 secret::resolve_secret_refs(&mut config_value)?;
125
126 serde_json::from_value(config_value).map_err(|e| {
127 io::Error::new(
128 io::ErrorKind::InvalidData,
129 format!("failed to deserialize config {source} into requested type: {e}"),
130 )
131 })
132}
133
134pub use sql::postgres_store;
135pub use sql::postgres_store_with_table;
136pub use sql::DEFAULT_CONFIG_TABLE;
137pub use sql::PostgresConfigStore;
138
139#[cfg(test)]
140mod tests;