1use crate::config_options::ConfigOptions;
2use crate::merge::object::Object as MObject;
3use crate::merge::value::Value as MValue;
4use crate::parser::loader::{self, load_from_path, parse_hocon};
5use crate::parser::read::{StrRead, StreamRead};
6use crate::raw::raw_object::RawObject;
7use crate::raw::raw_string::RawString;
8use crate::raw::raw_value::RawValue;
9use crate::raw::{field::ObjectField, include::Inclusion};
10use crate::value::Value;
11use derive_more::{Deref, DerefMut};
12use serde::de::DeserializeOwned;
13
14#[derive(Debug, Clone, PartialEq, Deref, DerefMut)]
15pub struct Config {
16 #[deref]
17 #[deref_mut]
18 object: RawObject,
19 options: ConfigOptions,
20}
21
22impl Config {
23 pub fn new(options: Option<ConfigOptions>) -> Self {
24 Self {
25 object: Default::default(),
26 options: options.unwrap_or_default(),
27 }
28 }
29
30 pub fn load<T>(
31 path: impl AsRef<std::path::Path>,
32 options: Option<ConfigOptions>,
33 ) -> crate::Result<T>
34 where
35 T: DeserializeOwned,
36 {
37 let raw = loader::load(&path, options.unwrap_or_default())?;
38 tracing::debug!("path: {} raw obj: {}", path.as_ref().display(), raw);
39 Self::resolve_object::<T>(raw)
40 }
41
42 pub fn add_kv<K, V>(&mut self, key: K, value: V) -> &mut Self
43 where
44 K: Into<RawString>,
45 V: Into<RawValue>,
46 {
47 let field = ObjectField::key_value(key, value);
48 self.object.push(field);
49 self
50 }
51
52 pub fn add_include(&mut self, inclusion: Inclusion) -> &mut Self {
53 let field = ObjectField::inclusion(inclusion);
54 self.object.push(field);
55 self
56 }
57
58 pub fn add_kvs<I, V>(&mut self, kvs: I) -> &mut Self
59 where
60 I: IntoIterator<Item = (String, V)>,
61 V: Into<RawValue>,
62 {
63 let fields = kvs
64 .into_iter()
65 .map(|(key, value)| ObjectField::key_value(key, value));
66 self.object.extend(fields);
67 self
68 }
69
70 pub fn add_object(&mut self, object: RawObject) -> &mut Self {
71 self.object.extend(object.0);
72 self
73 }
74
75 pub fn resolve<T>(self) -> crate::Result<T>
76 where
77 T: DeserializeOwned,
78 {
79 Self::resolve_object(self.object)
80 }
81
82 pub fn parse_file<T>(
83 path: impl AsRef<std::path::Path>,
84 opts: Option<ConfigOptions>,
85 ) -> crate::Result<T>
86 where
87 T: DeserializeOwned,
88 {
89 let raw = load_from_path(path, opts.unwrap_or_default())?;
90 Self::resolve_object::<T>(raw)
91 }
92
93 #[cfg(feature = "urls_includes")]
94 pub fn parse_url<T>(url: impl AsRef<str>, opts: Option<ConfigOptions>) -> crate::Result<T>
95 where
96 T: DeserializeOwned,
97 {
98 use std::str::FromStr;
99 let url = url::Url::from_str(url.as_ref())?;
100 let raw = loader::load_from_url(url, opts.unwrap_or_default().into())?;
101 Self::resolve_object::<T>(raw)
102 }
103
104 pub fn parse_map<T>(values: std::collections::HashMap<String, Value>) -> crate::Result<T>
105 where
106 T: DeserializeOwned,
107 {
108 fn into_raw(value: Value) -> RawValue {
109 match value {
110 Value::Object(object) => {
111 let len = object.len();
112 let fields = object.into_iter().fold(
113 Vec::with_capacity(len),
114 |mut acc, (key, value)| {
115 let field = ObjectField::key_value(key, into_raw(value));
116 acc.push(field);
117 acc
118 },
119 );
120 RawValue::Object(RawObject::new(fields))
121 }
122 Value::Array(array) => RawValue::array(array.into_iter().map(into_raw).collect()),
123 Value::Boolean(boolean) => RawValue::Boolean(boolean),
124 Value::Null => RawValue::Null,
125 Value::String(string) => {
126 let s = RawString::path_expression(
127 string.split('.').map(RawString::quoted).collect(),
128 );
129 RawValue::String(s)
130 }
131 Value::Number(number) => RawValue::Number(number),
132 }
133 }
134 let raw = into_raw(Value::Object(ahash::HashMap::from_iter(values)));
135 if let RawValue::Object(raw_obj) = raw {
136 Self::resolve_object::<T>(raw_obj)
137 } else {
138 unreachable!("raw should always be an object");
139 }
140 }
141
142 pub fn parse_str<T>(s: &str, options: Option<ConfigOptions>) -> crate::Result<T>
143 where
144 T: DeserializeOwned,
145 {
146 let read = StrRead::new(s);
147 let raw = parse_hocon(read, options.unwrap_or_default())?;
148 Self::resolve_object::<T>(raw)
149 }
150
151 pub fn parse_reader<R, T>(rdr: R, options: Option<ConfigOptions>) -> crate::Result<T>
152 where
153 R: std::io::Read,
154 T: DeserializeOwned,
155 {
156 use crate::parser::read::DEFAULT_BUFFER;
157 let read: StreamRead<_, DEFAULT_BUFFER> = StreamRead::new(std::io::BufReader::new(rdr));
158 let raw = parse_hocon(read, options.unwrap_or_default())?;
159 Self::resolve_object::<T>(raw)
160 }
161
162 fn resolve_object<T>(object: RawObject) -> crate::Result<T>
163 where
164 T: DeserializeOwned,
165 {
166 let object = MObject::from_raw(None, object)?;
167 let mut value = MValue::Object(object);
168 value.resolve()?;
169 if value.is_unmerged() {
170 return Err(crate::error::Error::ResolveIncomplete);
171 }
172 let value: Value = value.try_into()?;
173 T::deserialize(value)
174 }
175}
176
177impl From<RawObject> for Config {
178 fn from(value: RawObject) -> Self {
179 Config {
180 object: value,
181 options: Default::default(),
182 }
183 }
184}
185
186impl From<std::collections::HashMap<String, Value>> for Config {
193 fn from(value: std::collections::HashMap<String, Value>) -> Self {
194 let fields = value
195 .into_iter()
196 .map(|(k, v)| ObjectField::key_value(k, v))
197 .collect();
198 Config {
199 object: RawObject::new(fields),
200 options: Default::default(),
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::Result;
208 use crate::error::Error;
209 use crate::{config::Config, config_options::ConfigOptions, value::Value};
210 use rstest::rstest;
211
212 impl Value {
213 pub fn assert_deep_eq(&self, other: &Value, path: &str) {
214 match (self, other) {
215 (Value::Object(map1), Value::Object(map2)) => {
216 for (k, v1) in map1 {
217 let new_path = format!("{}/{}", path, k);
218 if let Some(v2) = map2.get(k) {
219 v1.assert_deep_eq(v2, &new_path);
220 } else {
221 panic!("Key missing in right: {}", new_path);
222 }
223 }
224 for k in map2.keys() {
225 if !map1.contains_key(k) {
226 panic!("Key missing in left: {}/{}", path, k);
227 }
228 }
229 }
230 (Value::Array(arr1), Value::Array(arr2)) => {
231 let len = arr1.len().max(arr2.len());
232 for i in 0..len {
233 let new_path = format!("{}/[{}]", path, i);
234 match (arr1.get(i), arr2.get(i)) {
235 (Some(v1), Some(v2)) => v1.assert_deep_eq(v2, &new_path),
236 (Some(_), None) => panic!("Index missing in right: {}", new_path),
237 (None, Some(_)) => panic!("Index missing in left: {}", new_path),
238 _ => {}
239 }
240 }
241 }
242 _ => {
243 assert_eq!(
244 self, other,
245 "Difference at {}: left={:?}, right={:?}",
246 path, self, other
247 );
248 }
249 }
250 }
251 }
252
253 #[rstest]
254 #[case("resources/empty.conf", "resources/empty.json")]
255 #[case("resources/base.conf", "resources/base.json")]
256 #[case("resources/add_assign.conf", "resources/add_assign_expected.json")]
257 #[case("resources/concat.conf", "resources/concat.json")]
258 #[case("resources/concat2.conf", "resources/concat2.json")]
259 #[case("resources/concat3.conf", "resources/concat3.json")]
260 #[case("resources/concat4.conf", "resources/concat4.json")]
261 #[case("resources/concat5.conf", "resources/concat5.json")]
262 #[case("resources/include.conf", "resources/include.json")]
263 #[case("resources/comment.conf", "resources/comment.json")]
264 #[case("resources/substitution.conf", "resources/substitution.json")]
265 #[case("resources/self_referential.conf", "resources/self_referential.json")]
266 fn test_hocon(
267 #[case] hocon: impl AsRef<std::path::Path>,
268 #[case] json: impl AsRef<std::path::Path>,
269 ) -> Result<()> {
270 let mut options = ConfigOptions::default();
271 options.classpath = vec!["resources".to_string()].into();
272 let value = Config::load::<Value>(hocon, Some(options))?;
273 let f = std::fs::File::open(json)?;
274 let expected_value: serde_json::Value = serde_json::from_reader(f)?;
275 let expected_value: Value = expected_value.into();
276 value.assert_deep_eq(&expected_value, "$");
277 Ok(())
278 }
279
280 #[test]
281 fn test_max_depth() -> Result<()> {
282 let error = Config::load::<Value>("resources/max_depth.conf", None)
283 .err()
284 .unwrap();
285 assert!(matches!(error, Error::RecursionDepthExceeded { .. }));
286 Ok(())
287 }
288
289 #[test]
290 fn test_include_cycle() -> Result<()> {
291 let mut options = ConfigOptions::default();
292 options.classpath = vec!["resources".to_string()].into();
293 let error = Config::load::<Value>("resources/include_cycle.conf", Some(options))
294 .err()
295 .unwrap();
296 assert!(matches!(error, Error::Include { .. }));
297 Ok(())
298 }
299
300 #[test]
301 fn test_substitution_cycle() -> Result<()> {
302 let mut options = ConfigOptions::default();
303 options.classpath = vec!["resources".to_string()].into();
304 let error = Config::load::<Value>("resources/substitution_cycle.conf", Some(options))
305 .err()
306 .unwrap();
307 assert!(matches!(error, Error::SubstitutionCycle { .. }));
308 Ok(())
309 }
310
311 #[test]
312 fn test_substitution_not_found() -> Result<()> {
313 let mut options = ConfigOptions::default();
314 options.classpath = vec!["resources".to_string()].into();
315 let error = Config::load::<Value>("resources/substitution2.conf", Some(options))
316 .err()
317 .unwrap();
318 assert!(matches!(error, Error::SubstitutionNotFound { .. }));
319 Ok(())
320 }
321}