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 options: ConfigOptions,
69
70 #[cfg(feature = "noml")]
72 noml_document: Option<noml::Document>,
73
74 #[cfg(feature = "validation")]
76 validation_rules: Option<ValidationRuleSet>,
77}
78
79impl Config {
80 pub fn new() -> Self {
82 Self {
83 values: Value::table(BTreeMap::new()),
84 file_path: None,
85 format: "conf".to_string(),
86 modified: false,
87 options: ConfigOptions::default(),
88 #[cfg(feature = "noml")]
89 noml_document: None,
90 #[cfg(feature = "validation")]
91 validation_rules: None,
92 }
93 }
94
95 pub fn from_string(source: &str, format: Option<&str>) -> Result<Self> {
97 let detected_format = format.unwrap_or_else(|| parsers::detect_format(source));
98
99 let values = parsers::parse_string(source, Some(detected_format))?;
100
101 #[cfg(feature = "noml")]
102 let mut config = Self {
103 values,
104 file_path: None,
105 format: detected_format.to_string(),
106 modified: false,
107 options: ConfigOptions::default(),
108 noml_document: None,
109 #[cfg(feature = "validation")]
110 validation_rules: None,
111 };
112
113 #[cfg(not(feature = "noml"))]
114 let config = Self {
115 values,
116 file_path: None,
117 format: detected_format.to_string(),
118 modified: false,
119 options: ConfigOptions::default(),
120 #[cfg(feature = "validation")]
121 validation_rules: None,
122 };
123
124 #[cfg(feature = "noml")]
126 if detected_format == "noml" || detected_format == "toml" {
127 if let Ok(document) = noml::parse_string(source, None) {
128 config.noml_document = Some(document);
129 }
130 }
131
132 Ok(config)
133 }
134
135 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
137 let path = path.as_ref();
138 let content =
139 std::fs::read_to_string(path).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 #[cfg(feature = "async")]
152 pub async fn from_file_async<P: AsRef<Path>>(path: P) -> Result<Self> {
153 let path = path.as_ref();
154 let content = tokio::fs::read_to_string(path)
155 .await
156 .map_err(|e| Error::io(path.display().to_string(), e))?;
157
158 let format = parsers::detect_format_from_path(path)
159 .unwrap_or_else(|| parsers::detect_format(&content));
160
161 let mut config = Self::from_string(&content, Some(format))?;
162 config.file_path = Some(path.to_path_buf());
163
164 Ok(config)
165 }
166
167 pub fn get(&self, path: &str) -> Option<&Value> {
169 self.values.get(path)
170 }
171
172 pub fn get_mut(&mut self, path: &str) -> Result<&mut Value> {
174 self.values.get_mut_nested(path)
175 }
176
177 pub fn set<V: Into<Value>>(&mut self, path: &str, value: V) -> Result<()> {
185 self.ensure_writable()?;
186 self.values.set_nested(path, value.into())?;
187 self.modified = true;
188 Ok(())
189 }
190
191 pub fn remove(&mut self, path: &str) -> Result<Option<Value>> {
199 self.ensure_writable()?;
200 let result = self.values.remove(path)?;
201 if result.is_some() {
202 self.modified = true;
203 }
204 Ok(result)
205 }
206
207 pub fn contains_key(&self, path: &str) -> bool {
209 self.values.contains_key(path)
210 }
211
212 pub fn keys(&self) -> Result<Vec<&str>> {
214 self.values.keys()
215 }
216
217 pub fn is_modified(&self) -> bool {
219 self.modified
220 }
221
222 pub fn mark_clean(&mut self) {
224 self.modified = false;
225 }
226
227 pub fn format(&self) -> &str {
229 &self.format
230 }
231
232 pub fn file_path(&self) -> Option<&Path> {
234 self.file_path.as_deref()
235 }
236
237 pub fn save(&mut self) -> Result<()> {
239 match &self.file_path {
240 Some(path) => {
241 self.save_to_file(path.clone())?;
242 self.modified = false;
243 Ok(())
244 }
245 None => Err(Error::internal(
246 "Cannot save configuration that wasn't loaded from a file",
247 )),
248 }
249 }
250
251 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
253 let serialized = self.serialize()?;
254 std::fs::write(path, serialized).map_err(|e| Error::io("save".to_string(), e))?;
255 Ok(())
256 }
257
258 #[cfg(feature = "async")]
260 pub async fn save_async(&mut self) -> Result<()> {
261 match &self.file_path {
262 Some(path) => {
263 self.save_to_file_async(path.clone()).await?;
264 self.modified = false;
265 Ok(())
266 }
267 None => Err(Error::internal(
268 "Cannot save configuration that wasn't loaded from a file",
269 )),
270 }
271 }
272
273 #[cfg(feature = "async")]
275 pub async fn save_to_file_async<P: AsRef<Path>>(&self, path: P) -> Result<()> {
276 let serialized = self.serialize()?;
277 tokio::fs::write(path, serialized)
278 .await
279 .map_err(|e| Error::io("save".to_string(), e))?;
280 Ok(())
281 }
282
283 pub fn serialize(&self) -> Result<String> {
285 match self.format.as_str() {
286 "json" => {
287 #[cfg(feature = "json")]
288 return crate::parsers::json_parser::serialize(&self.values);
289 #[cfg(not(feature = "json"))]
290 return Err(Error::feature_not_enabled("json"));
291 }
292 "toml" => {
293 #[cfg(feature = "toml")]
294 {
295 #[cfg(feature = "noml")]
297 if let Some(ref document) = self.noml_document {
298 return Ok(noml::serialize_document(document)?);
299 }
300 self.serialize_as_toml()
302 }
303 #[cfg(not(feature = "toml"))]
304 return Err(Error::feature_not_enabled("toml"));
305 }
306 "noml" => {
307 #[cfg(feature = "noml")]
308 {
309 if let Some(ref document) = self.noml_document {
310 Ok(noml::serialize_document(document)?)
311 } else {
312 Err(Error::internal("NOML document not preserved"))
313 }
314 }
315 #[cfg(not(feature = "noml"))]
316 return Err(Error::feature_not_enabled("noml"));
317 }
318 "conf" => self.serialize_as_conf(),
319 _ => Err(Error::unknown_format(&self.format)),
320 }
321 }
322
323 fn serialize_as_conf(&self) -> Result<String> {
325 let mut output = String::new();
326 if let Value::Table(table) = &self.values {
327 self.write_conf_table(&mut output, table, "")?;
328 }
329 Ok(output)
330 }
331
332 fn write_conf_table(
334 &self,
335 output: &mut String,
336 table: &BTreeMap<String, Value>,
337 section_prefix: &str,
338 ) -> Result<()> {
339 for (key, value) in table {
341 if !value.is_table() {
342 let formatted_value = self.format_conf_value(value)?;
343 output.push_str(&format!("{key} = {formatted_value}\n"));
344 }
345 }
346
347 for (key, value) in table {
349 if let Value::Table(nested_table) = value {
350 let section_name = if section_prefix.is_empty() {
351 key.clone()
352 } else {
353 format!("{section_prefix}.{key}")
354 };
355
356 output.push_str(&format!("\n[{section_name}]\n"));
357 self.write_conf_table(output, nested_table, §ion_name)?;
358 }
359 }
360
361 Ok(())
362 }
363
364 #[allow(clippy::only_used_in_recursion)]
366 fn format_conf_value(&self, value: &Value) -> Result<String> {
367 match value {
368 Value::Null => Ok("null".to_string()),
369 Value::Bool(b) => Ok(b.to_string()),
370 Value::Integer(i) => Ok(i.to_string()),
371 Value::Float(f) => Ok(f.to_string()),
372 Value::String(s) => {
373 if s.contains(' ') || s.contains('\t') || s.contains('\n') {
374 Ok(format!("\"{}\"", s.replace('"', "\\\"")))
375 } else {
376 Ok(s.clone())
377 }
378 }
379 Value::Array(arr) => {
380 let items: Result<Vec<String>> =
381 arr.iter().map(|v| self.format_conf_value(v)).collect();
382 Ok(items?.join(" "))
383 }
384 Value::Table(_) => Err(Error::type_error(
385 "Cannot serialize nested table as value",
386 "primitive",
387 "table",
388 )),
389 #[cfg(feature = "chrono")]
390 Value::DateTime(dt) => Ok(dt.to_rfc3339()),
391 }
392 }
393
394 #[cfg(feature = "toml")]
396 fn serialize_as_toml(&self) -> Result<String> {
397 Err(Error::internal(
400 "Basic TOML serialization not implemented - use NOML library",
401 ))
402 }
403
404 #[cfg(feature = "schema")]
406 pub fn validate_schema(&self, schema: &Schema) -> Result<()> {
407 schema.validate(&self.values)
408 }
409
410 pub fn as_value(&self) -> &Value {
412 &self.values
413 }
414
415 pub fn merge(&mut self, other: &Config) -> Result<()> {
422 self.ensure_writable()?;
423 self.merge_value(&other.values)?;
424 self.modified = true;
425 Ok(())
426 }
427
428 fn merge_value(&mut self, other: &Value) -> Result<()> {
430 match (&mut self.values, other) {
431 (Value::Table(self_table), Value::Table(other_table)) => {
432 for (key, other_value) in other_table {
433 match self_table.get_mut(key) {
434 Some(self_value) => {
435 if let (Value::Table(_), Value::Table(_)) = (&*self_value, other_value)
436 {
437 let mut temp_config = Config::new();
439 temp_config.values = self_value.clone();
440 temp_config.merge_value(other_value)?;
441 *self_value = temp_config.values;
442 } else {
443 *self_value = other_value.clone();
445 }
446 }
447 None => {
448 self_table.insert(key.clone(), other_value.clone());
450 }
451 }
452 }
453 }
454 _ => {
455 self.values = other.clone();
457 }
458 }
459 Ok(())
460 }
461
462 pub fn key(&self, path: &str) -> ConfigValue<'_> {
470 ConfigValue::new(self.get(path))
471 }
472
473 pub fn has(&self, path: &str) -> bool {
475 self.contains_key(path)
476 }
477
478 pub fn get_or<V>(&self, path: &str, default: V) -> V
480 where
481 V: TryFrom<Value> + Clone,
482 V::Error: std::fmt::Debug,
483 {
484 self.get(path)
485 .and_then(|v| V::try_from(v.clone()).ok())
486 .unwrap_or(default)
487 }
488
489 #[cfg(feature = "validation")]
493 pub fn set_validation_rules(&mut self, rules: ValidationRuleSet) {
494 self.validation_rules = Some(rules);
495 }
496
497 #[cfg(feature = "validation")]
499 pub fn validate(&mut self) -> Result<Vec<ValidationError>> {
500 match &mut self.validation_rules {
501 Some(rules) => {
502 if let Value::Table(table) = &self.values {
503 let mut errors = Vec::new();
504
505 for (key, value) in table {
507 errors.extend(rules.validate(key, value));
508 }
509
510 Ok(errors)
514 } else {
515 Err(Error::validation(
516 "Configuration root must be a table for validation",
517 ))
518 }
519 }
520 None => Ok(Vec::new()), }
522 }
523
524 #[cfg(feature = "validation")]
526 pub fn validate_critical_only(&mut self) -> Result<Vec<ValidationError>> {
527 let all_errors = self.validate()?;
528 Ok(all_errors
529 .into_iter()
530 .filter(|e| e.severity == crate::validation::ValidationSeverity::Critical)
531 .collect())
532 }
533
534 #[cfg(feature = "validation")]
536 pub fn is_valid(&mut self) -> Result<bool> {
537 let critical_errors = self.validate_critical_only()?;
538 Ok(critical_errors.is_empty())
539 }
540
541 #[cfg(feature = "validation")]
543 pub fn validate_path(&mut self, path: &str) -> Result<Vec<ValidationError>> {
544 let value = self
546 .get(path)
547 .ok_or_else(|| Error::key_not_found(path))?
548 .clone();
549
550 match &mut self.validation_rules {
551 Some(rules) => Ok(rules.validate(path, &value)),
552 None => Ok(Vec::new()),
553 }
554 }
555}
556
557impl Default for Config {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563#[derive(Debug, Clone)]
595#[non_exhaustive]
596pub struct ConfigOptions {
597 pub read_only: bool,
601
602 pub cache_enabled: bool,
607
608 pub cache_capacity: usize,
611}
612
613impl Default for ConfigOptions {
614 fn default() -> Self {
618 Self {
619 read_only: false,
620 cache_enabled: true,
621 cache_capacity: 1024,
622 }
623 }
624}
625
626impl ConfigOptions {
627 pub fn new() -> Self {
630 Self::default()
631 }
632
633 pub fn read_only(mut self, read_only: bool) -> Self {
635 self.read_only = read_only;
636 self
637 }
638
639 pub fn cache_enabled(mut self, cache_enabled: bool) -> Self {
643 self.cache_enabled = cache_enabled;
644 self
645 }
646
647 pub fn cache_capacity(mut self, cache_capacity: usize) -> Self {
650 self.cache_capacity = cache_capacity;
651 self
652 }
653}
654
655impl Config {
656 pub fn with_options(options: ConfigOptions) -> Self {
662 let mut config = Self::new();
663 config.options = options;
664 config
665 }
666
667 pub fn options(&self) -> &ConfigOptions {
669 &self.options
670 }
671
672 pub fn is_read_only(&self) -> bool {
675 self.options.read_only
676 }
677
678 fn ensure_writable(&self) -> Result<()> {
681 if self.options.read_only {
682 Err(Error::general("Configuration is read-only"))
683 } else {
684 Ok(())
685 }
686 }
687}
688
689pub struct ConfigValue<'a> {
691 value: Option<&'a Value>,
692}
693
694impl<'a> ConfigValue<'a> {
695 fn new(value: Option<&'a Value>) -> Self {
696 Self { value }
697 }
698
699 pub fn as_string(&self) -> Result<String> {
701 match self.value {
702 Some(v) => v.as_string().map(|s| s.to_string()),
703 None => Err(Error::key_not_found("value not found")),
704 }
705 }
706
707 pub fn as_string_or(&self, default: &str) -> String {
709 self.value
710 .and_then(|v| v.as_string().ok())
711 .map(|s| s.to_string())
712 .unwrap_or_else(|| default.to_string())
713 }
714
715 pub fn as_integer(&self) -> Result<i64> {
717 match self.value {
718 Some(v) => v.as_integer(),
719 None => Err(Error::key_not_found("value not found")),
720 }
721 }
722
723 pub fn as_integer_or(&self, default: i64) -> i64 {
725 self.value
726 .and_then(|v| v.as_integer().ok())
727 .unwrap_or(default)
728 }
729
730 pub fn as_bool(&self) -> Result<bool> {
732 match self.value {
733 Some(v) => v.as_bool(),
734 None => Err(Error::key_not_found("value not found")),
735 }
736 }
737
738 pub fn as_bool_or(&self, default: bool) -> bool {
740 self.value.and_then(|v| v.as_bool().ok()).unwrap_or(default)
741 }
742
743 pub fn exists(&self) -> bool {
745 self.value.is_some()
746 }
747
748 pub fn value(&self) -> Option<&'a Value> {
750 self.value
751 }
752}
753
754pub struct ConfigBuilder {
756 format: Option<String>,
757 #[cfg(feature = "validation")]
758 validation_rules: Option<ValidationRuleSet>,
759}
760
761impl ConfigBuilder {
762 pub fn new() -> Self {
764 Self {
765 format: None,
766 #[cfg(feature = "validation")]
767 validation_rules: None,
768 }
769 }
770
771 pub fn format<S: Into<String>>(mut self, format: S) -> Self {
773 self.format = Some(format.into());
774 self
775 }
776
777 #[cfg(feature = "validation")]
779 pub fn validation_rules(mut self, rules: ValidationRuleSet) -> Self {
780 self.validation_rules = Some(rules);
781 self
782 }
783
784 pub fn from_string(self, source: &str) -> Result<Config> {
786 #[cfg(feature = "validation")]
787 let mut config = Config::from_string(source, self.format.as_deref())?;
788 #[cfg(not(feature = "validation"))]
789 let config = Config::from_string(source, self.format.as_deref())?;
790
791 #[cfg(feature = "validation")]
792 if let Some(rules) = self.validation_rules {
793 config.set_validation_rules(rules);
794 }
795
796 Ok(config)
797 }
798
799 pub fn from_file<P: AsRef<Path>>(self, path: P) -> Result<Config> {
801 #[cfg(feature = "validation")]
802 let mut config = Config::from_file(path)?;
803 #[cfg(not(feature = "validation"))]
804 let config = Config::from_file(path)?;
805
806 #[cfg(feature = "validation")]
807 if let Some(rules) = self.validation_rules {
808 config.set_validation_rules(rules);
809 }
810
811 Ok(config)
812 }
813}
814
815impl Default for ConfigBuilder {
816 fn default() -> Self {
817 Self::new()
818 }
819}
820
821impl From<Value> for Config {
823 fn from(value: Value) -> Self {
824 Self {
825 values: value,
826 file_path: None,
827 format: "conf".to_string(),
828 modified: false,
829 options: ConfigOptions::default(),
830 #[cfg(feature = "noml")]
831 noml_document: None,
832 #[cfg(feature = "validation")]
833 validation_rules: None,
834 }
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841
842 #[test]
843 fn test_config_creation() {
844 let config = Config::new();
845 assert!(!config.is_modified());
846 assert_eq!(config.format(), "conf");
847 }
848
849 #[test]
850 fn test_config_from_string() {
851 let config = Config::from_string("key = value\nport = 8080", Some("conf")).unwrap();
852
853 assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
854 assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
855 }
856
857 #[test]
858 fn test_config_modification() {
859 let mut config = Config::new();
860 assert!(!config.is_modified());
861
862 config.set("key", "value").unwrap();
863 assert!(config.is_modified());
864
865 config.mark_clean();
866 assert!(!config.is_modified());
867 }
868
869 #[test]
870 fn test_config_merge() {
871 let mut config1 = Config::new();
872 config1.set("a", 1).unwrap();
873 config1.set("b.x", 2).unwrap();
874
875 let mut config2 = Config::new();
876 config2.set("b.y", 3).unwrap();
877 config2.set("c", 4).unwrap();
878
879 config1.merge(&config2).unwrap();
880
881 assert_eq!(config1.get("a").unwrap().as_integer().unwrap(), 1);
882 assert_eq!(config1.get("b.x").unwrap().as_integer().unwrap(), 2);
883 assert_eq!(config1.get("b.y").unwrap().as_integer().unwrap(), 3);
884 assert_eq!(config1.get("c").unwrap().as_integer().unwrap(), 4);
885 }
886}