1#![deny(missing_docs)]
2
3mod error;
6mod value;
7
8use std::{fs, path::Path};
9
10use serde::de::DeserializeOwned;
11use serde_json::{Map, Value};
12
13use error::deserialize_value;
14pub use error::{ConfigError, Result};
15use value::{insert_path, merge_maps, parse_scalar, prefixed_key_start};
16
17#[derive(Clone, Debug, Default)]
55pub struct Config {
56 values: Map<String, Value>,
57}
58
59impl Config {
60 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> Self
70 where
71 K: Into<String>,
72 V: AsRef<str>,
73 {
74 let values = pairs
75 .into_iter()
76 .map(|(key, value)| (key.into(), parse_scalar(value.as_ref())))
77 .collect();
78 Self { values }
79 }
80
81 pub fn from_value(value: Value) -> Result<Self> {
83 match value {
84 Value::Object(values) => Ok(Self { values }),
85 _ => Err(ConfigError::RootNotObject),
86 }
87 }
88
89 pub fn from_json_str(source: &str) -> Result<Self> {
91 let value = serde_json::from_str(source).map_err(ConfigError::Parse)?;
92 Self::from_value(value)
93 }
94
95 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self> {
97 let path = path.as_ref();
98 let label = path.display().to_string();
99 let source = fs::read_to_string(path).map_err(|source| ConfigError::ReadFile {
100 path: label.clone(),
101 source,
102 })?;
103 let value = serde_json::from_str(&source).map_err(|source| ConfigError::ParseFile {
104 path: label.clone(),
105 source,
106 })?;
107 match value {
108 Value::Object(values) => Ok(Self { values }),
109 _ => Err(ConfigError::FileRootNotObject { path: label }),
110 }
111 }
112
113 pub fn from_env_prefix(prefix: &str) -> Self {
118 Self::from_prefixed_vars(prefix, std::env::vars())
119 }
120
121 pub fn from_prefixed_vars<K, V>(prefix: &str, vars: impl IntoIterator<Item = (K, V)>) -> Self
128 where
129 K: AsRef<str>,
130 V: AsRef<str>,
131 {
132 let prefix = prefixed_key_start(prefix);
133 let mut config = Self::new();
134
135 for (key, value) in vars {
136 let Some(raw_key) = key.as_ref().strip_prefix(&prefix) else {
137 continue;
138 };
139 if raw_key.is_empty() {
140 continue;
141 }
142
143 let path = raw_key
144 .split("__")
145 .filter(|segment| !segment.is_empty())
146 .map(|segment| segment.to_ascii_lowercase())
147 .collect::<Vec<_>>();
148 if !path.is_empty() {
149 insert_path(&mut config.values, &path, parse_scalar(value.as_ref()));
150 }
151 }
152
153 config
154 }
155
156 pub fn insert_value(&mut self, key: impl Into<String>, value: Value) {
158 self.values.insert(key.into(), value);
159 }
160
161 pub fn get(&self, key: &str) -> Option<&Value> {
163 self.values.get(key)
164 }
165
166 pub fn get_typed<T>(&self, key: &str) -> Result<Option<T>>
168 where
169 T: DeserializeOwned,
170 {
171 self.get(key)
172 .map(|value| deserialize_value(key.to_owned(), value))
173 .transpose()
174 }
175
176 pub fn get_required_typed<T>(&self, key: &str) -> Result<T>
178 where
179 T: DeserializeOwned,
180 {
181 self.get_typed(key)?
182 .ok_or_else(|| ConfigError::MissingValue {
183 path: key.to_owned(),
184 })
185 }
186
187 pub fn get_path<I, S>(&self, path: I) -> Option<&Value>
192 where
193 I: IntoIterator<Item = S>,
194 S: AsRef<str>,
195 {
196 let mut path = path.into_iter();
197 let first = path.next()?;
198 let mut value = self.values.get(first.as_ref())?;
199
200 for segment in path {
201 let segment = segment.as_ref();
202 value = match value {
203 Value::Object(object) => object.get(segment)?,
204 Value::Array(array) => array.get(segment.parse::<usize>().ok()?)?,
205 _ => return None,
206 };
207 }
208
209 Some(value)
210 }
211
212 pub fn get_path_typed<I, S, T>(&self, path: I) -> Result<Option<T>>
214 where
215 I: IntoIterator<Item = S>,
216 S: AsRef<str>,
217 T: DeserializeOwned,
218 {
219 let path = path
220 .into_iter()
221 .map(|segment| segment.as_ref().to_owned())
222 .collect::<Vec<_>>();
223 let label = path.join(".");
224 self.get_path(path.iter().map(String::as_str))
225 .map(|value| deserialize_value(label, value))
226 .transpose()
227 }
228
229 pub fn get_required_path_typed<I, S, T>(&self, path: I) -> Result<T>
231 where
232 I: IntoIterator<Item = S>,
233 S: AsRef<str>,
234 T: DeserializeOwned,
235 {
236 let path = path
237 .into_iter()
238 .map(|segment| segment.as_ref().to_owned())
239 .collect::<Vec<_>>();
240 let label = path.join(".");
241 match self.get_path(path.iter().map(String::as_str)) {
242 Some(value) => deserialize_value(label, value),
243 None => Err(ConfigError::MissingValue { path: label }),
244 }
245 }
246
247 pub fn merge(mut self, other: Self) -> Self {
253 self.merge_from(other);
254 self
255 }
256
257 pub fn merge_from(&mut self, other: Self) {
259 merge_maps(&mut self.values, other.values);
260 }
261
262 pub fn deserialize<T>(&self) -> Result<T>
264 where
265 T: DeserializeOwned,
266 {
267 T::deserialize(&self.values).map_err(ConfigError::Deserialize)
268 }
269}