use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigChange {
pub parameter: String,
pub current: Option<String>,
pub desired: String,
pub change_type: ChangeType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChangeType {
Add,
Modify,
Remove,
}
impl fmt::Display for ConfigChange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.change_type {
ChangeType::Add => write!(f, "+ {}: {}", self.parameter, self.desired),
ChangeType::Modify => write!(
f,
"~ {}: {} -> {}",
self.parameter,
self.current.as_deref().unwrap_or("(unset)"),
self.desired
),
ChangeType::Remove => write!(
f,
"- {}: {}",
self.parameter,
self.current.as_deref().unwrap_or("(unset)")
),
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigDiff {
changes: Vec<ConfigChange>,
}
impl ConfigDiff {
pub fn new() -> Self {
Self {
changes: Vec::new(),
}
}
pub fn add_change(&mut self, change: ConfigChange) {
self.changes.push(change);
}
pub fn has_changes(&self) -> bool {
!self.changes.is_empty()
}
pub fn changes(&self) -> &[ConfigChange] {
&self.changes
}
pub fn len(&self) -> usize {
self.changes.len()
}
pub fn is_empty(&self) -> bool {
self.changes.is_empty()
}
pub fn changes_by_type(&self, change_type: ChangeType) -> Vec<&ConfigChange> {
self.changes
.iter()
.filter(|c| c.change_type == change_type)
.collect()
}
pub fn additions(&self) -> Vec<&ConfigChange> {
self.changes_by_type(ChangeType::Add)
}
pub fn modifications(&self) -> Vec<&ConfigChange> {
self.changes_by_type(ChangeType::Modify)
}
pub fn removals(&self) -> Vec<&ConfigChange> {
self.changes_by_type(ChangeType::Remove)
}
pub fn summary(&self) -> String {
if self.is_empty() {
return "No changes".to_string();
}
let adds = self.additions().len();
let mods = self.modifications().len();
let rems = self.removals().len();
let mut parts = Vec::new();
if adds > 0 {
parts.push(format!(
"{} addition{}",
adds,
if adds == 1 { "" } else { "s" }
));
}
if mods > 0 {
parts.push(format!(
"{} modification{}",
mods,
if mods == 1 { "" } else { "s" }
));
}
if rems > 0 {
parts.push(format!(
"{} removal{}",
rems,
if rems == 1 { "" } else { "s" }
));
}
parts.join(", ")
}
}
impl Default for ConfigDiff {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ConfigDiff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
write!(f, "No configuration changes")
} else {
writeln!(f, "Configuration changes ({}):", self.summary())?;
for change in &self.changes {
writeln!(f, " {}", change)?;
}
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_change_display() {
let change = ConfigChange {
parameter: "max_connections".to_string(),
current: Some("100".to_string()),
desired: "200".to_string(),
change_type: ChangeType::Modify,
};
assert_eq!(change.to_string(), "~ max_connections: 100 -> 200");
let change = ConfigChange {
parameter: "shared_buffers".to_string(),
current: None,
desired: "256MB".to_string(),
change_type: ChangeType::Add,
};
assert_eq!(change.to_string(), "+ shared_buffers: 256MB");
}
#[test]
fn test_config_diff() {
let mut diff = ConfigDiff::new();
assert!(!diff.has_changes());
assert_eq!(diff.len(), 0);
diff.add_change(ConfigChange {
parameter: "max_connections".to_string(),
current: Some("100".to_string()),
desired: "200".to_string(),
change_type: ChangeType::Modify,
});
diff.add_change(ConfigChange {
parameter: "shared_buffers".to_string(),
current: None,
desired: "256MB".to_string(),
change_type: ChangeType::Add,
});
assert!(diff.has_changes());
assert_eq!(diff.len(), 2);
assert_eq!(diff.additions().len(), 1);
assert_eq!(diff.modifications().len(), 1);
assert_eq!(diff.removals().len(), 0);
}
#[test]
fn test_diff_summary() {
let mut diff = ConfigDiff::new();
assert_eq!(diff.summary(), "No changes");
diff.add_change(ConfigChange {
parameter: "test".to_string(),
current: None,
desired: "value".to_string(),
change_type: ChangeType::Add,
});
assert_eq!(diff.summary(), "1 addition");
diff.add_change(ConfigChange {
parameter: "test2".to_string(),
current: Some("old".to_string()),
desired: "new".to_string(),
change_type: ChangeType::Modify,
});
assert_eq!(diff.summary(), "1 addition, 1 modification");
}
#[test]
fn test_diff_display() {
let mut diff = ConfigDiff::new();
diff.add_change(ConfigChange {
parameter: "max_connections".to_string(),
current: Some("100".to_string()),
desired: "200".to_string(),
change_type: ChangeType::Modify,
});
let display = diff.to_string();
assert!(display.contains("Configuration changes"));
assert!(display.contains("max_connections"));
}
}