prns_config/editing/
repair.rs1use std::collections::BTreeSet;
2use std::fmt;
3
4use crate::configobj::{ConfigDocument, ConfigError};
5use crate::{parse_and_plan_named, ConfigDiagnostic, ConfigFix};
6
7use super::{ConfigEdit, InterfaceConfigKey, InterfaceName};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ConfigRepairReport {
11 diagnostics: Vec<ConfigDiagnostic>,
12}
13
14impl ConfigRepairReport {
15 pub fn analyze(source: &str) -> Result<Self, ConfigRepairError> {
16 Self::analyze_named("<config>", source)
17 }
18
19 pub fn analyze_named(
20 source_name: impl Into<String>,
21 source: &str,
22 ) -> Result<Self, ConfigRepairError> {
23 ConfigDocument::parse(source).map_err(ConfigRepairError::Syntax)?;
24 let diagnostics = match parse_and_plan_named(source_name, source) {
25 Ok(report) => report.warnings,
26 Err(errors) => errors.diagnostics().to_vec(),
27 };
28 Ok(Self { diagnostics })
29 }
30
31 pub fn diagnostics(&self) -> &[ConfigDiagnostic] {
32 &self.diagnostics
33 }
34
35 pub fn safe_edit(&self) -> Option<ConfigEdit> {
36 let mut disabled = BTreeSet::new();
37 let mut removed = BTreeSet::new();
38 for fix in self
39 .diagnostics
40 .iter()
41 .flat_map(ConfigDiagnostic::fixes)
42 .filter(|fix| fix.is_safe())
43 {
44 match fix {
45 ConfigFix::DisableInterface { name } => {
46 if let Ok(name) = InterfaceName::new(name.clone()) {
47 disabled.insert(name);
48 }
49 }
50 ConfigFix::RemoveValue { path, .. } => {
51 if let Some(target) = interface_value(path) {
52 removed.insert(target);
53 }
54 }
55 ConfigFix::InsertValue { .. }
56 | ConfigFix::ReplaceValue { .. }
57 | ConfigFix::ResolveAliases { .. }
58 | ConfigFix::ChooseInterfaceType { .. } => {}
59 }
60 }
61 if disabled.is_empty() && removed.is_empty() {
62 return None;
63 }
64 let mut edits = disabled
65 .into_iter()
66 .map(|name| ConfigEdit::SetEnabled {
67 name,
68 enabled: false,
69 })
70 .collect::<Vec<_>>();
71 edits.extend(
72 removed
73 .into_iter()
74 .map(|(name, key)| ConfigEdit::RemoveInterfaceValue { name, key }),
75 );
76 Some(ConfigEdit::Batch(edits))
77 }
78}
79
80fn interface_value(path: &str) -> Option<(InterfaceName, InterfaceConfigKey)> {
81 let start = path.find("[[")? + 2;
82 let rest = &path[start..];
83 let end = rest.find("]]")?;
84 if rest[end + 2..].contains("[[[") {
85 return None;
86 }
87 let name = InterfaceName::new(rest[..end].trim()).ok()?;
88 let key = InterfaceConfigKey::new(path.rsplit(" > ").next()?.trim()).ok()?;
89 Some((name, key))
90}
91
92#[derive(Debug)]
93pub enum ConfigRepairError {
94 Syntax(ConfigError),
95}
96
97impl fmt::Display for ConfigRepairError {
98 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Syntax(error) => write!(
101 formatter,
102 "{error}; malformed ConfigObj syntax is preserved for manual correction"
103 ),
104 }
105 }
106}
107
108impl std::error::Error for ConfigRepairError {}