1use crate::error::{Error, Result};
7use crate::parsers;
8use crate::value::Value;
9use std::collections::BTreeMap;
10use std::path::{Path, PathBuf};
11
12#[cfg(feature = "schema")]
13use crate::schema::Schema;
14
15#[cfg(feature = "validation")]
16use crate::validation::{ValidationError, ValidationRuleSet};
17
18pub struct Config {
54 values: Value,
56
57 file_path: Option<PathBuf>,
59
60 format: String,
62
63 modified: bool,
65
66 #[cfg(feature = "noml")]
68 noml_document: Option<noml::Document>,
69
70 #[cfg(feature = "validation")]
72 validation_rules: Option<ValidationRuleSet>,
73}
74
75impl Config {
76 pub fn new() -> Self {
78 Self {
79 values: Value::table(BTreeMap::new()),
80 file_path: None,
81 format: "conf".to_string(),
82 modified: false,
83 #[cfg(feature = "noml")]
84 noml_document: None,
85 #[cfg(feature = "validation")]
86 validation_rules: None,
87 }
88 }
89
90 pub fn from_string(source: &str, format: Option<&str>) -> Result<Self> {
92 let detected_format = format.unwrap_or_else(|| parsers::detect_format(source));
93
94 let values = parsers::parse_string(source, Some(detected_format))?;
95
96 let mut config = Self {
97 values,
98 file_path: None,
99 format: detected_format.to_string(),
100 modified: false,
101 #[cfg(feature = "noml")]
102 noml_document: None,
103 #[cfg(feature = "validation")]
104 validation_rules: None,
105 };
106
107 #[cfg(feature = "noml")]
109 if detected_format == "noml" || detected_format == "toml" {
110 if let Ok(document) = noml::parse_string(source, None) {
111 config.noml_document = Some(document);
112 }
113 }
114
115 Ok(config)
116 }
117
118 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
120 let path = path.as_ref();
121 let content =
122 std::fs::read_to_string(path).map_err(|e| Error::io(path.display().to_string(), e))?;
123
124 let format = parsers::detect_format_from_path(path)
125 .unwrap_or_else(|| parsers::detect_format(&content));
126
127 let mut config = Self::from_string(&content, Some(format))?;
128 config.file_path = Some(path.to_path_buf());
129
130 Ok(config)
131 }
132
133 #[cfg(feature = "async")]
135 pub async fn from_file_async<P: AsRef<Path>>(path: P) -> Result<Self> {
136 let path = path.as_ref();
137 let content = tokio::fs::read_to_string(path)
138 .await
139 .map_err(|e| Error::io(path.display().to_string(), e))?;
140
141 let format = parsers::detect_format_from_path(path)
142 .unwrap_or_else(|| parsers::detect_format(&content));
143
144 let mut config = Self::from_string(&content, Some(format))?;
145 config.file_path = Some(path.to_path_buf());
146
147 Ok(config)
148 }
149
150 pub fn get(&self, path: &str) -> Option<&Value> {
152 self.values.get(path)
153 }
154
155 pub fn get_mut(&mut self, path: &str) -> Result<&mut Value> {
157 self.values.get_mut_nested(path)
158 }
159
160 pub fn set<V: Into<Value>>(&mut self, path: &str, value: V) -> Result<()> {
162 self.values.set_nested(path, value.into())?;
163 self.modified = true;
164 Ok(())
165 }
166
167 pub fn remove(&mut self, path: &str) -> Result<Option<Value>> {
169 let result = self.values.remove(path)?;
170 if result.is_some() {
171 self.modified = true;
172 }
173 Ok(result)
174 }
175
176 pub fn contains_key(&self, path: &str) -> bool {
178 self.values.contains_key(path)
179 }
180
181 pub fn keys(&self) -> Result<Vec<&str>> {
183 self.values.keys()
184 }
185
186 pub fn is_modified(&self) -> bool {
188 self.modified
189 }
190
191 pub fn mark_clean(&mut self) {
193 self.modified = false;
194 }
195
196 pub fn format(&self) -> &str {
198 &self.format
199 }
200
201 pub fn file_path(&self) -> Option<&Path> {
203 self.file_path.as_deref()
204 }
205
206 pub fn save(&mut self) -> Result<()> {
208 match &self.file_path {
209 Some(path) => {
210 self.save_to_file(path.clone())?;
211 self.modified = false;
212 Ok(())
213 }
214 None => Err(Error::internal(
215 "Cannot save configuration that wasn't loaded from a file",
216 )),
217 }
218 }
219
220 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
222 let serialized = self.serialize()?;
223 std::fs::write(path, serialized).map_err(|e| Error::io("save".to_string(), e))?;
224 Ok(())
225 }
226
227 #[cfg(feature = "async")]
229 pub async fn save_async(&mut self) -> Result<()> {
230 match &self.file_path {
231 Some(path) => {
232 self.save_to_file_async(path.clone()).await?;
233 self.modified = false;
234 Ok(())
235 }
236 None => Err(Error::internal(
237 "Cannot save configuration that wasn't loaded from a file",
238 )),
239 }
240 }
241
242 #[cfg(feature = "async")]
244 pub async fn save_to_file_async<P: AsRef<Path>>(&self, path: P) -> Result<()> {
245 let serialized = self.serialize()?;
246 tokio::fs::write(path, serialized)
247 .await
248 .map_err(|e| Error::io("save".to_string(), e))?;
249 Ok(())
250 }
251
252 pub fn serialize(&self) -> Result<String> {
254 match self.format.as_str() {
255 "json" => {
256 #[cfg(feature = "json")]
257 return crate::parsers::json_parser::serialize(&self.values);
258 #[cfg(not(feature = "json"))]
259 return Err(Error::feature_not_enabled("json"));
260 }
261 "toml" => {
262 #[cfg(feature = "toml")]
263 {
264 #[cfg(feature = "noml")]
266 if let Some(ref document) = self.noml_document {
267 return Ok(noml::serialize_document(document)?);
268 }
269 self.serialize_as_toml()
271 }
272 #[cfg(not(feature = "toml"))]
273 return Err(Error::feature_not_enabled("toml"));
274 }
275 "noml" => {
276 #[cfg(feature = "noml")]
277 {
278 if let Some(ref document) = self.noml_document {
279 Ok(noml::serialize_document(document)?)
280 } else {
281 Err(Error::internal("NOML document not preserved"))
282 }
283 }
284 #[cfg(not(feature = "noml"))]
285 return Err(Error::feature_not_enabled("noml"));
286 }
287 "conf" => self.serialize_as_conf(),
288 _ => Err(Error::unknown_format(&self.format)),
289 }
290 }
291
292 fn serialize_as_conf(&self) -> Result<String> {
294 let mut output = String::new();
295 if let Value::Table(table) = &self.values {
296 self.write_conf_table(&mut output, table, "")?;
297 }
298 Ok(output)
299 }
300
301 fn write_conf_table(
303 &self,
304 output: &mut String,
305 table: &BTreeMap<String, Value>,
306 section_prefix: &str,
307 ) -> Result<()> {
308 for (key, value) in table {
310 if !value.is_table() {
311 let formatted_value = self.format_conf_value(value)?;
312 output.push_str(&format!("{key} = {formatted_value}\n"));
313 }
314 }
315
316 for (key, value) in table {
318 if let Value::Table(nested_table) = value {
319 let section_name = if section_prefix.is_empty() {
320 key.clone()
321 } else {
322 format!("{section_prefix}.{key}")
323 };
324
325 output.push_str(&format!("\n[{section_name}]\n"));
326 self.write_conf_table(output, nested_table, §ion_name)?;
327 }
328 }
329
330 Ok(())
331 }
332
333 #[allow(clippy::only_used_in_recursion)]
335 fn format_conf_value(&self, value: &Value) -> Result<String> {
336 match value {
337 Value::Null => Ok("null".to_string()),
338 Value::Bool(b) => Ok(b.to_string()),
339 Value::Integer(i) => Ok(i.to_string()),
340 Value::Float(f) => Ok(f.to_string()),
341 Value::String(s) => {
342 if s.contains(' ') || s.contains('\t') || s.contains('\n') {
343 Ok(format!("\"{}\"", s.replace('"', "\\\"")))
344 } else {
345 Ok(s.clone())
346 }
347 }
348 Value::Array(arr) => {
349 let items: Result<Vec<String>> =
350 arr.iter().map(|v| self.format_conf_value(v)).collect();
351 Ok(items?.join(" "))
352 }
353 Value::Table(_) => Err(Error::type_error(
354 "Cannot serialize nested table as value",
355 "primitive",
356 "table",
357 )),
358 #[cfg(feature = "chrono")]
359 Value::DateTime(dt) => Ok(dt.to_rfc3339()),
360 }
361 }
362
363 #[cfg(feature = "toml")]
365 fn serialize_as_toml(&self) -> Result<String> {
366 Err(Error::internal(
369 "Basic TOML serialization not implemented - use NOML library",
370 ))
371 }
372
373 #[cfg(feature = "schema")]
375 pub fn validate_schema(&self, schema: &Schema) -> Result<()> {
376 schema.validate(&self.values)
377 }
378
379 pub fn as_value(&self) -> &Value {
381 &self.values
382 }
383
384 pub fn merge(&mut self, other: &Config) -> Result<()> {
386 self.merge_value(&other.values)?;
387 self.modified = true;
388 Ok(())
389 }
390
391 fn merge_value(&mut self, other: &Value) -> Result<()> {
393 match (&mut self.values, other) {
394 (Value::Table(self_table), Value::Table(other_table)) => {
395 for (key, other_value) in other_table {
396 match self_table.get_mut(key) {
397 Some(self_value) => {
398 if let (Value::Table(_), Value::Table(_)) = (&*self_value, other_value)
399 {
400 let mut temp_config = Config::new();
402 temp_config.values = self_value.clone();
403 temp_config.merge_value(other_value)?;
404 *self_value = temp_config.values;
405 } else {
406 *self_value = other_value.clone();
408 }
409 }
410 None => {
411 self_table.insert(key.clone(), other_value.clone());
413 }
414 }
415 }
416 }
417 _ => {
418 self.values = other.clone();
420 }
421 }
422 Ok(())
423 }
424
425 pub fn key(&self, path: &str) -> ConfigValue {
433 ConfigValue::new(self.get(path))
434 }
435
436 pub fn has(&self, path: &str) -> bool {
438 self.contains_key(path)
439 }
440
441 pub fn get_or<V>(&self, path: &str, default: V) -> V
443 where
444 V: TryFrom<Value> + Clone,
445 V::Error: std::fmt::Debug,
446 {
447 self.get(path)
448 .and_then(|v| V::try_from(v.clone()).ok())
449 .unwrap_or(default)
450 }
451
452 #[cfg(feature = "validation")]
456 pub fn set_validation_rules(&mut self, rules: ValidationRuleSet) {
457 self.validation_rules = Some(rules);
458 }
459
460 #[cfg(feature = "validation")]
462 pub fn validate(&mut self) -> Result<Vec<ValidationError>> {
463 match &mut self.validation_rules {
464 Some(rules) => {
465 if let Value::Table(table) = &self.values {
466 let mut errors = Vec::new();
467
468 for (key, value) in table {
470 errors.extend(rules.validate(key, value));
471 }
472
473 Ok(errors)
477 } else {
478 Err(Error::validation(
479 "Configuration root must be a table for validation",
480 ))
481 }
482 }
483 None => Ok(Vec::new()), }
485 }
486
487 #[cfg(feature = "validation")]
489 pub fn validate_critical_only(&mut self) -> Result<Vec<ValidationError>> {
490 let all_errors = self.validate()?;
491 Ok(all_errors
492 .into_iter()
493 .filter(|e| e.severity == crate::validation::ValidationSeverity::Critical)
494 .collect())
495 }
496
497 #[cfg(feature = "validation")]
499 pub fn is_valid(&mut self) -> Result<bool> {
500 let critical_errors = self.validate_critical_only()?;
501 Ok(critical_errors.is_empty())
502 }
503
504 #[cfg(feature = "validation")]
506 pub fn validate_path(&mut self, path: &str) -> Result<Vec<ValidationError>> {
507 let value = self
509 .get(path)
510 .ok_or_else(|| Error::key_not_found(path))?
511 .clone();
512
513 match &mut self.validation_rules {
514 Some(rules) => Ok(rules.validate(path, &value)),
515 None => Ok(Vec::new()),
516 }
517 }
518}
519
520impl Default for Config {
521 fn default() -> Self {
522 Self::new()
523 }
524}
525
526pub struct ConfigValue<'a> {
528 value: Option<&'a Value>,
529}
530
531impl<'a> ConfigValue<'a> {
532 fn new(value: Option<&'a Value>) -> Self {
533 Self { value }
534 }
535
536 pub fn as_string(&self) -> Result<String> {
538 match self.value {
539 Some(v) => v.as_string().map(|s| s.to_string()),
540 None => Err(Error::key_not_found("value not found")),
541 }
542 }
543
544 pub fn as_string_or(&self, default: &str) -> String {
546 self.value
547 .and_then(|v| v.as_string().ok())
548 .map(|s| s.to_string())
549 .unwrap_or_else(|| default.to_string())
550 }
551
552 pub fn as_integer(&self) -> Result<i64> {
554 match self.value {
555 Some(v) => v.as_integer(),
556 None => Err(Error::key_not_found("value not found")),
557 }
558 }
559
560 pub fn as_integer_or(&self, default: i64) -> i64 {
562 self.value
563 .and_then(|v| v.as_integer().ok())
564 .unwrap_or(default)
565 }
566
567 pub fn as_bool(&self) -> Result<bool> {
569 match self.value {
570 Some(v) => v.as_bool(),
571 None => Err(Error::key_not_found("value not found")),
572 }
573 }
574
575 pub fn as_bool_or(&self, default: bool) -> bool {
577 self.value.and_then(|v| v.as_bool().ok()).unwrap_or(default)
578 }
579
580 pub fn exists(&self) -> bool {
582 self.value.is_some()
583 }
584
585 pub fn value(&self) -> Option<&'a Value> {
587 self.value
588 }
589}
590
591pub struct ConfigBuilder {
593 format: Option<String>,
594 #[cfg(feature = "validation")]
595 validation_rules: Option<ValidationRuleSet>,
596}
597
598impl ConfigBuilder {
599 pub fn new() -> Self {
601 Self {
602 format: None,
603 #[cfg(feature = "validation")]
604 validation_rules: None,
605 }
606 }
607
608 pub fn format<S: Into<String>>(mut self, format: S) -> Self {
610 self.format = Some(format.into());
611 self
612 }
613
614 #[cfg(feature = "validation")]
616 pub fn validation_rules(mut self, rules: ValidationRuleSet) -> Self {
617 self.validation_rules = Some(rules);
618 self
619 }
620
621 pub fn from_string(self, source: &str) -> Result<Config> {
623 #[cfg(feature = "validation")]
624 let mut config = Config::from_string(source, self.format.as_deref())?;
625 #[cfg(not(feature = "validation"))]
626 let config = Config::from_string(source, self.format.as_deref())?;
627
628 #[cfg(feature = "validation")]
629 if let Some(rules) = self.validation_rules {
630 config.set_validation_rules(rules);
631 }
632
633 Ok(config)
634 }
635
636 pub fn from_file<P: AsRef<Path>>(self, path: P) -> Result<Config> {
638 #[cfg(feature = "validation")]
639 let mut config = Config::from_file(path)?;
640 #[cfg(not(feature = "validation"))]
641 let config = Config::from_file(path)?;
642
643 #[cfg(feature = "validation")]
644 if let Some(rules) = self.validation_rules {
645 config.set_validation_rules(rules);
646 }
647
648 Ok(config)
649 }
650}
651
652impl Default for ConfigBuilder {
653 fn default() -> Self {
654 Self::new()
655 }
656}
657
658impl From<Value> for Config {
660 fn from(value: Value) -> Self {
661 Self {
662 values: value,
663 file_path: None,
664 format: "conf".to_string(),
665 modified: false,
666 #[cfg(feature = "noml")]
667 noml_document: None,
668 #[cfg(feature = "validation")]
669 validation_rules: None,
670 }
671 }
672}
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[test]
679 fn test_config_creation() {
680 let config = Config::new();
681 assert!(!config.is_modified());
682 assert_eq!(config.format(), "conf");
683 }
684
685 #[test]
686 fn test_config_from_string() {
687 let config = Config::from_string("key = value\nport = 8080", Some("conf")).unwrap();
688
689 assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
690 assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
691 }
692
693 #[test]
694 fn test_config_modification() {
695 let mut config = Config::new();
696 assert!(!config.is_modified());
697
698 config.set("key", "value").unwrap();
699 assert!(config.is_modified());
700
701 config.mark_clean();
702 assert!(!config.is_modified());
703 }
704
705 #[test]
706 fn test_config_merge() {
707 let mut config1 = Config::new();
708 config1.set("a", 1).unwrap();
709 config1.set("b.x", 2).unwrap();
710
711 let mut config2 = Config::new();
712 config2.set("b.y", 3).unwrap();
713 config2.set("c", 4).unwrap();
714
715 config1.merge(&config2).unwrap();
716
717 assert_eq!(config1.get("a").unwrap().as_integer().unwrap(), 1);
718 assert_eq!(config1.get("b.x").unwrap().as_integer().unwrap(), 2);
719 assert_eq!(config1.get("b.y").unwrap().as_integer().unwrap(), 3);
720 assert_eq!(config1.get("c").unwrap().as_integer().unwrap(), 4);
721 }
722}